Organisasi sistem acara di Unity - melalui mata seorang desainer game

Halo semuanya!

Saya meminta maaf sebelumnya untuk amatirisme, tetapi saya membaca sebuah artikel tentang bagaimana seseorang mencoba untuk berurusan dengan konektivitas entitas yang berlebihan di Unity, dan saya pikir akan menarik untuk berbicara tentang sepeda saya, yang saya kumpulkan untuk membuat prototipe permainan sebagai perancang permainan.

Tugas saya adalah membuat sistem peristiwa dan pesan dari berbagai entitas, menghindari koherensi ketika setiap objek memiliki sejumlah besar tautan ke objek lain.

Akibatnya, sistem saya memungkinkan saya untuk tidak membuat tautan seperti itu sama sekali. Ini memecahkan masalah utama: nyaman bagi saya untuk bekerja dengannya, itu tidak membuang kode dengan sampah yang tidak perlu dan, tampaknya, tidak seburuk dalam kinerja seperti panggilan konstan ke GetComponent ().

Saya akan senang dengan kritik apa pun tentang topik mengapa ini tidak perlu, dan bagaimana melakukannya semua sama.

Untuk memulai, saya mendefinisikan kembali fungsi standar acara Unity untuk melewati dua GameObject sebagai parameter: subjek dan objek acara:

[System.Serializable] public class Event : UnityEvent<GameObject, GameObject> {} 

Saya menyimpan tipe acara di kelas statis dengan semua jenis konstanta:

 public enum EventTypes { TargetLock, TargetLost, TargetInRange, TargetOutOfRange, Attack, } 

Kelas penangan dari acara ini sepele.
 public class EventManager : MonoBehaviour { Dictionary<EventTypes, Event> events; static EventManager eventManager; public static EventManager Instance { get { if (!eventManager) { eventManager = FindObjectOfType(typeof(EventManager)) as EventManager; if (!eventManager) { print("no event manager"); } else { eventManager.Init(); } } return eventManager; } } void Init() { if (events == null) { events = new Dictionary<EventTypes, Event>(); } } public static void StartListening(EventTypes eventType, UnityAction<GameObject, GameObject> listener) { if (Instance.events.TryGetValue(eventType, out Event thisEvent)) { thisEvent.AddListener(listener); } else { thisEvent = new Event(); thisEvent.AddListener(listener); Instance.events.Add(eventType, thisEvent); } } public static void StopListening(EventTypes eventType, UnityAction<GameObject, GameObject> listener) { if (eventManager == null) return; if (Instance.events.TryGetValue(eventType, out Event thisEvent)) { thisEvent.RemoveListener(listener); } } public static void TriggerEvent(EventTypes eventType, GameObject obj1, GameObject obj2) { if (Instance.events.TryGetValue(eventType, out Event thisEvent)) { thisEvent.Invoke(obj1, obj2); } } } 


Lalu saya membuat komponen Acara, yang dilampirkan ke setiap objek dalam game.
Di dalamnya, saya membuat pasangan Event-Handler untuk semua jenis acara dalam game.

 public class Events : MonoBehaviour { Dictionary<EventTypes, UnityAction<GameObject, GameObject>> eventHandlers; void HandlersInit() { eventHandlers = new Dictionary<EventTypes, UnityAction<GameObject, GameObject>> { { EventTypes.TargetLock, TargetLock }, { EventTypes.TargetLost, TargetLost }, { EventTypes.TargetInRange, TargetInRange }, { EventTypes.TargetOutOfRange, TargetOutOfRange }, { EventTypes.Attack, Attack }, }; } } 

Akibatnya, file tersebut rumit, tetapi nyaman bagi saya bahwa itu adalah satu - untuk semua objek sekaligus.

Saya menambah dan mematikan pendengar untuk semua acara di kamus, jadi semua objek game mendengarkan semua acara game, yang tidak optimal, tetapi, sekali lagi, lebih mudah untuk membuat prototipe ketika saya mengubah perilaku entitas tertentu dengan cepat:

  void OnEnable() { foreach (KeyValuePair<EventTypes, UnityAction<GameObject, GameObject>> pair in eventHandlers) StartListening(pair.Key, pair.Value); } void OnDisable() { foreach (KeyValuePair<EventTypes, UnityAction<GameObject, GameObject>> pair in eventHandlers) StopListening(pair.Key, pair.Value); } 

Sekarang saya perlu memahami objek mana yang dilampirkan oleh instance Acara ini.

Untuk melakukan ini, saya mencari tautan ke komponen dari gameObject: misalnya, jika objek kita adalah Karakter, bidang yang sesuai akan menjadi! = Null:

  Monster _mob; Character _char; void ComponentsInit() { _mob = GetComponent<Monster>(); _char = GetComponent<Character>(); } 

Ini adalah operasi yang mahal, tetapi saya hanya melakukannya sekali di Sedarlah ().

Sekarang tinggal menjelaskan penangan untuk semua jenis acara:

  void TargetLock(GameObject g1, GameObject g2) { if (_char) _char.TargetLock(g1, g2); if (_mob) _mob.TargetLock(g1, g2); } 

Hasilnya adalah daftar besar metode, satu untuk setiap jenis acara, di dalamnya masing-masing penangan terkait disebut di dalam komponen, tergantung pada jenis acara kejadian ini dilampirkan.

Karenanya, di dalam komponen Character atau Monster, saya sudah menulis sesuatu seperti itu:

  public virtual void TargetLock(GameObject g1, GameObject g2) { if (g1 == gameObject) target = g2; if (g2 == gameObject) TargetedBy(g1); } 

Pada saat yang sama, saya tidak perlu mempertahankan referensi silang antara objek, saya menyimpan semua peristiwa baru dan penangan "primer" mereka di satu tempat, dan objek terakhir menerima semua informasi yang mereka butuhkan bersama dengan acara tersebut.

Sejauh ini, saya belum menemukan masalah kinerja yang nyata: sistem "tanpa terasa" bekerja dengan 100+ jenis peristiwa dan lusinan objek di layar, memproses bahkan peristiwa yang sensitif waktu seperti mengambil kerusakan dari tabrakan dengan panah ke karakter.

Source: https://habr.com/ru/post/id445358/


All Articles