Link zum ProjektIn diesem Artikel möchte ich zeigen, wie Sie mit 
SharedEvents einen Charakter einer dritten Person steuern können, der einen Standardsatz von Assets anbietet. Ich 
habe in früheren Artikeln ( 
dies und 
das ) über 
SharedEvents geschrieben .
Willkommen bei Katze!
Als erstes müssen Sie ein Projekt mit implementierten SharedState / SharedEvents erstellen und einen Standardsatz von Assets hinzufügen

Ich habe eine kleine und sehr einfache Szene aus Prototypen von Fertighäusern erstellt

Und Backflächennavigation mit Standardeinstellungen

Danach müssen Sie dieser Szene das vorgefertigte 
ThirdPersonCharacter hinzufügen

Dann können Sie beginnen und sicherstellen, dass alles sofort funktioniert. Anschließend können Sie die Verwendung der zuvor erstellten 
SharedState / SharedEvents-Infrastruktur konfigurieren . Entfernen Sie dazu die 
ThirdPersonUserController- Komponente aus dem 
Zeichenobjekt .

da eine manuelle Steuerung über die Tastatur nicht erforderlich ist. Der Charakter wird von Agenten gesteuert und gibt die Position an, an der er sich bewegen wird.
Um dies zu ermöglichen, müssen Sie die 
NavMeshAgent- Komponente zum 
Zeichenobjekt hinzufügen und konfigurieren

Jetzt müssen Sie einen einfachen Controller erstellen, der den Charakter steuert
mit der Maus 
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() {  
Fügen Sie es dem Objekt des Charakters hinzu und verknüpfen Sie es mit der Kamera, dem Controller des Charakters und dem Agenten. Es ist alles von der Bühne erhältlich.

Und alle. Dies reicht aus, um den Charakter zu steuern, indem Sie dem Agenten mit der Maus mitteilen, wohin er sich bewegen soll (Linksklick).
Sie können beginnen und sicherstellen, dass alles funktioniert

SharedEvents-Integration
Nachdem die Basisszene fertig ist, können Sie die Zeichensteuerung über 
SharedEvents integrieren . Dazu müssen Sie mehrere Komponenten erstellen. Die erste davon ist die Komponente, die für den Empfang des Signals von der Maus und die Benachrichtigung aller Komponenten verantwortlich ist, die die Position des Mausklicks auf die Szene verfolgen. Sie sind nur an den Koordinaten des Klicks interessiert.
Die Komponente wird beispielsweise 
MouseHandlerComponent genannt
 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() {  
Diese Komponente benötigt eine Klasse, um Daten in Benachrichtigungen zu senden. Für solche Klassen, die nur Daten für Benachrichtigungen enthalten, können Sie eine Datei erstellen und sie 
DefinedEventsData nennen
Fügen Sie eine Klasse hinzu, um die Position eines Klicks mit der Maus zu senden
 using UnityEngine; public class PointOnGroundEventData : EventData { public Vector3 Point { get; set; } } 
Als Nächstes fügen Sie eine Komponente hinzu, die nach Belieben ein Wrapper oder Dekorator für die 
NavMeshAgent- Komponente ist. Da ich die vorhandenen Komponenten (Drittanbieter) nicht ändern werde, verwende ich Dekoratoren für die Integration in 
SharedState / SharedEvents .

Diese Komponente erhält Benachrichtigungen über Mausklicks an bestimmten Stellen in der Szene und teilt dem Agenten mit, wohin er sich bewegen soll. Überwachen Sie außerdem die Position der Agentenposition in jedem Frame und erstellen Sie eine Benachrichtigung über deren Änderung.
Diese Komponente hängt von der 
NavMeshAgent- Komponente ab 
. 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() {  
Zum Senden von Daten benötigt diese Komponente eine Klasse, die der 
DefinedEventsData- Datei hinzugefügt werden 
muss. public class AgentMoveEventData : EventData { public Vector3 DesiredVelocity { get; set; } } 
Dies reicht bereits aus, damit sich der Charakter bewegen kann. Aber er wird es ohne Animation tun, da wir 
ThirdPersonCharater noch nicht verwenden. Und dafür müssen Sie genau wie für 
NavMeshAgent einen CharacterWrapperComponent-Dekorator erstellen

Die Komponente hört Benachrichtigungen über die Positionsänderung des Agenten ab und bewegt das Zeichen in die Richtung, die von der Benachrichtigung (Ereignis) empfangen wurde.
 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); } } 
Und das ist alles. Diese Komponenten müssen noch dem Spielobjekt des Charakters hinzugefügt werden. Sie müssen eine Kopie von der vorhandenen erstellen und die alte 
AgentMouseControl- Komponente entfernen
 Fügen
Fügen Sie neue 
MouseHandlerComponent , 
AgentWrapperComponent und 
CharacterWrapperComponent hinzu .
In 
MouseHandlerComponent müssen Sie die Kamera von der Szene übertragen, aus der die 
Klickposition berechnet wird.


Sie können beginnen und sicherstellen, dass alles funktioniert.
So geschah es mit Hilfe von 
SharedEvents , den Charakter zu steuern, ohne eine direkte Verbindung zwischen den Komponenten zu haben, wie im ersten Beispiel. Dies ermöglicht eine flexiblere Konfiguration verschiedener Komponentenzusammensetzungen und die Anpassung der Interaktion zwischen ihnen.
Asynchrones Verhalten für SharedEvents
Die Art und Weise, wie der Benachrichtigungsmechanismus jetzt implementiert wird, basiert auf der synchronen Übertragung des Signals und seiner Verarbeitung. Das heißt, je mehr Zuhörer vorhanden sind, desto länger dauert die Verarbeitung. Um dies zu vermeiden, müssen Sie die asynchrone Benachrichtigungsverarbeitung implementieren. Als erstes müssen Sie eine asynchrone Version der 
Publish- Methode hinzufügen
 
Jetzt müssen wir die abstrakte 
OnUpdate- Methode in der 
SharedStateComponent- Basisklasse in asynchron ändern, damit Aufgaben zurückgegeben werden, die innerhalb der Implementierung dieser Methode initiiert wurden, und in 
OnUpdateAsync umbenannt werden protected abstract Task[] OnUpdateAsync(); 
Sie benötigen außerdem einen Mechanismus, der die Ausführung von Aufgaben aus dem vorherigen Frame vor dem aktuellen Frame steuert
 private Task[] _previosFrameTasks = null;  
Die 
Update- Methode in der Basisklasse muss als 
asynchron markiert sein und die Ausführung vorheriger Aufgaben vorab überprüfen
 async void Update() { await CompletePreviousTasks();  
Nach diesen Änderungen in der Basisklasse können Sie die Implementierung der alten 
OnUpdate- Methode in die neue 
OnUpdateAsync ändern . Die erste Komponente, in der dies durchgeführt wird, ist 
AgentWrapperComponent . Diese Methode erwartet nun die Rückgabe des Ergebnisses. Dieses Ergebnis ist eine Reihe von Aufgaben. Ein Array, da in der Methode mehrere parallel gestartet werden können und wir sie in einem Bündel verarbeiten.
 protected override Task[] OnUpdateAsync() {  
Der nächste Kandidat für Änderungen an der 
OnUpdate- Methode ist 
MouseHandlerController . Hier ist das Prinzip dasselbe
  protected override Task[] OnUpdateAsync() {  
In allen anderen Implementierungen, in denen diese Methode leer war, reicht es aus, sie durch zu ersetzen
 protected override Task[] OnUpdateAsync() { return null; } 
Das ist alles. Jetzt können Sie beginnen. Wenn die Komponenten, die Benachrichtigungen asynchron verarbeiten, nicht auf die Komponenten zugreifen, die im Hauptthread verarbeitet werden sollen, z. B. Transformieren, funktioniert alles. Andernfalls erhalten wir Fehler in der Konsole, die darauf hinweisen, dass wir nicht über den Hauptthread auf diese Komponenten zugreifen

Um dieses Problem zu lösen, müssen Sie eine Komponente erstellen, die den Code im Hauptthread verarbeitet. Erstellen Sie einen separaten Ordner für Skripte, nennen Sie ihn System und fügen Sie das 
Dispatcher- Skript hinzu.

Diese Komponente ist ein Singleton und verfügt über eine öffentliche abstrakte Methode, die Code im Hauptthread ausführt. Das Prinzip des Dispatchers ist recht einfach. Wir werden ihm die Delegierten weitergeben, die im Haupt-Thread ausgeführt werden sollen, er wird sie in die Warteschlange stellen. Und wenn sich in jedem Frame etwas in der Warteschlange befindet, führen Sie es im Hauptthread aus. Diese Komponente wird sich in einer einzigen Kopie zur Szene hinzufügen. Ich mag einen so einfachen und effektiven Ansatz.
 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();  
Als nächstes wenden Sie den Dispatcher an. Es gibt 2 Orte, um dies zu tun. 1. ist der Dekorateur des Charakters, dort fragen wir ihn nach der Richtung. In der 
CharacterWrapperComponent- Komponente
 private void OnAgentMove(AgentMoveEventData eventData) { Dispatcher.RunOnMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); } 
2. ist der Dekorateur des Agenten, dort geben wir die Position für den Agenten an. In der 
AgentWrapperComponent- Komponente
 private void OnPointToGroundGot(PointOnGroundEventData eventData) {  
Jetzt gibt es keine Fehler mehr, der Code funktioniert korrekt. Sie können dies starten und sehen.
Ein bisschen Refactoring
Nachdem alles fertig ist und alles funktioniert, können Sie den Code ein wenig kämmen und ihn ein wenig bequemer und einfacher machen. Dies erfordert einige Änderungen.
Um kein Array von Aufgaben zu erstellen und die einzige manuell darin abzulegen, können Sie eine Erweiterungsmethode erstellen. Für alle Erweiterungsmethoden können Sie dieselbe Datei für die Übertragung an Benachrichtigungen sowie für alle Klassen verwenden. Es befindet sich im Ordner 
System und heißt 
Erweiterungen
Im Inneren erstellen wir eine einfache generische Erweiterungsmethode, die jede Instanz in ein Array einschließt
 public static class Extensions {  
Die nächste Änderung besteht darin, die direkte Verwendung des Dispatchers in Komponenten zu verbergen. Erstellen Sie stattdessen eine Methode in der 
SharedStateComponent- Basisklasse und verwenden Sie von dort aus den Dispatcher.
 protected void PerformInMainThread(Action action) { Dispatcher.RunOnMainThread(action); } 
Und jetzt müssen Sie diese Änderungen an mehreren Stellen anwenden. Ändern Sie zunächst die Methoden, mit denen wir manuell Arrays von Aufgaben erstellen, und fügen Sie ihnen eine einzelne Instanz hinzu
In der 
AgentWrapperComponent- Komponente
 protected override Task[] OnUpdateAsync() {  
Und in der Komponente 
MouseHandlerComponent protected override Task[] OnUpdateAsync() {  
Jetzt werden wir die direkte Verwendung des Dispatchers in den Komponenten los und rufen stattdessen die 
PerformInMainThread- Methode in der Basisklasse auf.
Zuerst in 
AgentWrapperComponent private void OnPointToGroundGot(PointOnGroundEventData eventData) {  
und in der 
CharacterWrapperComponent- Komponente
 private void OnAgentMove(AgentMoveEventData eventData) { PerformInMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); } 
Das ist alles. Es bleibt das Spiel zu starten und sicherzustellen, dass während des Refactorings nichts kaputt gegangen ist und alles richtig funktioniert.