天天看点

[U3D Learning Note] Unity C# Survival Guide (17) --Delegates and Events

emmmm先附上一个讲得不错的链接:Difference Between Delegates and Events in C#

Delegates(代表/委托)

  • Delegate: a variable that holds a method or several methods.

来看一下一个例子。在这里我们先声明了一个delegate,这是装有一个输入类型为

Color

的参数并且返回为空的方法的数据类型。我们像声明一个变量或类一样声明一个delegate变量

onColorChange

。然后我们再来写两个方法一个是带有一个传入

Color

类型参数的方法

UpdateColor()

,另一个是啥都没有的

Task()

,我们可以发现我们可以将

UpdateColor

赋给

onColorChange

,但无法将

Task

赋给

onColorChange

,因为签名不匹配。同样的,传入参数数量不一致也不行,传回类型不一样也不行。我们就假设有个宗门吧,把Delegate比作宗门的掌门吧,掌门代表了整个宗门,掌门都会制定表示宗门的令牌也就是Signature,上面是代表标志啊花纹啊特殊能量印记啊(返回类型和输入参数),只有全部符合才能说“啊你是这个宗门的”,有一点点不一致就是伪造啦就不是这个宗门的(不用宗门功法举例是因为宗门功法是可以被外人偷学的(bushi 。然后调用(call/invoke)的话就把这个delegate变量当作方法一样调用就好了。

注意: delegate是作为函数指针来引用方法的

public class MainForDelegates : MonoBehaviour
    {
        // define a signature for delegate
        public delegate void ChangeColor(Color newColor);

        // use delegate
        public ChangeColor onColorChange; // we can assign onColorChange to any method that matches this delegate signature.

        private void Start()
        {
            onColorChange = UpdateColor;
            onColorChange(Color.blue); //Invoke
            //onColorChange = Task; //ERROR
        }

        public void UpdateColor(Color newColor){
			Debug.Log("change color to: " + newColor.ToString());
		}

        public void Task(){}
    }
           
  • Multicast(多播) delegates: stack on these task and invoke them all at once. 将拥有相同签名的方法添加给delegate变量,通过delegate进行一次性同时调用。就比如说我们的掌门可以发出多条命令,比如进攻啊埋伏啊疗伤后援啊。(其实不太明白为什么这边存储方式是说stack,栈的话是后进先出啊,可以测试结果看是先进先出???
public delegate void TaskToDo();
        public TaskToDo tasks;

        private void Start()
        {
            // add all u want to call
            tasks += Task1;
            tasks += Task2;
            tasks += Task3; 
            tasks -= Task1;//remove
            if(tasks != null){
                tasks();
            }
        }
        public void Task1(){
            Debug.Log("Task1!");
        }
        public void Task2(){
            Debug.Log("Task2!");
        }
        public void Task3(){
            Debug.Log("Task3!");
        }
           

Events(事件)

  • events: specialized delegates, allowing us to create a type of broadcast(广播) system that allows other classes and objects to subscribe or de-subscribe to our delegates. 也就是说触发事件,让多个物体参与然后执行一些事情。就还是延续上面的宗门相关的例子,比如现在有一场宗派大战(event),好多个宗门(class)都要参加。大战开打时,每个宗门的掌门(delegate)就要开始号令本门弟子(符合signature)执行战斗(methods)。

举个例子,我们现在要通过一个按钮,当我点击它的时候,我希望当我点击这个按钮时,所有场景里的小方块都要变色。在之前我们都怎么做,获取场景里的小方块然后写个判断,当我按下按钮时foreach每个方块执行变色代码对吧。

  • 按钮: 右键->UI->Button. 在Inspector的Button的这个地方,我们可以对按下按钮要触发的函数进行设置。左边是脚本附着的Obj,右边是要调用的脚本里的方法
    [U3D Learning Note] Unity C# Survival Guide (17) --Delegates and Events

如果用event相关的话要怎么做?

首先我们先声明一个delegate用于引用点击的函数,然后声明一个event,要注意声明event时要把参与者,也就是delegate是哪些范围的也要说明,就像宗门间的大比只有宗门可以参加,魔教啊散修组织啊就不参加。然后我们要做个判断是否有宗门参加了这次宗门大比,有的话,我们就可以调用这个event方法。调用event方法的方式和调用普通的方法一样。这边涉及到设计模式:观察者模式

  • Observer Pattern(观察者模式): subscribers and listeners. 在这个例子中,listeners是小方块,它们可以订阅方法onClick,当我们按下按钮时,我们要判断有没有listener订阅了onClick。
public class MainForEvent : MonoBehaviour
    {
        public delegate void ActionClick();

        public static event ActionClick onClick;
        public void ButtonClick(){
            if(onClick != null){
                //change all color
                onClick();
            }
        }
        // Start is called before the first frame update        
    }
           

现在我们给每个listener赋予一个脚本,所有独立的个体都会共享同一个脚本。The whole purpose of working with delegates in events is that it allows these cubes to be self contained. In this example, They don’t rely on the Main, they don’t rely on the button and they don’t rely on each other.

在这边我们给事件onClick方法赋值,把delegate所代表的方法添加到事件中,这个过程就叫做订阅。

public class CubeForEvent : MonoBehaviour
    {
        // Start is called before the first frame update
        void Start()
        {
            MainForEvent.onClick += ChangeColor;
        }

        // Update is called once per frame
        public void ChangeColor(){
            this.GetComponent<Renderer>().material.color = new Color(Random.value, Random.value, Random.value);
        }
    }
           

就 整个的运行机制是什么呢?在这个例子中Main会对所有的cube进行一个foreach loop,然后过滤掉不属于代表的cube,剩余的进行改变颜色操作。但是我们cube之间是没有联系的,绑定Main脚本的Main Camera也没有获取cube的信息,都是独立开的。

场景应用很多,比如你的游戏中某个怪的生命值降到了30%以下,这个时候触发event,告知周围其他的怪。

  • WHY use events: events have inherit security while delegate variables do not. If we use the delegate variable, other classes could invoke and control the execution of your delegate. With events however, it only allows the classes to subscribe and unsubscribe from our delegate. It doesn’t allow us to actually invoke it. We can only do the subscribing and the unsubscribing. And u want to make sure that when u’re subscribing, u’re always unsubscribing somewhere, usually when u destory the obj. So we usually have this script for listener to avoid creating any type of error in your application.
private void OnDisable() {
            MainForEvent.onClick -= ChangeColor;    
        }
           

emmm我们前面讲宗门例子时有提到我们的掌门可以发出多条命令,这些命令会分配给不同弟子比如身手敏捷的弟子埋伏暗杀啊修习炼体术的弟子负责当肉盾啊。同样的,在按钮和小方块的例子中,我们就再加一个小球吧,但是小球进行的操作不是变色而是掉落,那我们就可以对小球做这样一个脚本

public class Sphere : MonoBehaviour
    {
        private void Start()
        {
            MainForEvent.onClick += LetItFall;
        }
        public void LetItFall(){
            this.GetComponent<Rigidbody>().useGravity = true;
        }
    }
           

Ok总结一下,event就是特殊情况下的deligate,是有触发场景条件的。以宗门的例子来说,宗门大比是一个event,宗门招新也是一个event,再不同event下,我们掌门会让弟子们做不同事情。有了event就能更好管理操作。下面的代码实现一个不同event的切换

/* Main */
	public class MainForEvent : MonoBehaviour
    {
        public delegate void ActionClick();
        public static event ActionClick onClick;

        public delegate void Move(int des);
        public static event Move toMove;

		public delegate void ChangePosition(Vector3 posi);
        public static event ChangePosition newPosi;
        
        public bool isDanger = true;
        // in different situation, invoke different event, and do different tasks
        public void ButtonClick(){
            if(isDanger==true && onClick != null){
                //change all color
                onClick();
                isDanger = false;
            }else if(isDanger==false && toMove != null){
                toMove(2);
                isDanger = true;
            }
        }
        private void Update()
        {
            if(Input.GetKeyDown(KeyCode.Space)){
                if(newPosi != null){
                    newPosi(new Vector3(5,2,0));
                }
            }
        }
    }

/* Cube */
	public class CubeForEvent : MonoBehaviour
    {
        // Start is called before the first frame update
        void Start()
        {
            MainForEvent.onClick += ChangeColor;
            MainForEvent.toMove += ChangePosi;
            MainForEvent.newPosi += ChangePosi;
        }

        // Update is called once per frame
        public void ChangeColor(){
            this.GetComponent<Renderer>().material.color = new Color(Random.value, Random.value, Random.value);
        }

        private void OnDisable() {
            MainForEvent.onClick -= ChangeColor;    
        }

        private void ChangePosi(int des){
            transform.position = transform.position + new Vector3(0, des, 0);
        }
        private void ChangePosi(Vector3 posi){
            transform.position = posi;
        }
    }
           

哦对 因为event是整个游戏过程中都可能会出发的,所以设置为静态变量。然后因为delegate有很严格的匹配,所以你的obj上的方法可以是多态的,它会自己匹配完全符合的那一个。应用场景的话,比如满血的event你的角色移动速度固定(无输入参数), 残血event时速度根据血量的百分比进行加速(输入参数为血量); 又或者是普攻的攻击力是多少多少(无输入参数),有buff加成的话攻击力多少多少(输入参数为buff对应), 不同buff加成方式也不一样,有的是按百分点(float),有的是直接一个整数(int),也有可能多个buff叠加(多参数),如果给每种都单独命名的话就很麻烦嘛(毕竟取名无能

(看到这边休息一下吧正好网卡了orz 啊好累 就三节我居然看了一天(窒息

OK继续

Actions

actions are identical to delegates and events, but instead of using two lines of code, one for delegate and one for the event, we can basically combine them into one line using actions.(作用是无差别的

下面是Action的一个例子 注释掉的部分是delegates+event的

需要注意的是,使用Action时要加上

using System;

才可以用

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ChapterAction{
    public class PlayerForAction : MonoBehaviour
    {
        //public delegate void OnDamageReceived(int currHealth);
        //public static event OnDamageReceived onDamage;

        public static Action<int> OnDamageReceived;
        public int Health {get; set;}

        private void Start()
        {
            Health = 10;
        }

        void Damage(){
            Health --;
            /*
            if(onDamage != null){
                onDamage(Health);
            }*/
            if(OnDamageReceived != null){
                OnDamageReceived(Health);
            }
        }
    }
}


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ChapterAction{
    public class UIManagerForAction : MonoBehaviour
    {       
        private void OnEnable()
        {
            //PlayerForAction.onDamage += UpdateHealth;
            PlayerForAction.OnDamageReceived += UpdateHealth;
        }
        // Start is called before the first frame update
        public void UpdateHealth(int health){
            // displayed updated health here
            Debug.Log("current health: "+health);
        }
    }
}
           

Return Type Delegates and Function

  • function delegate: return type delegate

    举个栗子,我们现在写一个小代码返回输入字符串长度。 之前的话都是这样子

void Start()
        {
            int getlength = GetCharacters("WonWoo");
            Debug.Log(getlength);            
        }
        int GetCharacters(string name){
            return name.Length;
        }
           

那如果用基本的return type delegate呢(看下面) 要注意的一个是,因为这边delegate代表的方法是有返回值的,所以不能像前面代表

void

方法那样可以通过操作符

+=

来进行添加达到同时调用。

public delegate int CharacterLength(string text);

        void Start()
        {
            CharacterLength getLength = new CharacterLength(GetCharacters); //or
            CharacterLength cl = GetCharacters;
			Debug.Log(cl("Joshua"));
            Debug.Log(getLength("wonwoo"));
        }  
        int GetCharacters(string name){
            return name.Length;
        }
    }
           

现在再来看看新同学function delegate。 和Action一样,我们要先导入System这个包。

<>

中是至少一个参数类型的声明,无论几个参数,最后一个都是TResult. 注意一下,因为Func也是一种特殊的delegate,所以传入参数类型不能多种(T),就只能一种,要嘛string要嘛int要嘛其他。

public Func<string,int> CharacterLength;
		void Start()
        {
            CharacterLength = GetCharacters;
            Debug.Log(CharacterLength("Camus"));
        }
           

(我突然迷茫这样有什么用了感觉很鸡肋啊

C#: Lambda Expression

lambda expression allows us write methods in line

举个上一节的栗子 对就是下面这个 看看怎么改成lambda表达式

public Func<string,int> CharacterLength;
		void Start()
        {
            CharacterLength = GetCharacters;
            Debug.Log(CharacterLength("Camus"));
        }
		int GetCharacters(string name){
            return name.Length;
        }
           

lambda操作符是

=>

表示goto,左边是传入参数,右边是返回值或者函数体(如果是函数体的话要用{}括起来

(怎么那么像js啊

public Func<string,int> CharacterLength;
		void Start()
        {
            CharacterLength = (name) => name.Length; //lambda exp
            Debug.Log(CharacterLength("Camus"));
        }
           

Practice

  1. Practice Delegate of Type void with Paramters
public class PracticeDelegateOne : MonoBehaviour
    {
        /* Practice
        // create a delegate of type void that calculates the sum of two num.
        // use a lambda to avoid dedicated method
        */
        
        public delegate void CalculateSum(int a, int b); 
        public CalculateSum calculateSum;

        // Start is called before the first frame update
        void Start()
        {
            calculateSum = (a, b) => Debug.Log(a+b);
            calculateSum(3, 6);
        }
    }
           
  1. Practice Delegate of Type void with No Paramters using lambda

    (用了普通的return void delegate和Action这两种

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace ChapterDelegate{
    public class PracticeDelegateThree : MonoBehaviour
    {
        /* Practice
        // create a delegate of type int that returns the length of gameobj name
        // without param
        */

        // using ori delegate
        public delegate int LengthOfName();
        public LengthOfName lengthOfName;

        // using func delegate
        public Func<int> GetLength;
        
        // Start is called before the first frame update
        void Start()
        {
            lengthOfName = () => this.name.Length;
            Debug.Log(lengthOfName());

            GetLength = () => this.name.Length;
            Debug.Log(GetLength());
        }
    }
}
           
  1. Practice Delegate of Return Type without Paramters

    用delegate和func两种

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace ChapterDelegate{
    public class PracticeDelegateThree : MonoBehaviour
    {
        /* Practice
        // create a delegate of type int that returns the length of gameobj name
        // without param
        */

        // using ori delegate
        public delegate int LengthOfName();
        public LengthOfName lengthOfName;

        // using func delegate
        public Func<int> GetLength;
        
        // Start is called before the first frame update
        void Start()
        {
            lengthOfName = () => this.name.Length;
            Debug.Log(lengthOfName());

            GetLength = () => this.name.Length;
            Debug.Log(GetLength());
        }
    }
}

           
  1. Practice Delegate of Return Type with Paramters

    直接用Func了…懒得用普通delegate

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace ChapterDelegate{
    public class PracticeDelegateFour : MonoBehaviour
    {
        /* Practice
        // create a delegate of type int that takes two param and add the sum
        */

        public Func<int, int, int> AddSum;
          
        // Start is called before the first frame update
        void Start()
        {
            AddSum = (x, y) => x+y;
            Debug.Log(AddSum(1, 1));
        }
    }    
}
           

Simple Callback System

顺便回忆一下协程

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class CallbackSys : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(MyRoutine(()=>{
            Debug.Log("the routine complete");
            Debug.Log("surprise!");
        }));
    }

    public IEnumerator MyRoutine(Action onComplete = null)
    {
        yield return new WaitForSeconds(5.0f); //after game start 5s
        if(onComplete != null){
            onComplete();
        }
    }
}

           

继续阅读