Delegates
?Delegate
是一种类型,可以使用委托(delegate)来实现模块化的编程。通常用于表示对一个或多个方法的引用。Delegate
可以将方法作为参数传递给其他方法,也可以将方法存储在变量中以便稍后调用。Delegate
通常用于实现回调函数、事件处理程序和插件架构。
Delegate
的用途如下:
Delegate
来实现回调函数。在调用的方法中将Delegate
作为参数传递,执行完后调用Delegate
所引用的方法。Delegate
。事件是用于触发和处理应用程序中的事件的机制,如按钮点击、鼠标移动等。通过使用Delegate
,我们可以在事件触发时调用相应的事件处理程序方法。Delegate
作为参数传递和调用外部程序的方法。后面的内容主要的就是讲述实际的代码写法与应用,以让你更直观地感受委托的作用。
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;
}
}
在C#中,有一些内置的delegate
类型,可以在不需要显式定义新的delegate
类型的情况下使用。
以下是几个常见的内置delegate类型:
Action
:表示没有返回值的方法。Action
类型可以接受0~16
个参数。例如,Action
表示没有返回值且接受一个int
参数的方法。Func
:表示具有返回值的方法。Func
类型的最后一个类型参数是返回类型,其他类型参数是方法参数。例如,Func
表示接受两个int
参数并返回一个int
值的方法。Predicate
:表示具有bool
返回值的方法,接受一个参数。Predicate
表示接受一个T
类型参数并返回bool
的方法。这些内置的delegate
类型可以大大简化代码,减少代码量,并提高代码的可读性。
例如,使用Action
和Func
可以避免为每个方法定义一个新的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的例子,正常来说我们要在计时器执行某个操作,会选择在同一个类中Update
函数直接加入计时的判断操作,现在要把他们拆开,在C#代码定义了一个计时器,并在计时器结束时执行一个回调函数。
TimerExample类
调用了actionOnTimer
的SetTimer()
方法,该方法接受两个参数:一个表示计时器持续时间的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
,分别命名为Testing
和ActionOnTimer
,Testing
要Add Component
脚本TimerExample
,ActionOnTimer
要Add Component
脚本ActionOnTimer
。
之后自己调试就可以看到效果了
以下的内容全是伪代码,方便理解
场景:
假设我们制作了一个场景地图,该场景有一个角色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();
}
}
}