【C#基础】什么是委托(Delegates)?它在游戏中有什么应用?

What are Delegates?

Intro

Delegate是一种类型,可以使用委托(delegate)来实现模块化的编程。通常用于表示对一个或多个方法的引用。Delegate可以将方法作为参数传递给其他方法,也可以将方法存储在变量中以便稍后调用。Delegate通常用于实现回调函数、事件处理程序和插件架构。

Delegate的用途如下:

  • 回调函数:在某些情况下,我们需要让某个方法在另一个方法执行完毕后自动调用。这时就可以使用Delegate来实现回调函数。在调用的方法中将Delegate作为参数传递,执行完后调用Delegate所引用的方法。
  • 事件处理程序:在C#中,事件是一种特殊的Delegate。事件是用于触发和处理应用程序中的事件的机制,如按钮点击、鼠标移动等。通过使用Delegate,我们可以在事件触发时调用相应的事件处理程序方法。
  • 插件架构:在开发框架时,我们可能需要提供某些接口供外部程序使用,这些接口可能需要动态调用外部程序提供的方法。这时可以使用Delegate作为参数传递和调用外部程序的方法。

后面的内容主要的就是讲述实际的代码写法与应用,以让你更直观地感受委托的作用。

Assigning Delegates

Assigning Delegates允许我们将方法作为变量传递给其他方法或存储在其他对象中以供后续使用。

一、将单个方法赋值给Delegate

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//继承了MonoBehaviour类,就可以添加到Unity游戏对象上
public class Testing : MonoBehaviour
{
	//定义一个名为TestDelegate的委托类型,该类型可以存储不带参数和返回值的方法引用。
    public delegate void TestDelegate();
    //声明上述委托类型为私有变量
    private TestDelegate testDelegateFunction;
    
    void Start()
    {
    	//将等号右侧的方法引用添加到testDelegateFunction委托变量
    	testDelegateFunction = MyTestDelegateFunction;
    	//调用testDelegateFunction委托变量中所存储的方法
        testDelegateFunction();
    }	 
    
    //下面都是一些实现方法
    private void MyTestDelegateFunction()
    {
        Debug.Log("MyTestDelegateFunction");
    }
}

Start()函数里,除了要让委托类型变成私有变量->将方法添加到委托变量这种操作,其实有三种方式可以直接添加方法到委托变量中。

方法一:delegate (){...}

testDelegateFunction = delegate (){ Debug.Log("Anoymous method");};

方法二:使用lambda

testDelegateFunction += () => { Debug.Log("Lambda expression"); };

方法三:new 委托变量(调用方法)

testDelegateFunction = new TestDelegate(MyTestDelegateFunction);

二、将多个方法赋值给Delegate

主要是看Start()函数中的+=-=部分

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//继承了MonoBehaviour类,就可以添加到Unity游戏对象上
public class Testing : MonoBehaviour
{
	//定义一个名为TestDelegate的委托类型,该类型可以存储不带参数和返回值的方法引用。
    public delegate void TestDelegate();
    //定义委托类型,该类型可以存储一个具有一个整数参数并返回布尔值的方法引用。
    public delegate bool TestBoolDelegate(int i);
	//声明上述委托类型的私有变量
    private TestDelegate testDelegateFunction;
    private TestBoolDelegate testBoolDelegateFunction;

    void Start()
    {	/**无参数无返回的例子**/
    	// += 表示将等号右侧的多个方法引用添加到testDelegateFunction委托变量
        testDelegateFunction += MyTestDelegateFunction;
        testDelegateFunction += MySecondTestDelegateFunction;
        //调用testDelegateFunction委托变量中所存储的方法
        testDelegateFunction();
        //从委托变量中移除
        testDelegateFunction -= MySecondTestDelegateFunction;
        
        /**有参数有返回的例子**/
        //也是添加到委托变量
        //testBoolDelegateFunction = (int i) => i < 5; 
        testBoolDelegateFunction = MyTestBoolDelegateFunction;
        //调用testBoolDelegateFunction委托变量中所存储的方法,并将整数参数1传递给该方法
        Debug.Log(testBoolDelegateFunction(1));
    }
	//下面都是一些实现方法
    private void MyTestDelegateFunction()
    {
        Debug.Log("MyTestDelegateFunction");
    }

    private void MySecondTestDelegateFunction()
    {
        Debug.Log("MySecondTestDelegateFunction");
    }

    private bool MyTestBoolDelegateFunction(int i)
    {
        return i < 5;
    }
}

内置 Delegates

在C#中,有一些内置的delegate类型,可以在不需要显式定义新的delegate类型的情况下使用。

以下是几个常见的内置delegate类型:

  • Action:表示没有返回值的方法。Action类型可以接受0~16个参数。例如,Action表示没有返回值且接受一个int参数的方法。
  • Func:表示具有返回值的方法。Func类型的最后一个类型参数是返回类型,其他类型参数是方法参数。例如,Func表示接受两个int参数并返回一个int值的方法。
  • Predicate:表示具有bool返回值的方法,接受一个参数。Predicate表示接受一个T类型参数并返回bool的方法。

这些内置的delegate类型可以大大简化代码,减少代码量,并提高代码的可读性。

例如,使用ActionFunc可以避免为每个方法定义一个新的delegate类型。使用Predicate可以避免编写复杂的if语句来检查某个值是否满足某个条件。

以下是一些示例,展示如何使用内置的delegate类型:

// 使用Action类型定义一个没有参数和返回值的方法
Action myAction = () => Console.WriteLine("Hello world");
myAction();

// 使用Func类型定义一个接受两个int参数并返回一个int值的方法
Func<int, int, int> myFunc = (x, y) => x + y;
int result = myFunc(2, 3);
Console.WriteLine(result);

// 使用Predicate类型定义一个接受一个int参数并返回bool的方法
Predicate<int> myPredicate = x => x > 0;
bool isPositive = myPredicate(5);
Console.WriteLine(isPositive);

Timer Delegate

接下来,讲解有关于Timer的例子,正常来说我们要在计时器执行某个操作,会选择在同一个类中Update函数直接加入计时的判断操作,现在要把他们拆开,在C#代码定义了一个计时器,并在计时器结束时执行一个回调函数。

TimerExample类

调用了actionOnTimerSetTimer()方法,该方法接受两个参数:一个表示计时器持续时间的float类型的timer变量,和一个表示计时器结束时要执行的回调函数的Action类型的timerCallback变量。在这个示例中,计时器持续时间为1秒,回调函数只是简单地输出“Timer complete!”消息。

public class TimerExample : MonoBehaviour
{
    //actionOnTimer对象为ActionOnTimer类的实例
    [SerializeField] private ActionOnTimer actionOnTimer;
    
    void Start()
    {
        actionOnTimer.SetTimer(1f, () => { Debug.Log("Timer complete!"); });
    }
}

ActionOnTimer类

public class ActionOnTimer : MonoBehaviour
{
	//Action类型的delegate,表示在计时器结束时要执行的回调函数。
    private Action timerCallback;
    //表示计时器的剩余时间
    private float timer;
    //将计时器的持续时间和回调函数赋值给这两个变量
    public void SetTimer(float timer,Action timerCallback)
    {
        this.timer = timer;
        this.timerCallback = timerCallback;
    }
    private void Update()
    {
    //如果计时器还没有结束
        if (timer > 0)
        {
        //将计时器的剩余时间减去每帧的时间(即Time.deltaTime)。
            timer -= Time.deltaTime;
            //如果计时器已经结束,我们调用timerCallback变量表示的回调函数。
            if (IsTimerComplete())
            {
                timerCallback();
            }
        }
    }
    //IsTimerComplete()方法用于检查计时器是否已经结束。
    public bool IsTimerComplete()
    {
        return timer <= 0f;
    }
}

在Unity中,创建两个GameObject,分别命名为TestingActionOnTimerTestingAdd Component脚本TimerExampleActionOnTimerAdd Component脚本ActionOnTimer

之后自己调试就可以看到效果了

Module Delegate的应用

以下的内容全是伪代码,方便理解

场景

假设我们制作了一个场景地图,该场景有一个角色player,它的行动逻辑如下:

public class PlayerDelegates : MonoBehaviour
{
    void Update()
    {
        switch (state)
        {
            default:
            case "空闲状态":
                //移动操作
                HandleMovement();
                //攻击操作
                HandleAttack();
                break;
            case "繁忙状态":
                HandleAttack();
                break ;
        }   
    }

    private void HandleAttack()
    {
        //鼠标左键
        if (Input.GetMouseButtonDown(0))
        {
            //使用拳头攻击
            PunchAttack();
        }
    }
    
    private void PunchAttack() { ... }
    private void SwordAttack() { ... }
}

需求:

我们希望可以使用快捷键可以切换我们已经从背包中设置好的武器选项,假设按下键盘Q是切换剑的快捷键,在代码上,我们要从原来的只能使用拳头Punch攻击,变成可以使用剑Sword攻击,该怎么做?

一般,我们会直接使用布尔状态进行判断,改进如下:

public class PlayerDelegates : MonoBehaviour
{
	//设置布尔状态判断
    private bool isUsingSword = false;
    void Update()
    {
        switch (state)
        {
            default:
            case "空闲状态":
                //移动操作
                HandleMovement();
                //攻击操作
                HandleAttack();
                break;
            case "繁忙状态":
                HandleAttack();
                break ;
        }
        if (Input.GetKeyDown(KeyCode.Q))
        {
            SetUseSword();
        }
    }

    private void SetUseSword()
    {
        isUsingSword = true;
    }
    
    private void HandleAttack()
    {
        //鼠标左键
        if (Input.GetMouseButtonDown(0))
        {
            if (isUsingSword)
            {
                //使用剑攻击
                SwordAttack();
            }
            else
            {
                //使用拳头攻击
                PunchAttack();
            }
        }
    }
}

问题:

  • 如果我不是只有一个武器切换的快捷键,而是多个,那么上面的代码将变得冗杂。

当然肯定会有一些”小天才“们想到,我为什么不直接默认Q按键为切换所有武器的快捷键呢?只要切换到我要的武器不就行了,我只能说还是想得太简单了,但是我不说,自己思考去吧。

让我们回到正题,如果要解决这个问题,我们就可以使用到delegate这玩意了,改进如下:

public class PlayerDelegates : MonoBehaviour
{
    private Action attackFunction;

    private void Start()
    {
        attackFunction = PunchAttack;
    }

    void Update()
    {
        switch (state)
        {
            default:
            case "空闲状态":
                //移动操作
                HandleMovement();
                //攻击操作
                HandleAttack();
                break;
            case "繁忙状态":
                HandleAttack();
                break ;
        }

        if (Input.GetKeyDown(KeyCode.Q))
        {
            SetUseSword();
        }
    }

    private void SetUseSword()
    {
        attackFunction = SwordAttack;
    }
    private void HandleAttack()
    {
        //鼠标左键
        if (Input.GetMouseButtonDown(0))
        {
            attackFunction();
        }
    }
}

你可能感兴趣的:(游戏,c#,unity,开发语言)