第六章 在U3D中使用委托


先說說觀察者模式

簡單來說,就是存在一對多模式的時候。如果主題(subject)的內容發生改變,那么應當通知訂閱了本主題(subject)的觀察者(Observer)收到更新的通知,簡單來說就是這么回事。

具體的設計模式嘛,再談咯。

 

首先確定一件事情,U3D里的SendMessage與BroadcastMessage是有嚴重的缺陷的

  首先是性能損耗。這個系統是非常依賴反射機制的。過度依賴反射機制非常影響性能。

  當然這還不是最嚴重的問題。最嚴重的問題是,它的識別是依靠函數名字符串來識別的。如果重構中不小心重構了用於消息的方法名字,那就尷尬了。

  還有就是因為使用了反射機制,這套消息系統對於似有方法也是可以調用的。但是這一個在類內部沒有被調用過的私有方法誰會想到有人用反射調用過呢!如果刪掉了,尷尬同上。

 

首先介紹委托delegate,其實比較類似於C++里的函數指針。

委托是支持重載的。就是說在給委托賦值的時候,如果有同名重載函數,委托會根據自己的類型選擇合適的函數。

委托的回調中,判斷委托為不為空是非常關鍵和必要的

委托的調用,其實是invoke的過程

  比如md(value);(md為委托)其實相當於md.Invoke(value);

委托的內部實現暫時先不關注了,有興趣可以看看書

 

事件

一個定義了時間成員的類型需要提供以下功能來實現這種交互的機制:

①方法能夠訂閱它對事件的關注

②方法能夠取消它對事件的關注

③事件發生時,訂閱了該事件的方法會收到通知

訂閱者訂閱事情的本質就是把委托類型的實例添加到委托列表中。

 

給個我自己寫的例子吧

public enum NumType
{
    _hp,_mp,_exp
}

public class BaseUnit {
    private float _hp;
    private float _mp;
    private float _exp;
    public string _name;

    public float hp
    {
        get
        {
            return _hp;
        }
    }

    public float mp
    {
        get
        {
            return _mp;
        }
    }
    
    public float exp
    {
        get
        {
            return _exp;
        }
    }

    public BaseUnit(string name)
    {
        _name = name;
        _hp = 100;
        _mp = 100;
        _exp = 0;
    }

    public delegate void changeNum(BaseUnit source, float amount, NumType numtype);
    public event changeNum OnNumChange;

    public void ChangeNum(float amount,NumType numtype)
    {
        switch (numtype)
        {
            case NumType._hp:
                _hp += amount;
                break;
            case NumType._mp:
                _mp += amount;
                break;
            case NumType._exp:
                _exp += amount;
                break;
        }
        if (OnNumChange != null)
        {
            OnNumChange(this, amount, numtype);
        }
    }

}

 

 這個算是對於原版的簡化,這個是主題(subject)部分

接下來是觀察者部分

public class BattleInforamtion : MonoBehaviour {
    BaseUnit hero;
    // Use this for initialization
    void Start () {
        hero = new BaseUnit("hiro");
        hero.OnNumChange += Hero_OnNumChange;
    }

    private void Hero_OnNumChange(BaseUnit source, float amount, NumType numtype)
    {
        print(source._name + "" + numtype.ToString() + "   " + "增加了:" + amount);
    }

    // Update is called once per frame
    void Update () {
        
    }

    void OnGUI()
    {
        if(GUI.Button(new Rect(0, 0, 100, 30), "加血量"))
        {
            hero.ChangeNum(100, NumType._hp);
        }
        if (GUI.Button(new Rect(0, 30, 100, 30), "加魔量"))
        {
            hero.ChangeNum(100, NumType._mp);
        }
        if (GUI.Button(new Rect(0, 60, 100, 30), "加經驗"))
        {
            hero.ChangeNum(100, NumType._exp);
        }
    }
    
}

 

 嘛,大概就是這個樣子,形式其實是蠻簡單的。

其實我很想知道怎么用枚舉的名字直接來調用同名的字段啊!超想知道!!!!!!!!

 

 

委托的簡化語法

匿名方法有點類似於js里面那一套function 機制,就是作為常量的函數。

不用聲明返回類型,自己return類型然后判斷。

 

Action<T>是返回void的泛型委托,Func<T>將類型參數的最后一個作為返回類型的泛型委托(注意要引入system命名空間)

Predicate<T>為返回bool類型的,Comparison<T>接受兩個T類型的參數返回一個int值

 

comparison結合sort非常好用,二者結合可以用來做一些有趣的排序的效果出來

舉個例子

public class Adelegate : MonoBehaviour
{

    List<BaseUnit> _listunits;
    Comparison<BaseUnit> baseunitcompa;
    // Use this for initialization
    void Start()
    {
        
        _listunits = new List<BaseUnit>();
        for (int i = 0; i <= 5; i++)
        {
            _listunits.Add(new BaseUnit(true));
        }
    }

    // Update is called once per frame
    void Update()
    {

    }

    void OnGUI()
    {
        GUILayout.BeginArea(new Rect(0, 0, Screen.width, Screen.height));
        if (_listunits != null)
        {
            foreach (BaseUnit baseunit in _listunits)
            {
                string content;
                content = "Name:" + baseunit._name + "\n" + "HP:" + baseunit.hp + "\n" + "MP:" + baseunit.mp + "\n" + "EXP:" + baseunit.exp;
                GUILayout.Box(content);
            }
            if (GUILayout.Button("按hp排序"))
            {
                SortListByNumType(BaseUnit.NumType._hp, _listunits);
            }
            if (GUILayout.Button("按mp排序"))
            {
                SortListByNumType(BaseUnit.NumType._mp, _listunits);
            }
            if (GUILayout.Button("按exp排序"))
            {
                SortListByNumType(BaseUnit.NumType._exp, _listunits);
            }
        }
        GUILayout.EndArea();
    }

    void SortListByNumType(BaseUnit.NumType numtype,List<BaseUnit> _list)
    {
        switch (numtype)
        {
            case BaseUnit.NumType._hp:
                baseunitcompa = delegate (BaseUnit b1, BaseUnit b2) { return b1.hp.CompareTo(b2.hp); };
                break;
            case BaseUnit.NumType._mp:
                baseunitcompa = delegate (BaseUnit b1, BaseUnit b2) { return b1.mp.CompareTo(b2.mp); };

                break;
            case BaseUnit.NumType._exp:
                baseunitcompa = delegate (BaseUnit b1, BaseUnit b2) { return b1.exp.CompareTo(b2.exp); };

                break;

        }
        _list.Sort(baseunitcompa);
    }
}

 

 嘛,算是用了書上的好辦法了吧。

 

閉包,這是使用匿名方法時另一個需要注意的點。

外部變量指的是,定義了匿名方法的作用域內,匿名方法之外的局部變量或者參數

捕獲的外部變量:在匿名方法中使用了的外部變量

這個閉包特性,使得本來的變量的生命周期超過了他本來應該有的生命周期。

並且因此,所有的局部變量並非全都被分配在棧上,也有可能分配在托管堆上

 舉一個閉包的例子吧:

 

   void Start()
    {     

        Action<int> printint = testbibao();
        printint(10);
        printint(100);
        printint(1000);
        printint(10000);

    }

    private Action<int> testbibao()
    {
        int n = 0;
        Action<int> testint = num => {
            n += num;
            print(n);
        };
        testint(n);
        return testint;
    }

 

對應的輸出也非常有趣

輸出依次是0,10,110,1110,11110

 

最后說一下Lambda表達式,其實就是匿名方法的一種簡寫

 

書上規則總結得很好,這里就按照它的總結來說一下吧:

情景 例子
如果委托實例不需要獲取任何參數,那么可以直接使用() Func<string>Name=()=>"U3D";
如果委托實例需要獲取一個或者一個以上的參數,可以顯示指定參數類型,也可以不指定

 Comparsion<int>compare = ( b1,  b2)=>  b1.exp.CompareTo(b2.exp); 

Comparsion<int>compare = (int b1, int b2)=>  b1.exp.CompareTo(b2.exp); 

如果委托實例獲取一個參數,則可以省略括號     Func<int,string>name=n=>n.ToString();
如果委托實例有ref/out類型,必須指定其類型與參數

delegate void DelTest(out int number);

DelTest deltest=(out int number)=>number=0;

表達式的語句有兩句以及以上時,必須要和匿名方法一個寫法了,return不能省略,大括號得加


免責聲明!

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



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