Link para o projetoNeste artigo, quero mostrar como você pode usar o 
SharedEvents para controlar um caractere de terceira pessoa que oferece um conjunto padrão de ativos. Eu escrevi sobre o 
SharedEvents em artigos anteriores ( 
isso e 
isso ).
Bem-vindo ao gato!
A primeira coisa que você precisa é levar um projeto com o SharedState / SharedEvents implementado e adicionar um conjunto padrão de ativos

Eu criei uma cena pequena e muito simples de pré-fabricar protótipos

E faça a navegação na superfície com configurações padrão

Depois disso, você precisa adicionar o 
ThirdPersonCharacter pré-fabricado a esta cena

Então você pode começar e garantir que tudo funcione imediatamente. Em seguida, você pode continuar a configurar o uso da 
infraestrutura SharedState / SharedEvents criada anteriormente. Para fazer isso, remova o componente 
ThirdPersonUserController do objeto de caractere.

pois o controle manual usando o teclado não é necessário. O personagem será controlado pelos agentes, indicando a posição para onde se moverá.
E para tornar isso possível, você precisa adicionar e configurar o componente 
NavMeshAgent ao objeto de caractere

Agora você precisa criar um controlador simples que controle o personagem
com o mouse 
AgentMouseController
using UnityEngine; using UnityEngine.AI; using UnityStandardAssets.Characters.ThirdPerson; public class AgentMouseController : MonoBehaviour { public NavMeshAgent agent; public ThirdPersonCharacter character; public Camera cam; void Start() {  
E adicione-o ao objeto do personagem, vincule-o à câmera, ao controlador do personagem e ao agente. Está tudo disponível a partir do palco.

E isso é tudo. Isso é suficiente para controlar o personagem dizendo ao agente para onde se mover, usando o mouse (clique esquerdo).
Você pode começar e garantir que tudo funcione

Integração SharedEvents
Agora que a cena base está pronta, você pode prosseguir para integrar o controle de caracteres através do 
SharedEvents . Para fazer isso, você precisará criar vários componentes. O primeiro deles é o componente que será responsável por receber o sinal do mouse e notificar todos os componentes que rastreiam a posição do clique do mouse na cena; eles estarão interessados apenas nas coordenadas do clique.
O componente será chamado, por exemplo, 
MouseHandlerComponent
 using UnityEngine; public class MouseHandlerComponent : SharedStateComponent { public Camera cam; #region MonoBehaviour protected override void OnSharedStateChanged(SharedStateChangedEventData newState) { } protected override void OnStart() { if (cam == null) throw new MissingReferenceException("   "); } protected override void OnUpdate() {  
Este componente precisa de uma classe para enviar dados nas notificações. Para essas classes que conterão apenas dados para notificações, você pode criar um arquivo e nomeá-lo como 
DefinedEventsData
E adicione uma classe a ela, para enviar a posição de um clique com o mouse
 using UnityEngine; public class PointOnGroundEventData : EventData { public Vector3 Point { get; set; } } 
A próxima coisa a fazer é adicionar um componente que será um wrapper ou decorador, como você desejar, para o componente 
NavMeshAgent . Como não alterarei os componentes existentes (de terceiros), usarei decoradores para integrar-se ao 
SharedState / SharedEvents .

Este componente receberá notificações sobre os cliques do mouse em determinados pontos da cena e informará o agente para onde se mover. E também monitore a posição da posição do agente em cada quadro e crie uma notificação sobre sua alteração.
Este componente dependerá do componente 
NavMeshAgent. using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent))] public class AgentWrapperComponent : SharedStateComponent { private NavMeshAgent agent; #region Monobehaviour protected override void OnSharedStateChanged(SharedStateChangedEventData newState) { } protected override void OnStart() {  
Para enviar dados, esse componente precisa de uma classe que precise ser adicionada ao arquivo 
DefinedEventsData. public class AgentMoveEventData : EventData { public Vector3 DesiredVelocity { get; set; } } 
Isso já é suficiente para o personagem se mover. Mas ele fará isso sem animação, pois ainda não estamos usando o 
ThirdPersonCharater . E, para isso, assim como no 
NavMeshAgent, você precisa criar um decorador CharacterWrapperComponent

O componente ouvirá notificações sobre a mudança de posição do agente e moverá o personagem na direção recebida da notificação (evento).
 using UnityEngine; using UnityStandardAssets.Characters.ThirdPerson; [RequireComponent(typeof(ThirdPersonCharacter))] public class CharacterWrapperComponent : SharedStateComponent { private ThirdPersonCharacter character; #region Monobehaviour protected override void OnSharedStateChanged(SharedStateChangedEventData newState) { } protected override void OnStart() { character = GetComponent<ThirdPersonCharacter>(); Events.Subscribe<AgentMoveEventData>("agentmoved", OnAgentMove); } protected override void OnUpdate() { } #endregion private void OnAgentMove(AgentMoveEventData eventData) { //       character.Move(eventData.DesiredVelocity, false, false); } } 
E isso é tudo. Resta adicionar esses componentes ao objeto de jogo do personagem. Você precisa criar uma cópia da existente, remover o antigo componente 
AgentMouseControl
E adicione os novos 
MouseHandlerComponent , 
AgentWrapperComponent e 
CharacterWrapperComponent .
No 
MouseHandlerComponent, você precisa transferir a câmera da cena a partir da qual a posição do clique será calculada.


Você pode iniciar e garantir que tudo funcione.
Aconteceu com a ajuda do 
SharedEvents para controlar o personagem sem ter uma conexão direta entre os componentes, como no primeiro exemplo. Isso permitirá uma configuração mais flexível de diferentes composições de componentes e personalizará a interação entre eles.
Comportamento assíncrono para SharedEvents
A maneira como o mecanismo de notificação é implementado agora é baseada na transmissão síncrona do sinal e seu processamento. Ou seja, quanto mais ouvintes houver, mais tempo levará para processar. Para evitar isso, você precisa implementar o processamento de notificações assíncronas. A primeira coisa a fazer é adicionar uma versão assíncrona do método 
Publish 
Agora você precisa alterar o método 
OnUpdate abstrato na classe base 
SharedStateComponent para assíncrono, para que ele retorne tarefas iniciadas dentro da implementação desse método e renomeie-o para 
OnUpdateAsync protected abstract Task[] OnUpdateAsync(); 
Você também precisará de um mecanismo que controle a conclusão das tarefas do quadro anterior, antes do atual
 private Task[] _previosFrameTasks = null;  
O método 
Update na classe base precisa ser marcado como 
assíncrono e verificar previamente a execução de tarefas anteriores
 async void Update() { await CompletePreviousTasks();  
Após essas alterações na classe base, você pode continuar alterando a implementação do método 
OnUpdate antigo para o novo 
OnUpdateAsync . O primeiro componente em que isso será feito é o 
AgentWrapperComponent . Agora este método espera o retorno do resultado. Este resultado será uma variedade de tarefas. Uma matriz porque no método várias podem ser iniciadas em paralelo e as processaremos em conjunto.
 protected override Task[] OnUpdateAsync() {  
O próximo candidato a alterações no método 
OnUpdate é 
MouseHandlerController . Aqui o princípio é o mesmo
  protected override Task[] OnUpdateAsync() {  
Em todas as outras implementações em que esse método estava vazio, basta substituir por
 protected override Task[] OnUpdateAsync() { return null; } 
Só isso. Agora você pode iniciar e, se os componentes que processam notificações de forma assíncrona não acessarem os componentes que devem ser processados no encadeamento principal, como o Transform, por exemplo, tudo funcionará. Caso contrário, obteremos erros no console informando que estamos acessando esses componentes e não do encadeamento principal

Para resolver esse problema, você precisa criar um componente que processará o código no thread principal. Crie uma pasta separada para scripts e chame-o de Sistema, e também adicione o script 
Dispatcher .

Este componente será um singleton e terá um método abstrato público que executará o código no thread principal. O princípio do expedidor é bastante simples. Passaremos a ele os delegados para serem executados no thread principal, ele os colocará na fila. E em cada quadro, se houver algo na fila, execute no encadeamento principal. Este componente se adiciona à cena em uma única cópia, eu gosto de uma abordagem tão simples e eficaz.
 using System; using System.Collections; using System.Collections.Concurrent; using UnityEngine; public class Dispatcher : MonoBehaviour { private static Dispatcher _instance; private volatile bool _queued = false; private ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>(); private static readonly object _sync_ = new object();  
A próxima coisa a fazer é aplicar o expedidor. Existem 2 lugares para fazer isso. 1º é o decorador do personagem, lá pedimos a ele a direção. No componente 
CharacterWrapperComponent private void OnAgentMove(AgentMoveEventData eventData) { Dispatcher.RunOnMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); } 
2º é o decorador do agente, lá indicamos a posição do agente. No componente 
AgentWrapperComponent private void OnPointToGroundGot(PointOnGroundEventData eventData) {  
Agora não haverá erros, o código funcionará corretamente. Você pode começar e ver isso.
Um pouco de refatoração
Depois que tudo estiver pronto e tudo funcionar, você poderá pentear o código um pouco e torná-lo um pouco mais conveniente e simples. Isso exigirá algumas alterações.
Para não criar uma matriz de tarefas e colocar a única manualmente, você pode criar um método de extensão. Para todos os métodos de extensão, você pode usar o mesmo arquivo para transmitir notificações, bem como para todas as classes. Ele estará localizado na pasta 
Sistema e chamado 
Extensões
Dentro, criaremos um método de extensão genérico simples que envolverá qualquer instância em uma matriz
 public static class Extensions {  
A próxima alteração está ocultando o uso direto do despachante nos componentes. Em vez disso, crie um método na classe base 
SharedStateComponent e use o distribuidor a partir daí.
 protected void PerformInMainThread(Action action) { Dispatcher.RunOnMainThread(action); } 
E agora você precisa aplicar essas alterações em vários lugares. Primeiro, altere os métodos nos quais criamos manualmente matrizes de tarefas e colocamos nelas uma única instância
No componente 
AgentWrapperComponent protected override Task[] OnUpdateAsync() {  
E no componente 
MouseHandlerComponent protected override Task[] OnUpdateAsync() {  
Agora nos livramos do uso direto do despachante nos componentes e, em vez disso, chamamos o método 
PerformInMainThread na classe base.
Primeiro no 
AgentWrapperComponent private void OnPointToGroundGot(PointOnGroundEventData eventData) {  
e no componente 
CharacterWrapperComponent private void OnAgentMove(AgentMoveEventData eventData) { PerformInMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); } 
Só isso. Resta executar o jogo e garantir que nada tenha quebrado durante a refatoração e que tudo funcione corretamente.