Gagasan untuk menulis agregator acara diperpanjang Anda sendiri untuk Unity3d sudah lama ditunggu. Setelah membaca beberapa artikel tentang topik ini, saya menyadari bahwa tidak ada cukup "benar" (dalam Unity3d) dan agregator yang diperlukan bagi saya, semua solusi dilucuti dan tidak memiliki fungsi yang diperlukan.
Fungsi yang Diperlukan:
- Setiap kelas dapat berlangganan ke acara apa pun (seringkali agregator di unit membuat pelanggan Gameobject tertentu)
- Kemungkinan berlangganan dua kali dari kejadian spesifik ke acara tertentu harus dikecualikan (dalam alat standar Anda harus mengikuti ini sendiri)
- Seharusnya ada fungsionalitas baik manual berhenti berlangganan dan otomatis, dalam kasus menghapus instance / menonaktifkan monobekh (saya ingin berlangganan dan tidak mandi uap sehingga pelanggan tiba-tiba membuang kembali kuku)
- acara harus dapat mentransfer data / tautan dengan kompleksitas apa pun (saya ingin berlangganan dalam satu baris dan mendapatkan seluruh rangkaian data tanpa masalah)
Di mana menerapkannya
- Ini sangat ideal untuk UI, ketika ada kebutuhan untuk meneruskan data dari objek apa pun tanpa koneksi apa pun.
- Pesan tentang perubahan data, beberapa analog dari kode reaktif.
- Untuk injeksi ketergantungan
- Callback global
Poin lemah
- Karena memeriksa pelanggan yang mati dan mengambil (saya akan mengungkapkan nanti) kode lebih lambat daripada solusi serupa
- Kelas / struct digunakan sebagai inti dari acara tersebut, agar tidak mengalokasikan memori + masalah utama, tidak disarankan untuk acara spam dalam pembaruan)
Ideologi umum
Ideologi umumnya adalah bahwa bagi kami suatu peristiwa adalah paket data yang spesifik dan relevan. Katakanlah kita menekan tombol pada antarmuka / joystick. Dan kami ingin mengirim acara dengan tanda-tanda menekan tombol tertentu untuk diproses lebih lanjut. Hasil pemrosesan klik adalah perubahan visual ke antarmuka dan beberapa jenis tindakan dalam logika. Dengan demikian, mungkin ada pemrosesan / berlangganan di dua tempat berbeda.
Seperti apa paket acara / paket data dalam kasus saya:Contoh badan acarapublic struct ClickOnButtonEvent { public int ButtonID;
Seperti apa langganan acara: public static void AddListener<T>(object listener, Action<T> action)
Untuk berlangganan, kita perlu menentukan:
Objek yang merupakan pelanggan (biasanya kelas itu sendiri di mana berlangganan, tetapi ini tidak perlu, pelanggan dapat menentukan salah satu instance kelas dari bidang kelas.
Jenis / acara tempat kami berlangganan. Ini adalah esensi utama dari agregator ini, bagi kami jenis kelas tertentu adalah peristiwa yang kami dengarkan dan proses.
Berlangganan terbaik di Sedar dan Aktif;
Contoh public class Example : MonoBehaviour { private void Awake() { EventAggregator.AddListener<ClickOnButtonEvent>(this, ClickButtonListener); } private void ClickButtonListener(ClickOnButtonEvent obj) { Debug.Log(" " + obj.ButtonID); } }
Untuk memperjelas chip apa, pertimbangkan kasus yang lebih kompleks
Kami memiliki ikon karakter yang:- Ketahui karakter yang melekat pada mereka.
- Mencerminkan jumlah mana, hp, exp, dan status (setrum, kebutaan, ketakutan, kegilaan)
Dan di sini Anda dapat melakukan beberapa acaraUntuk mengubah indikator:
public struct CharacterStateChanges { public Character Character; public float Hp; public float Mp; public float Xp; }
Untuk mengubah status negatif:
public struct CharacterNegativeStatusEvent { public Character Character; public Statuses Statuses;
Mengapa dalam kedua kasus kita lulus kelas karakter? Berikut adalah pelanggan acara dan penangannya:
private void Awake() { EventAggregator.AddListener<CharacterNegativeStatusEvent> (this, CharacterNegativeStatusListener); } private void CharacterNegativeStatusListener(CharacterNegativeStatusEvent obj) { if (obj.Character != _character) return; _currentStatus = obj.Statuses; }
Ini adalah penanda dimana kami memproses acara dan memahami apa yang sebenarnya kami butuhkan.
Mengapa Anda tidak mengira Anda berlangganan langsung ke kelas Karakter? Dan spam mereka?Akan sulit untuk melakukan debut, lebih baik bagi sekelompok kelas / acara untuk membuat acara terpisah mereka sendiri.
Mengapa, sekali lagi, di dalam acara itu, jangan menaruh Karakter dan mengambil semuanya darinya?Jadi omong-omong itu mungkin, tetapi sering di kelas ada batasan visibilitas, dan data yang diperlukan untuk acara tersebut mungkin tidak terlihat dari luar.
apakah kelasnya terlalu berat untuk digunakan sebagai marker?Bahkan, dalam banyak kasus, penanda tidak diperlukan, sekelompok kelas yang diperbarui agak jarang. Biasanya satu entitas tertentu membutuhkan suatu peristiwa - model pengontrol / tampilan, yang biasanya menampilkan keadaan karakter pertama. Dan selalu ada solusi dangkal - ID dari berbagai jenis (dari inam, hingga hash kompleks, dll.).
Apa yang ada di balik tudung dan bagaimana cara kerjanya?
Kode agregator langsung namespace GlobalEventAggregator public delegate void EventHandler<T>(T e); { public class EventContainer<T> : IDebugable { private event EventHandler<T> _eventKeeper; private readonly Dictionary<WeakReference, EventHandler<T>> _activeListenersOfThisType = new Dictionary<WeakReference, EventHandler<T>>(); private const string Error = "null"; public bool HasDuplicates(object listener) { return _activeListenersOfThisType.Keys.Any(k => k.Target == listener); } public void AddToEvent(object listener, EventHandler<T> action) { var newAction = new WeakReference(listener); _activeListenersOfThisType.Add(newAction, action); _eventKeeper += _activeListenersOfThisType[newAction]; } public void RemoveFromEvent(object listener) { var currentEvent = _activeListenersOfThisType.Keys.FirstOrDefault(k => k.Target == listener); if (currentEvent != null) { _eventKeeper -= _activeListenersOfThisType[currentEvent]; _activeListenersOfThisType.Remove(currentEvent); } } public EventContainer(object listener, EventHandler<T> action) { _eventKeeper += action; _activeListenersOfThisType.Add(new WeakReference(listener), action); } public void Invoke(T t) { if (_activeListenersOfThisType.Keys.Any(k => k.Target.ToString() == Error)) { var failObjList = _activeListenersOfThisType.Keys.Where(k => k.Target.ToString() == Error).ToList(); foreach (var fail in failObjList) { _eventKeeper -= _activeListenersOfThisType[fail]; _activeListenersOfThisType.Remove(fail); } } if (_eventKeeper != null) _eventKeeper(t); return; } public string DebugInfo() { string info = string.Empty; foreach (var c in _activeListenersOfThisType.Keys) { info += c.Target.ToString() + "\n"; } return info; } } public static class EventAggregator { private static Dictionary<Type, object> GlobalListeners = new Dictionary<Type, object>(); static EventAggregator() { SceneManager.sceneUnloaded += ClearGlobalListeners; } private static void ClearGlobalListeners(Scene scene) { GlobalListeners.Clear(); } public static void AddListener<T>(object listener, Action<T> action) { var key = typeof(T); EventHandler<T> handler = new EventHandler<T>(action); if (GlobalListeners.ContainsKey(key)) { var lr = (EventContainer<T>)GlobalListeners[key]; if (lr.HasDuplicates(listener)) return; lr.AddToEvent(listener, handler); return; } GlobalListeners.Add(key, new EventContainer<T>(listener, handler)); } public static void Invoke<T>(T data) { var key = typeof(T); if (!GlobalListeners.ContainsKey(key)) return; var eventContainer = (EventContainer<T>)GlobalListeners[key]; eventContainer.Invoke(data); } public static void RemoveListener<T>(object listener) { var key = typeof(T); if (GlobalListeners.ContainsKey(key)) { var eventContainer = (EventContainer<T>)GlobalListeners[key]; eventContainer.RemoveFromEvent(listener); } } public static string DebugInfo() { string info = string.Empty; foreach (var listener in GlobalListeners) { info += " " + listener.Key.ToString() + "\n"; var t = (IDebugable)listener.Value; info += t.DebugInfo() + "\n"; } return info; } } public interface IDebugable { string DebugInfo(); } }
Mari kita mulai dengan yang utamaIni adalah kamus di mana kuncinya adalah ketik dan nilainya adalah wadah
public class EventContainer<T> : IDebugable
private static Dictionary<Type, object> GlobalListeners = new Dictionary<Type, object>();
Mengapa kita menyimpan wadah sebagai objek? Kamus tidak tahu cara menyimpan obat generik. Tetapi karena kuncinya, kita dapat dengan cepat membawa objek ke tipe yang kita butuhkan.
Apa isi wadah itu? private event EventHandler<T> _eventKeeper; private readonly Dictionary<WeakReference, EventHandler<T>> _activeListenersOfThisType = new Dictionary<WeakReference, EventHandler<T>>();
Ini berisi multidelegate generik dan koleksi di mana kuncinya adalah objek yang merupakan pelanggan, dan nilainya adalah metode penangan yang sama. Faktanya, kamus ini berisi semua objek dan metode yang termasuk dalam jenis ini. Akibatnya, kami menyebutnya multidelegate, dan ia memanggil semua pelanggan, ini adalah sistem acara "jujur" di mana tidak ada batasan pada pelanggan, tetapi di sebagian besar agregator lain, kumpulan kelas yang digeneralisasi baik oleh antarmuka khusus atau diwarisi dari kelas yang mengimplementasikan sistem tersebut di bawah tenda pesan.
Ketika multidelegate dipanggil, pemeriksaan dilakukan untuk melihat apakah ada kunci mati, koleksi dibersihkan dari mayat, dan kemudian multidelegate dengan pelanggan yang relevan diambil. Ini membutuhkan waktu, tetapi sekali lagi, pada kenyataannya, jika fungsionalitas acara dipisahkan, maka satu acara akan memiliki 3-5 pelanggan, sehingga pemeriksaannya tidak begitu menakutkan, manfaat kenyamanannya lebih jelas. Untuk cerita jaringan di mana bisa ada seribu atau lebih pelanggan, agregator ini lebih baik tidak menggunakan. Meskipun pertanyaannya tetap terbuka - jika Anda menghapus centang untuk mayat, yang lebih cepat - iterasi melalui array pelanggan dari 1k atau memanggil delegasi multi-dari pelanggan 1k.
Fitur penggunaan
Langganan paling baik didorong ke Sedarlah.
Jika sebuah objek aktif aktif / nonaktif, lebih baik berlangganan Sedarlah dan Hidup, itu tidak akan masuk dua kali, tetapi kemungkinan GameObject yang tidak aktif akan diambil untuk mati akan dikecualikan.
Faktur acara lebih baik daripada memulai, ketika semua pelanggan akan dibuat dan terdaftar.
Agregator membersihkan daftar pada saat bongkar adegan. Di beberapa agregator, disarankan untuk membersihkan pemuatan adegan - ini adalah file, acara pemuatan adegan muncul setelah Sedar / Aktif, pelanggan yang ditambahkan akan dihapus.
Agregator memiliki - string statis publik DebugInfo (), Anda dapat melihat kelas mana yang berlangganan acara mana.
Repositori GitHub