【Unity】【CSharp】事件和委托的用法

之前写了一篇介绍事件和委托的帖子,但是没怎么运用过,只是使用一些简单的事件

这次看了一个项目的源码,趁着这个机会好好研究一下事件和委托的作用

event的作用

1
2
public Action Action_A;
public event Action Action_B;
  • Action:安全性较差,外部代码可以触发这个委托(使用.Invoke()
  • event Action:安全性强,外部只能订阅或取消订阅(使用-=+=),不能直接触发事件

使用情景

假设现在有两个类ClassAClassB,在以下情况推荐使用事件委托来实现

  1. 需要在特定的时机触发某个方法
  2. 需要使用其他实例的私有属性
  3. 某个方法的实现有两种限制,比如UI中需要该物体处于激活状态,且按下手柄的A键

前面两个都还比较好理解,第三种情况除了事件本身Invoke()调用的时机外,还可以通过注册(+=)注销(-=)来控制这个事件中是否有任务来控制释放的时机

需要在特定时机时触发某个方法

场景:

  • ClassA需要特定的时候(比如参数intA等于0、或某个按钮被按下)执行方法FuncA

具体操作:

  1. 创建一个事件ActionA
  2. 初始化的时候注册这个事件ActionA += FuncA
  3. intA等于0时触发这个事件ActionA.Invoke()

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ClassA : MonoBehaviour		// 这里需要引用UniTask插件,为了简洁我就不写了引用了
{
public int intA;
private Action ActionA;

private void Start()
{
ActionA += FuncA; // 注册事件,并不会执行FuncA方法,只有在A.ActionA被Invoke的时候才会执行FuncA
UpdateNumber().Forget();
}

private async UniTask UpdateNumber()
{
while (true)
{
await UniTask.Delay(TimeSpan.FromSeconds(.5f), ignoreTimeScale: false);
// 当第一个实参大于等于11时,返回0;当第一个实参小于0时返回11;其他则返回第一个实参。
// 这里--在前面,所以是先自减,再将结果当做Repeat的第一个实参
intA = (int)Mathf.Repeat(--intA, 11);

if (intA == 0) //第一个实参为0时也返回0,所以intA是能等于0的
ActionA?.Invoke();
}
}

private void FuncA()
{
Debug.Log($"当前值为: {intA}");
}
}

需要使用其他实例的私有属性

场景:

  • ClassA需要特定的时候(比如参数intA等于0、或某个按钮被按下)获取其他类中的私有方法FuncB或参数strB

具体操作:

  1. ClassA创建一个事件ActionA

  2. ClassB中注册这个事件ClassA.ActionA += FuncB

  3. intA等于0时ActionA.Invoke()

示例代码:

ClassA中的intA等于0时,就会执行ClassB中的FuncB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ClassA : MonoBehaviour
{
public int intA;
public Action ActionA;

private void Start()
{
UpdateNumber().Forget();
}

private async UniTask UpdateNumber()
{
while (true)
{
await UniTask.Delay(TimeSpan.FromSeconds(.5f), ignoreTimeScale: false);
intA = (int)Mathf.Repeat(--intA, 11);

if (intA == 0)
ActionA?.Invoke();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ClassB : MonoBehaviour
{
private string strB = "I am private"; // B内部使用的参数,“我不能暴露出去哦”
public ClassA A; // 在Inspector窗口中引用实例

private void Start()
{
A.ActionA += FuncB; // 注册事件,并不会执行FuncA方法,只有在A.ActionA被Invoke的时候才会执行FuncA
}

private void FuncB()
{
Debug.Log(strB);
}
}

某个方法的实现有两种限制

场景:

  • ClassA需要特定的时候(比如参数intA等于0、某个按钮被按下),且B处于激活状态时执行方法FuncA

具体操作:

  1. ClassA创建事件ActionA
  2. 在激活B时将FuncA注册给ActionA,停用B时将FuncA注销掉

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ClassA : MonoBehaviour
{
public int intA;
public Action ActionA;

private void Start()
{
UpdateNumber().Forget();
}

private async UniTask UpdateNumber()
{
while (true)
{
await UniTask.Delay(TimeSpan.FromSeconds(.5f), ignoreTimeScale: false);
intA = (int)Mathf.Repeat(--intA, 11);

if (intA == 0)
ActionA?.Invoke();
}
}

public void FuncA()
{
Debug.Log("我只有在intA等于0且B处于激活状态的时候执行");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ClassB : MonoBehaviour
{
public ClassA A;

private void OnEnable()
{
A.ActionA += A.FuncA;
}

private void OnDisable()
{
A.ActionA -= A.FuncA;
}
}