在Unity中组织事件系统-通过游戏设计师的眼光

大家好!

我为业余爱好事先表示歉意,但是我读了一篇关于一个人如何尝试在Unity中处理过多实体连通性的文章,并认为谈论我的自行车会很有趣,我将它作为游戏设计师组合起来创建了游戏原型。

我的任务是创建一个各种实体的事件和消息的系统,避免当每个对象具有指向其他对象的大量链接时的非常连贯性。

结果,我的系统完全不允许我建立此类链接。 它解决了一个主要问题:它对我来说很方便使用,它不会在代码中造成不必要的垃圾,并且看起来不像对GetComponent()的常量调用那样糟糕。

我对任何关于为什么不需要这样做以及如何做到这一点的批评都感到高兴。

首先,我重新定义了Unity事件的标准功能,以传递两个GameObject作为参数:主题和事件对象:

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

我将事件类型存储在具有所有常量的静态类中:

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

这些事件的处理程序类很简单。
 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); } } } 


然后,我创建了事件组件,该组件附加到游戏中的每个对象上。
在其中,我为游戏中所有类型的事件创建事件处理程序对。

 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 }, }; } } 

因此,该文件很繁琐,但对我来说方便,它是一个文件-一次可用于所有对象。

我添加了字典中所有事件的监听器,所以所有游戏对象都监听所有游戏事件,这不是最佳选择,但是,当我即时更改某些实体的行为时,它便于原型制作:

  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); } 

现在,我需要了解此事件实例连接到的对象。

为此,我从gameObject寻找到组件的链接:例如,如果我们的对象是Character,则相应的字段将变为!= Null:

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

这是一项昂贵的操作,但我在Awake()中仅执行一次。

现在剩下描述所有类型事件的处理程序了:

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

结果是大量的方法,每种事件都使用一种方法,在每种方法内部,在组件内部调用相应的处理程序,具体取决于此实例所附加的事件类型。

因此,在“角色”或“怪物”组件中,我已经在写这样的东西:

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

同时,我不需要维护对象之间的任何交叉引用,我将所有新事件及其“主要”处理程序都放在一个位置,最终对象将随事件一起接收他们需要的所有信息。

到目前为止,我还没有遇到任何明显的性能问题:该系统“无法感知”地处理100多种类型的事件和屏幕上的数十个对象,甚至处理时间敏感的事件,例如因与箭头的碰撞而损坏字符。

Source: https://habr.com/ru/post/zh-CN445358/


All Articles