简介
在游戏开发过程中,我们会大量使用事件系统。很多时候,比起直接调用对象组件的方法,使用事件触发将很大程度上降低系统的耦合度,从而实现更为优雅的系统设计。
封装一个好用的事件系统将对我们的开发起到很大的帮助。
本文将基于Unity提供的ScriptableObject和UnityEvent来封装一个我们自己的事件系统。随后,我们可以自定义事件,并在监听器监听到事件后执行对应的程序逻辑。
我们将基于一个实际的需求来更好地说明这个事件系统是如何进行工作的。假设我们现在正在开发关卡选择页面,这个页面上将出现数量不定的关卡按钮(关卡数量随着开发的进行需要不断增加),点击关卡按钮后,需要加载对应关卡的Scene。这个需求适合用事件系统来解决。我们将在按钮UI的组件上绑定"StartScene"的GameEvent,点击按钮后将触发这个事件,然后由MapSelectManager上的GameEventListener进行监听,并执行加载关卡的逻辑。
正文
GameEvent类
GameEvent类将继承ScriptableObject类,这样做可以让我们能够在Unity中为每个自定义事件创建一个数据对象。ScriptableObject是全局的,在跨场景的需求中尤其好用。
代码
[CreateAssetMenu(menuName = "Scriptable/GameEvent")]
public class GameEvent : ScriptableObject
{
[HideInInspector] public List<GameEventListener> listeners = new List<GameEventListener>();
// Raise event through different methods signatures
public void Raise(Component sender, object data)
{
for (int i = 0; i < listeners.Count; i++)
{
listeners[i].OnEventRaised(sender, data);
}
}
public void RegisterListener(GameEventListener listener)
{
if (!listeners.Contains(listener))
{
listeners.Add(listener);
}
}
public void UnregisterListener(GameEventListener listener)
{
if (listeners.Contains(listener))
{
listeners.Remove(listener);
}
}
}
GameEvent负责做3件事:触发事件、注册监听器、取消注册监听器。触发事件时,所有注册本事件的监听器都会调用OnEventRaised(sender, data)来响应此事件。我们可以通过data参数来传递所需要的数据,比如需要加载的关卡名称。
GameEventListener类
GameEventListener负责监听GameEvent并执行后续逻辑。
代码
public class GameEventListener : MonoBehaviour
{
public GameEvent gameEvent;
public CustomGameEvent response;
private void OnEnable()
{
gameEvent.RegisterListener(this);
}
private void OnDisable()
{
gameEvent.UnregisterListener(this);
}
public void OnEventRaised(Component sender, object data)
{
response.Invoke(sender, data);
}
}
GameEventListener被Enable时在需要监听的GameEvent上进行注册,被Disable时取消注册。GameEvent事件触发时,OnEventRaised(Component sender, object data)被调用,执行response.Invoke(sender, data)。response是外部传入的,一个CustomGameEvent对象。
CustomGameEvent
它是Unity提供的UnityEvent的一个封装,让我们能将参数传入UnityEvent。
代码
public class CustomGameEvent : UnityEvent<Component, object> { }
使用监听器并绑定对应执行逻辑
创建一个GameObject用于挂载GameEventListener。
将自定义创建的StartGameScene传入GameEvent变量。Response也可以传入外部脚本并调用脚本中的方法,需要注意的是调用的方法必须要能够接收Component和object参数。
加载场景代码
public class MapSelectionManager : MonoBehaviour
{
private IEnumerator OnStartGameCoroutine(string sceneName)
{
...
}
public void LoadScene(Component sender, object data)
{
if (data is string sceneName)
{
StartCoroutine(OnStartGameCoroutine(sceneName));
}
}
}
事件的触发
假设我们使用MapItemController来负责关卡按钮的逻辑,那么当按下按钮时,将调用StartGameScene()方法,随后触发onStartGameScene的Raise(Component sender, object data)方法,完成事件的触发。
public class MapItemController: MonoBehaviour
{
[SerializeField] private GameEvent onStartGameScene;
public void StartGameScene()
{
string sceneName = GetSceneName(); // 伪代码,获取需要加载的场景名称
onStartGameScene.Raise(this, sceneName);
}
onStartGameScene变量中传入的对象要与先前我们创建的GameEventListener中传入的对象保持完全一致。
至此,我们就实现了一个松耦合的开始关卡的需求。
总结
我们基于ScriptableObject和UnityEvent封装了一个自己的好用且便于理解的事件系统。文章来源:https://www.toymoban.com/news/detail-785433.html
基于事件可以松耦合地实现各类游戏开发需求。文章来源地址https://www.toymoban.com/news/detail-785433.html
到了这里,关于Unity基础 - 封装一个好用的事件系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!