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.