Tautan ke proyekDalam artikel ini, saya ingin menunjukkan bagaimana Anda dapat menggunakan 
SharedEvents untuk mengontrol karakter orang ketiga yang menawarkan serangkaian aset standar. Saya menulis tentang 
SharedEvents di artikel sebelumnya ( 
ini dan 
ini ).
Selamat datang di kucing!
Hal pertama yang Anda butuhkan adalah mengambil proyek dengan SharedState / SharedEvents yang diimplementasikan dan menambahkan seperangkat aset standar

Saya membuat adegan kecil dan sangat sederhana dari membuat prototipe cetakan

Dan memanggang navigasi permukaan dengan pengaturan standar

Setelah itu, Anda perlu menambahkan prefab 
ThirdPersonCharacter ke adegan ini

Kemudian Anda dapat memulai dan memastikan bahwa semuanya berjalan di luar kotak. Kemudian Anda dapat melanjutkan untuk mengonfigurasi penggunaan 
infrastruktur SharedState / SharedEvents yang dibuat sebelumnya. Untuk melakukan ini, hapus komponen 
ThirdPersonUserController dari objek karakter.

karena kontrol manual menggunakan keyboard tidak diperlukan. Karakter akan dikontrol oleh agen, menunjukkan posisi di mana ia akan bergerak.
Dan untuk memungkinkan ini, Anda perlu menambahkan dan mengkonfigurasi komponen 
NavMeshAgent ke objek karakter

Sekarang Anda perlu membuat pengontrol sederhana yang akan mengontrol karakter
dengan 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() {  
Dan tambahkan ke objek karakter, berikan tautan ke kamera, pengontrol dan agen karakter. Semua tersedia dari panggung.

Dan itu saja. Ini cukup untuk mengontrol karakter dengan memberi tahu agen tempat bergerak, menggunakan mouse (klik kiri).
Anda dapat memulai dan memastikan semuanya bekerja

Integrasi SharedEvents
Sekarang setelah adegan dasar siap, Anda dapat melanjutkan untuk mengintegrasikan kontrol karakter melalui 
SharedEvents . Untuk melakukan ini, Anda perlu membuat beberapa komponen. Yang pertama adalah komponen yang akan bertanggung jawab untuk menerima sinyal dari mouse dan memberitahukan semua komponen yang melacak posisi klik mouse di tempat kejadian, mereka hanya akan tertarik pada koordinat klik.
Komponen akan dipanggil, misalnya, 
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() {  
Komponen ini membutuhkan kelas untuk mengirim data dalam pemberitahuan. Untuk kelas yang hanya akan berisi data untuk pemberitahuan, Anda dapat membuat satu file dan 
beri nama 
DefinedEventsData
Dan tambahkan satu kelas untuk itu, untuk mengirim posisi klik dengan mouse
 using UnityEngine; public class PointOnGroundEventData : EventData { public Vector3 Point { get; set; } } 
Hal selanjutnya yang harus dilakukan adalah menambahkan komponen yang akan menjadi pembungkus atau dekorator, sesuka Anda, untuk komponen 
NavMeshAgent . Karena saya tidak akan mengubah komponen (pihak ketiga) yang ada, saya akan menggunakan dekorator untuk berintegrasi dengan 
SharedState / SharedEvents .

Komponen ini akan menerima pemberitahuan tentang klik mouse pada titik-titik tertentu di tempat kejadian dan memberi tahu agen tempat untuk pindah. Dan juga memantau posisi posisi agen di setiap frame dan membuat pemberitahuan tentang perubahannya.
Komponen ini akan tergantung pada komponen 
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() {  
Untuk mengirim data, komponen ini membutuhkan kelas yang perlu ditambahkan ke file 
DefinedEventsData. public class AgentMoveEventData : EventData { public Vector3 DesiredVelocity { get; set; } } 
Ini sudah cukup bagi karakter untuk bergerak. Tapi dia akan melakukannya tanpa animasi, karena kita belum menggunakan 
ThirdPersonCharater . Dan untuk itu, seperti untuk 
NavMeshAgent, Anda perlu membuat dekorator CharacterWrapperComponent

Komponen akan mendengarkan pemberitahuan tentang perubahan posisi agen, dan memindahkan karakter ke arah yang diterima dari pemberitahuan (acara).
 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); } } 
Dan itu saja. Tetap menambahkan komponen-komponen ini ke objek permainan karakter. Anda perlu membuat salinan dari yang sudah ada, menghapus komponen 
AgentMouseControl yang lama

Dan tambahkan 
MouseHandlerComponent , 
AgentWrapperComponent dan 
CharacterWrapperComponent baru .
Di 
MouseHandlerComponent Anda perlu mentransfer kamera dari tempat dari mana posisi klik akan dihitung.


Anda dapat memulai dan memastikan semuanya bekerja.
Itu terjadi dengan bantuan 
SharedEvents untuk mengontrol karakter tanpa memiliki koneksi langsung antara komponen, seperti pada contoh pertama. Ini akan memungkinkan konfigurasi komponen komposisi berbeda yang lebih fleksibel dan menyesuaikan interaksi di antara mereka.
Perilaku Asynchronous untuk SharedEvents
Cara mekanisme pemberitahuan sekarang diterapkan didasarkan pada transmisi sinkron sinyal dan prosesnya. Artinya, semakin banyak pendengar, semakin lama prosesnya. Untuk menghindari hal ini, Anda perlu menerapkan pemrosesan notifikasi sinkron. Hal pertama yang harus dilakukan adalah menambahkan versi asinkron dari metode 
Publikasikan 
Sekarang kita perlu mengubah metode 
OnUpdate abstrak di kelas dasar 
SharedStateComponent menjadi asinkron sehingga mengembalikan tugas yang dimulai di dalam implementasi metode ini dan 
menamainya menjadi 
OnUpdateAsync protected abstract Task[] OnUpdateAsync(); 
Anda juga akan membutuhkan mekanisme yang akan mengontrol penyelesaian tugas dari frame sebelumnya, sebelum saat ini
 private Task[] _previosFrameTasks = null;  
Metode 
Pembaruan di kelas dasar perlu ditandai sebagai 
async dan pra-periksa pelaksanaan tugas sebelumnya
 async void Update() { await CompletePreviousTasks();  
Setelah perubahan ini di kelas dasar, Anda dapat melanjutkan untuk mengubah implementasi metode 
OnUpdate lama ke 
OnUpdateAsync baru. Komponen pertama di mana ini akan dilakukan adalah 
AgentWrapperComponent . Sekarang metode ini mengharapkan kembalinya hasil. Hasil ini akan menjadi array tugas. Array karena dalam metode ini beberapa dapat diluncurkan secara paralel dan kami akan memprosesnya secara berkelompok.
 protected override Task[] OnUpdateAsync() {  
Kandidat berikutnya untuk perubahan metode 
OnUpdate adalah 
MouseHandlerController . Di sini prinsipnya sama
  protected override Task[] OnUpdateAsync() {  
Di semua implementasi lain di mana metode ini kosong, cukup untuk menggantinya
 protected override Task[] OnUpdateAsync() { return null; } 
Itu saja. Sekarang Anda dapat memulai, dan jika komponen yang memproses notifikasi secara serempak tidak mengakses komponen-komponen yang seharusnya diproses di utas utama, seperti Transform, misalnya, semuanya akan berfungsi. Jika tidak, kami akan mendapatkan kesalahan di konsol yang memberitahukan bahwa kami mengakses komponen-komponen ini bukan dari utas utama

Untuk mengatasi masalah ini, Anda perlu membuat komponen yang akan memproses kode di utas utama. Buat folder terpisah untuk skrip dan sebut Sistem, dan juga tambahkan skrip 
Dispatcher ke skrip tersebut.

Komponen ini akan menjadi singleton dan memiliki satu metode abstrak publik yang akan mengeksekusi kode di utas utama. Prinsip operator sangat sederhana. Kami akan menyampaikan kepadanya delegasi yang akan dieksekusi di utas utama, ia akan menempatkan mereka dalam antrian. Dan di setiap frame, jika ada sesuatu dalam antrian, jalankan di utas utama. Komponen ini akan menambahkan dirinya ke adegan dalam satu salinan, saya suka pendekatan yang sederhana dan efektif.
 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();  
Hal selanjutnya yang harus dilakukan adalah menerapkan dispatcher. Ada 2 tempat untuk melakukan ini. Pertama adalah dekorator karakter, di sana kami menanyakan arahnya. Dalam komponen 
CharacterWrapperComponent private void OnAgentMove(AgentMoveEventData eventData) { Dispatcher.RunOnMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); } 
2 adalah dekorator agen, di sana kami menunjukkan posisi untuk agen. Dalam komponen 
AgentWrapperComponent private void OnPointToGroundGot(PointOnGroundEventData eventData) {  
Sekarang tidak akan ada kesalahan, kode akan berfungsi dengan benar. Anda dapat memulai dan melihat ini.
Sedikit refactoring
Setelah semuanya siap dan semuanya berfungsi, Anda dapat menyisir kode sedikit dan membuatnya sedikit lebih nyaman dan sederhana. Ini akan membutuhkan beberapa perubahan.
Agar tidak membuat array tugas dan memasukkan satu-satunya tugas secara manual, Anda dapat membuat metode ekstensi. Untuk semua metode ekstensi, Anda dapat menggunakan file yang sama untuk transmisi ke notifikasi serta untuk semua kelas. Ini akan terletak di folder 
System dan disebut 
Extensions
Di dalam, kami akan membuat metode ekstensi generik sederhana yang akan membungkus instance apa pun dalam array
 public static class Extensions {  
Perubahan selanjutnya adalah menyembunyikan penggunaan langsung dari operator dalam komponen. Sebagai gantinya, buat metode di kelas dasar 
SharedStateComponent dan gunakan dispatcher dari sana.
 protected void PerformInMainThread(Action action) { Dispatcher.RunOnMainThread(action); } 
Dan sekarang Anda perlu menerapkan perubahan ini di beberapa tempat. Pertama, ubah metode di mana kami secara manual membuat array tugas dan memasukkannya ke dalam satu instance
Dalam komponen 
AgentWrapperComponent protected override Task[] OnUpdateAsync() {  
Dan di komponen 
MouseHandlerComponent protected override Task[] OnUpdateAsync() {  
Sekarang kita menyingkirkan penggunaan langsung dari dispatcher di komponen dan sebaliknya kita memanggil metode 
PerformInMainThread di kelas dasar.
Pertama di 
AgentWrapperComponent private void OnPointToGroundGot(PointOnGroundEventData eventData) {  
dan di komponen 
CharacterWrapperComponent private void OnAgentMove(AgentMoveEventData eventData) { PerformInMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); } 
Itu saja. Tetap menjalankan permainan dan memastikan tidak ada yang rusak selama refactoring dan semuanya berfungsi dengan benar.