分享

【新提醒】【委托、事件、观察者模式、事件驱动程序设计(二)】

 kiki的号 2017-04-05
本帖最后由 u75379946 于 2016-3-23 11:16 编辑

1.概述:
     事件,其实就是委托的一种应用,或者说实现形式。只有在特定的情况发生时某些功能才会被调用。同时,事件其实就是设计模式中的Observer模式在.Net中的一种实现方式,因为两者比较相似,在这里暂时不讲观察者模式,有兴趣的朋友可以看我公众微信或者Google。
2.为什么要引入事件机制
     在游戏世界中,当一个对象的变化会影响其他对象时,他们之间必然会产生关联,例如,角色死亡要通知程序暂停,炸弹爆炸周围事物要收到影响等等。这样我们想到的第一个方法就是把收到影响的对象保存在变化的对象中,当对象变化时一一通知这些观察对象,还有一个方法是Unity自带的SendMessage,虽然这样也可以解决对象间通信问题,但是这两种方法会产生如下问题:
A.将observer对象保存在subject中:
     a.如果增加一个observer就要改动subject,不符合开闭原则。
     b.如果减少一个observer,或者说Destyroy一个observer对象,无法在subject中也将其移除。
     c.事件发生时,需要逐个调用各Observer对象的事件处理程序,让代码十分冗长。
B.SendMessage:
     a.这个方法效率低,能少用就少用。
     b.语法怪异,并且以字符串形式传递方法名,容易产生错误。
因此,我们引入事件机制来避免上述的这些问题。
3.发布者与订阅者:
     当一个程序的事件发生时,程序其他部分可以得到相应的通知并响应该事件。这时,前者就变成了事件的发布者,而其他人则变成了订阅者:
     如下图所示:红色区域代表了实现一个事件机制所必备的五个成分(也是使用事件的五个步骤)


file:///C:/Users/Anchor/AppData/Local/Temp/enhtmlclip/Image(26).png
A.定义委托:事件和事件处理程序都必须遵循一个相同的签名和返回类型,这需要事先通过委托类型来描述。
B.声明事件:public event EventHandler Fire;//event 是事件关键字,事件是类或结构的成员,并隐式自动初始化为null。
C.触发事件的方法:事件成员只是保存了事件触发后要调用的方法而没有定义何时触发。
     if(Fire != null)//确认有订阅者
     Fire(this,args);//如果有则触发事件
D.事件处理程序:即回调函数,每个订阅者类都有各自的事件处理程序供事件触发后调用。
E.事件注册:事件声明好后我们需要有人去订阅它,这样才能在触发后通知订阅者,在这里用+=来订阅,-=来取消订阅。
   订阅的本质:为事件提供代码,当事件发生时调用这些代码。
注意:以上,细心的朋友发现事件注册和委托注册及其类似,那么事件和委托是什么关系呢?看下图:

file:///C:/Users/Anchor/AppData/Local/Temp/enhtmlclip/Image(27).png
     其实事件中包含了一个私有的委托,当事件触发时它依次调用委托中的方法来执行操作,而与委托不同,除了触发自身外,事件仅有的两个操作就是上图中左侧的+=和-=。
4.实例:
下面我们结合例子看看具体实现:
     我们有一个Subject小球,当小球从天上落到地下以后,另一个Observer小球会被震起,并打印震源小球的名字。(即:这里Subject中有一个落地事件,而Observer订阅这个事件以获得通知)
[C#] 纯文本查看 复制代码

public class SubjectBall : MonoBehaviour {
    public string ballName;
    //添加球落地事件
    public event EventHandler<BallEventArgs> BallDown;
    void Start()
    {
        ballName = "Little Ball";
    }
    //添加触发事件方法
    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Ground")
        {
            BallEventArgs ballArgs = new BallEventArgs();
            ballArgs.BallName = this.ballName;
            //如果有人订阅则触发事件
            if (BallDown != null)
                BallDown(this,ballArgs);
        }
    }

}

[C#] 纯文本查看 复制代码

public class ObserverBall : MonoBehaviour {
    public float force = 30;
    void Start()
    {
        //订阅事件
        GameObject.Find("SubjectBall").GetComponent<SubjectBall>().BallDown += Jump;
    }
    //事件处理程序
    public void Jump(object go,BallEventArgs e)
    {
        Debug.Log(e.BallName);
        gameObject.GetComponent<Rigidbody>().AddForce(Vector3.up.normalized * force,ForceMode.Impulse);
    }
}

[C#] 纯文本查看 复制代码

//自定义事件参数类传递数据,继承自EventArgs
public class BallEventArgs : EventArgs {

    public string BallName;
}


注:
1.EventArgs为系统自带的参数类型,但其实它并不能传递数据,如果你要传递数据需要声明一个派生自EventArgs的类。
2.EventHandler为C#中自带的,标准的用于事件处理的委托类型,当然你也可以自己定义。
3.EventHandler<BallEventArgs>为泛型委托。效果:


附工程:

本帖隐藏的内容

链接.txt (48 Bytes, 下载次数: 81)

有问题欢迎来我公众微信: 黑客画家 (独立游戏,影视,音乐,阅读,文学,设计,心理)











    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多