脚本之间访问其实有更好的方式。
我们先分下脚本访问脚本的几种形式。
第一种,A GameObject 是 B GameObject 的 Parent,或者是中间隔着几个层级的 Parent。
那这种情况下,如果 A 脚本想调用 B 脚本的方法,直接通过 transform.Find(“XXX/YYY/ZZZ”).GetComponent<B>().DoSomething() 就可以了。
但是如果是 B 脚本想调用 A 脚本的方法,比较好的方式呢,是在 B 脚本中声明委托,然后在 A 中注册特定方法。当 B 想调用 A 脚本的方法的时候,通过委托通知就好。
除了使用委托,也可以使用消息机制,Unity 本身实现了一套消息机制,比如在 B 脚本中可以使用, this.SendMessageUpward(“MethedName”) 这样的方式。不过这种方式由于是使用字符串,并且可能用到了反射,所以网上大部分博客都不太推荐使用,但是也算是个不错的方式。
第二种情况呢是,A GameObject 和 B GameObject 是同级的,比如他们有共同的 Parent。这种情况下,笔者还是推荐用消息机制,不过不是 Unity 自带的消息机制,而是自己实现的消息机制。
第三种情况是,A GameObject 和 B GameObject 不在同一个 GameObject 树下。那么这种情况很可能就是跨模块通信了,这种情况下,还是推荐用消息机制。
所以,我们可以试试使用消息机制来解决我们的问题。
可是我们目前手里没有消息机制…
那就造一个吧。
消息机制要用到的知识:
List 或 LinkedList 或者自己实现的链表。
Dictionary
委托
关于第一条,我们选择 List 就好了,不过为了有更高的效率,我们最后会升级成链表。第三条,我们选择 Action,因为这是我们接触过的,以后也是用的比较多的。
而一般的消息机制会提供三个 API。
注册事件
注销事件
发送事件
我们先试着设计一下,假如我们想这样使用我们的 API
MsgDispatcher.Register("消息名",(obj)=>{ /* 处理消息 */ }); MsgDispatcher.Send("消息名","消息内容"); MsgDispatcher.UnRegister("消息名");首先事件名,是一个字符串类型的,而事件名要对应一个委托。我们声明一个静态的字典变量就好了。
private static Dictionary<string, Action<object>> RegisteredMsgs = new Dictionary<string, Action<object>>();为什么是静态的呢?因为,我们的消息机制呢不需要创建实例,而消息是要在整个项目内之间通信的,也就是全局的消息。全局的消息就需要放在唯一容器里注册。而这个容器就是我们的这个字典变量。
我们先实现注册事件功能。
public static void Register(string msgName, Action<object> onMsgReceived) { RegisteredMsgs.Add(msgName, onMsgReceived); }非常简单。
我们再实现注销功能。
public static void UnRegister(string msgName) { RegisteredMsgs.Remove(msgName); }也非常简单。
再实现发送功能。
public static void Send(string msgName, object data) { RegisteredMsgs[msgName](data); }非常简单。
第十二个示例代码如下:
using System; using System.Collections.Generic; using UnityEngine; namespace QFramework { public class MsgDispatcher { private static Dictionary<string, Action<object>> RegisteredMsgs = new Dictionary<string, Action<object>>(); public static void Register(string msgName, Action<object> onMsgReceived) { RegisteredMsgs.Add(msgName, onMsgReceived); } public static void UnRegister(string msgName) { RegisteredMsgs.Remove(msgName); } public static void Send(string msgName, object data) { RegisteredMsgs[msgName](data); } #if UNITY_EDITOR [UnityEditor.MenuItem("QFramework/12.简易消息机制", false, 13)] #endif private static void MenuClicked() { Register("消息1", data => { Debug.LogFormat("消息1:{0}", data); }); Send("消息1", "hello world"); UnRegister("消息1"); Send("消息1", "hello"); } } }菜单执行结果如下
哈哈哈,报错啦,不过我们发现,第一次消息发送成功了,但是第二次发送的时候报错了。是因为消息进行注销了,也就是字典里没有消息名了,这时候直接从字典里取值当然会报错。
这个问题我们留在下一篇解决,在下一篇,我们要讲解关于这个消息机制的完善。
第十二个示例还没有完成。
集成到 MonoBehaviourSimplify 里。还记得我们的简易消息机制是为了解决什么问题诞生的嘛?
是为了解决脚本间访问的问题。
我们回过头再看下 A 脚本如果想访问 B 脚本,使用消息机制,如何实现。
代码如下:
public class A : MonoBehaviour { void Update() { if(Input.GetMouseButtonDown(0)) { MsgDispatcher.Send("DO","ok"); } } } public class B : MonoBehaviour { void Awake() { MsgDispatcher.Register("DO",DoSomething); } void DoSomething(object data) { // do something } void OnDestroy() { MsgDispatcher.UnRegiter("DO",DoSomething); } }用法还是很简单的。