【Unity小技巧】限定条件与释放权限的时机

前言

在游戏中,存在许多限定条件,例如在对话状态下玩家就不能控制角色在场景左右移动,在很早之前开发《名为怪物游戏》的时候,开发菜单系统使用了「栈」这种数据结构来解决这个问题,但有的时候限定条件会存在多个触发器的问题。

名为怪物游戏菜单系统:https://huotuyouxi.com/2021/08/17/monster-game-14

以横版卷轴为例,玩家在场景中左右移动,按键释放技能等等,统一归纳为「玩家操作」,而如果玩家打开菜单或者与NPC进行对话的时候应该限制玩家不能操作,不然就太奇怪了;这种时候,使用栈不一定能很好解决问题,用栈可以很方便的解决多级菜单控制权问题,但是限制玩家操作的地方很多,例如前面的打开菜单和NPC对话,这种多对一的情况下,可能会出现乱序问题,比如玩家在打开菜单的情况下,因为某些情况发生了移动(比如站在斜面上因为重力影响而下滑)而恰好滑入了某个剧情触发器,进而自动弹出对话,那么这个时候,玩家的菜单并没有关闭,但是已经进入了对话系统,当对话结束的时候,菜单窗口还在,可是因为对话系统结束了,执行了出栈操作,解放了玩家的操作权,因而就会导致玩家明明开着菜单,却可以在地图移动的情况。

多级菜单的控制权转移

多级菜单可以逐级限制,逐级释放权限,但是很多场景并不是这么有序的情况。

玩家操作权限

玩家的操作权限受到很多其他系统的控制,如何保证有序的执行呢?

思路

当一个条件存在多引用的时候,何时释放权限成为了最大问题。
我想到了利用内存的垃圾回收机制——即计数器引用的方式来控制权限。
这是面试很常见的一道问题:

请解释一个变量是如何被系统回收的?

首先,变量在内存中有两个字段要保存,第一个是变量本身的值,第二个是变量的引用次数,每当变量被引用就会让计数器自增一次,而每当调用完毕就会让计数器自减,然后系统再根据变量的计数器来判断这个变量要不要被回收(即计数器为0的变量就会被判定为无用变量)。

由上面的思路,在写某些限定条件的时候,可以写一个如下的父类:

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
31
32
/// <summary>
/// 一个计数器模式
/// </summary>
namespace FireRabbit
{
public class Counter
{
/// <summary>
/// 引用计数器
/// </summary>
protected int count;

/// <summary>
/// 功能权限判定
/// </summary>
/// <returns></returns>
public bool IsEnabled()
{
return count == 0;
}

public void Increment()
{
count++;
}

public void Decrement()
{
count--;
}
}
}

下面是一个简单的示例:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class Test : Counter
{
public void Handle()
{
if (IsEnabled())
{
// 执行某些逻辑
}
else
{
// 还存在未被释放的引用,无法执行的处理
}
}
}

public class A
{
public void DoSomeThing(Test test)
{
// 开始执行,先让Test的计数器自增,令其处于被引用状态
test.Increment();

// ... 这里执行A的逻辑
}

/// <summary>
/// 执行完毕后释放引用计数器
/// </summary>
/// <param name="test"></param>
public void OnCompleted(Test test)
{
test.Decrement();
}
}

public class B
{
public void DoSomeThing(Test test)
{
// 开始执行,先让Test的计数器自增,令其处于被引用状态
test.Increment();

// ... 这里执行A的逻辑
}

/// <summary>
/// 执行完毕后释放引用计数器
/// </summary>
/// <param name="test"></param>
public void OnCompleted(Test test)
{
test.Decrement();
}
}

public class Main
{
public void Method()
{
var test = new Test();
var a = new A();
var b = new B();

// 没有任何引用的时候(正常执行)
test.Handle();

// 依次执行a和b的方法
a.DoSomeThing(test);
b.DoSomeThing(test);

// 现在就无法执行了(因为a和b还未释放引用)
test.Handle();

// 将A的引用释放
a.OnCompleted(test);

// 因为还存在b的引用未被释放,所以还是执行不了
test.Handle();

// 再将b释放
b.OnCompleted(test);

// 至此,所有引用都已经被释放,可以正常执行了
test.Handle();
}
}

好了,这就是利用了引用计数器模式解决无序权限的限制与释放时机问题。

文章作者: 火烧兔子
文章链接: http://huotuyouxi.com/2022/11/11/unity-tips-036/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 火兔游戏工作室