【Unity3D技巧】在Unity中使用事件/委托機制(event/delegate)進行GameObject之間的通信


作者:王選易,出處:http://www.cnblogs.com/neverdie/ 歡迎轉載,也請保留這段聲明。如果你喜歡這篇文章,請點【推薦】。謝謝!

QQ20140529123319_thumb1

引子

在前面兩篇文章:

我們了解了2D中的Sprite,Animation,RigidBody和Collider,在繼續開發游戲的過程中,我們會遇到這樣的問題,如何處理GameObject之間的相互調用,比如說在FlappyBird中我們在小鳥撞倒管子的時候,要把這個消息通知給許多GameObject,管子接到這個消息之后需要停止運動,UI接到這個消息要彈出GameOver的字樣。接下來,我來講一下如何合理地解決這個問題。相關源碼參考:Flappy Bird的源碼

為什么要進行GameObject之間的通訊?

在游戲開發中我們經常遇到這樣的問題,在游戲中發生了一個事件(event),我們如何把這個時間通知給其他GameObject:比如游戲中發生了爆炸,我們需要在一定范圍內的GameObject都感知到這一事件。有的時候,我們希望攝像機以外的物體不要接到我們這一事件的通知。游戲中豐富多彩的世界正是由通信機制構成的。

有一種方法是在發生事件的GameObject的Start方法里面把對該事件感興趣的所有GameObject當作成員變量保存在腳本組件里,那么我們把發生事件的object當作Subject,把對該事件感興趣的object當作Observer。

將Observer作為成員變量存儲在Subject中有一下缺點:

  • 難以變更,一旦要新增一個Observer就需要更改Subject中的代碼
  • 如果Observer被銷毀了,無法從Subject中移除掉這個成員變量,會發生NullReferernceException。
  • 在發生事件時,一個個去invoke不同Observer中的相應handle方法的代碼變得冗長繁雜。

還好的是,我們可以通過引入觀察者模式來解決這個問題,更好的是,C#內置有一個非常棒的事件/委托機制,能讓我們非常方便地進行觀察者模式的構建。

如果你還不了解觀察者模式和事件/委托機制,可以參考以下幾篇文章:

 

C#中標准的委托類型

我們在構建事件/委托機制的時候,首先要定義委托類型,參考在Cocos2d-x中的CCCallback,我先定義了以下三種類型的委托:

// 該委托不傳任何參數
public delegate void CallFunc();
// 該委托會傳入發生事件的GameObject,即sender
public delegate void CallFuncO(GameObject sender);
// 該委托會傳入發生事件的GameObject,即sender。和一個變長參數列表
public delegate void CallFuncOP(GameObject sender, EventArgs args);

但是我發現C#本身已經提供了一種比較好的委托類型:EventHandler,所以我就把游戲中的委托都替換成了這種委托。

public delegate void EventHandler(object sender, EventArgs e);

另一種更好的委托方式是使用泛型參數的委托類型:EventHandler<TEventArgs>,其簽名如下:

public delegate void EventHandler<TEventArgs>(
    Object sender,
    TEventArgs e
)

采用 EventHandler 模式發布事件

如果這個事件不產生任何額外參數(即除了事件的發送者之外),則在在調用時,向EventHandler的第二個參數傳一個EventArgs.Empty即可。

如果產生額外參數,第二個參數是從 EventArgs 派生的類型並提供所有字段或屬性需要保存事件數據。使用 EventHandler<TEventArgs> 的優點在於,如果事件生成事件數據,則無需編寫自己的自定義委托代碼。

下面我們舉一個例子來證實EventHandler的用法:

 

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Counter c = new Counter(new Random().Next(10));
            //向該事件添加了一個委托函數
            c.ThresholdReached += c_ThresholdReached;

            Console.WriteLine("press 'a' key to increase total");
            while (Console.ReadKey(true).KeyChar == 'a')
            {
                Console.WriteLine("adding one");
                c.Add(1);
            }
        }

        static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e)
        {
            Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold,  e.TimeReached);
            Environment.Exit(0);
        }
    }

    class Counter
    {
        private int threshold;
        private int total;

        public Counter(int passedThreshold)
        {
            threshold = passedThreshold;
        }

        public void Add(int x)
        {
            total += x;
            if (total >= threshold)
            {
                ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
                args.Threshold = threshold;
                args.TimeReached = DateTime.Now;
                OnThresholdReached(args);
            }
        }

        protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)
        {
            EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
            if (handler != null)
            {
                handler(this, e);
            }
        }
        //添加了一個帶泛型參數的事件
        public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;
    }

    public class ThresholdReachedEventArgs : EventArgs
    {
        public int Threshold { get; set; }
        public DateTime TimeReached { get; set; }
    }
}

在游戲中的應用

我們通過一個小鳥撞倒管子來作為事例說明如何進行通信:

bird1

在這個情景下,我們首先為小鳥設定兩個事件(event),分別是分數加一(ScoreAdd)和小鳥碰到管子游戲結束(GameOver)  如下:

 

using UnityEngine;
using System.Collections;
using System;

public class BirdController : MonoBehaviour {

    public event EventHandler GameOver;
    public event EventHandler ScoreAdd;

    //當離開Empty Trigger的時候,分發ScoreAdd事件
    void OnTriggerExit2D(Collider2D col) {
        if (col.gameObject.name.Equals("empty")) {
            if (ScoreAdd != null)
                ScoreAdd(this, EventArgs.Empty);
        }
    }

    //當開始碰撞的時候,分發GameOver事件
    void OnCollisionEnter2D(Collision2D col)
    {
        rigidbody2D.velocity = new Vector2(0, 0);
        if (GameOver != null)
            GameOver(this, EventArgs.Empty);
        this.enabled = false;
    }

}

 

然后在對這個事件感興趣的GameObject會通過相應的Handler對該事件進行監聽,這樣就可以進行一對多的GameObject間的通信了。

 

using UnityEngine;
using System.Collections;
using System;


public class TubeController : MonoBehaviour {


    // Use this for initialization
    void Start () {
        GameObject.Find("bird").GetComponent<BirdController>().GameOver += OnGameOver;
    }

    void OnDestroy() {
        if ( GameObject.Find("bird") )
            GameObject.Find("bird").GetComponent<BirdController>().GameOver -= OnGameOver;
    }

    void OnGameOver(object sender, EventArgs e)
    {
        rigidbody2D.velocity = new Vector2(0, 0);
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM