你是否知道C#有一个内置的事件系统?而且是一个非常好的事件系统!它与Unity3D配合起来非常有用.本篇文章提供一个例子.
为了响应由GameObject派发的事件通常你会创建一个继承自MonoBehaviour的脚本并实现你需要的方法.所以如果你想要对用户鼠标悬停在对象上做出反应你将会创建OnMouseOver方法.通常来讲其看起来如下:
void OnMouseOver()
{
renderer.material.Color = Color.red;
}
这样运行良好.但如果你想要别的对象得到有关该事件通知的话将会怎样?一种办法是在你的脚本中保存一个别的对象的索引并在你的方法中调用它:
public MyScript myScript;
void onMouseOver()
{
myScript.NotifyMouseOver();
}
这样也成,但是你老得保存一个你要通知对象的索引.另外,你只能通知这个对象,除非你保存更多别的对象的索引.这样很快就会搞得乱七八糟的.Message的另一个解决方案是使用
SendMessage和
SendMessageUpward方法.尽管看起来他们是搞这个的正确工具,这些方法有些大的缺陷而且在我看来你应该完全的避免使用它们.首先其语法很怪异并且你需要将方法名作为字符串字面值传递而这很容易造成错误!另外,这些方法只作用于对象的变换层级(transform hierarchy).你(只)能调用绑定到同一GameObject对象的脚本或者绑定到该GameObject的父级之一.幸运的是对于事件来说有一个更好的方法来解决,这就是C#内置的事件系统.我不会就事件系统如何工作进行解释,但如果你对更多了解该话题感兴趣可以看看
MSDN上的教程.现在,我们看下如何在Unity3D中使用事件.考虑该脚本:
using UnityEngine;
public class EventDispatcher : MonoBehaviour
{
public delegate void EventHandler(GameObject e);
public event EventHandler MouseOver;
void OnMouseOver()
{
if(MouseOver != null)
{
MouseOver(this.gameObject);
}
}
}
如果你不能确切地知道它做什么的话不要担心.重要的是一旦你将其附着到一个GameObject上你可以到别的脚本(你项目中的任何脚本!)并且如果你有一个指向那个对象的索引你可以写一些如下代码:
private GameObject s;
[...]
s.GetComponent().MouseOver += Listener;
[...]
void Listener(GameObject g)
{
// g is being hovered, do something…
}
这种方法比使用message灵活的多,因为其对于任意脚本都有效而不只是在同一变换层级的才有效.如果你保持一个控制游戏状态的单例对象,你能够侦听来自其内部GameObject的任意事件.当然不只是GameObject才能发送事件----任何对象都可以.这种设置的最后一个结果就是同样的侦听函数能够用于响应来自不同对象的事件.通过传递一个引用来作为参数你总能知道是谁派发了事件----这就是我在例子里所做的.在上面的第一种情况中,引用,控制器和MVC你需要在发送事件的对象中保持一个侦听器的索引,而且我说了这不是一个好办法.在使用内置事件的版本中其所需恰恰相反----你需要一个要侦听事件的对象里面发送事件的对象的索引( you need a reference to the object that sends the event in the object that listens to it).你可能会问这样做为什么好些?首先,在这里发送者不需要知道谁在侦听其发送的事件甚至不需要知道有多少个侦听器.它所做的就是发送事件并忘掉它.在上面讨论的第一种情况中,想象一下要告诉事件派发者停止通知侦听器该有多麻烦!使用事件系统,侦听器负责决定它侦听什么事件,什么时候开始以及结束侦听.这种对象通常管理着应用程序的状态或者执行一些游戏逻辑.借用MVC设计模式中的术语----它是控制器.这就是为什么让它负责这个很合理.那么使用事件产生出更稳固的代码.最后我喜欢重载的操作符+=来用于添加侦听器,它是如此简洁.你可能已经猜到了,当你完成了事件侦听你可以说:
s.GetComponent().MouseOver -= Listener;
当然你可以创建一个泛型的实现了GameObject所能派发的所有事件的EventDispatcher类----这里是一个已经实现了其中几个的版本:
using UnityEngine;
using System.Collections;
/**
* A simple event dispatcher - allows to listen to events in one GameObject from another GameObject
*
* Author: Bartek Drozdz (bartek [at] everyday3d [dot] com)
*
* Usage:
* Add this script to the object that is supposed to dispatch events.
* In another objects follow this pattern to register as listener at intercept events:
void Start () {
EventDispatcher ev = GameObject.Find("someObject").GetComponent<EventDispatcher>();
ev.MouseDown += ListeningFunction; // Register the listener (and experience the beauty of overloaded operators!)
}
void ListeningFunction (GameObject e) {
e.transform.Rotate(20, 0, 0); // 'e' is the game object that dispatched the event
e.GetComponent<EventDispatcher>().MouseDown -= ListeningFunction; // Remove the listener
}
* This class does not implement all standards events, nor does it allow dispatching custom events,
* but you shold have no problem adding all the other methods.
*/
public class EventDispatcher : MonoBehaviour
{
public delegate void EventHandler (GameObject e);
public delegate void CollisionHandler (GameObject e, Collision c);
public event EventHandler MouseOver;
void OnMouseOver ()
{
if (MouseOver != null)
MouseOver (this.gameObject);
}
public event EventHandler MouseDown;
void OnMouseDown ()
{
if (MouseDown != null)
MouseDown (this.gameObject);
}
public event EventHandler MouseEnter;
void OnMouseEnter ()
{
if (MouseEnter != null)
MouseEnter (this.gameObject);
}
public event EventHandler MouseExit;
void OnMouseExit ()
{
if (MouseExit != null)
MouseExit (this.gameObject);
}
public event EventHandler BecameVisible;
void OnBecameVisible ()
{
if (BecameVisible != null)
BecameVisible (this.gameObject);
}
public event EventHandler BecameInvisible;
void OnBecameInvisible ()
{
if (BecameInvisible != null)
BecameInvisible (this.gameObject);
}
public event CollisionHandler CollisionEnter;
void OnCollisionEnter (Collision c)
{
if (CollisionEnter != null)
CollisionEnter (this.gameObject, c);
}
public event CollisionHandler CollisionExit;
void OnCollisionExit (Collision c)
{
if (CollisionExit != null)
CollisionExit (this.gameObject, c);
}
}
通过查看代码,我确信你可以搞明白如何添加别的事件.然而,谨防使用那种方式实现OnGUI(如果你想知道为什么,读下
这篇文章).