更深入的FSM内部
有限状态机是一种非常低级的顺序逻辑。它们可用于简单的决策逻辑。让我们举一个FSM的人类例子:
- 输入:翻转开关
- 状态:灯泡处于“开”状态
- 输出:灯泡现在可以为房间提供照明
输入是来自用户的任何形式的刺激,其可以基于执行该状态所需的条件来触发状态的改变。想想开灯:
如您所见,状态描述了FSM的当前状况。在用户进行另一次输入之前,灯泡不会将其状态更改为“关闭”。
输出与FSM的状态相关,具体取决于编程器如何编程FSM。
这是我在Unity中实现动态FSM的方法。这将要求我们拥有每个拥有状态的对象的主FSM类; FSM状态表示保存操作的对象; 和FSM执行国家输出的行动。
现在我们已经向您介绍了FSM,现在让我们来做一些脚本。我们一个接一个地做。首先,让我们组织我们的团结项目。
由您决定放置它们的位置。我总是将我的游戏资产与其他资产分开。我们将把这个FSM放在我们的公共文件夹中(我总是有“Common”文件夹,因为我有自己的库,我在每个游戏中使用),因为这个FSM系统将是动态的,你将能够使用所有内容每场比赛。
我们有一个操作文件夹的原因是编译我们所做的所有操作,并为其他类型的游戏创建一个操作库。
现在让我们创建我们的主要FSM类,并将它放在常见的FSM文件夹中,让我们称之为FSM.cs.
FSM.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
using
UnityEngine
;
using
System
;
using
System
.
Collections
.
Generic
;
namespace
Common
.
FSM
{
///<summary>
///This is the main engine of our FSM, without this, you won't be
///able to use FSM States and FSM Actions.
///</summary>
public
class
FSM
{
}
}
|
一些事情,我是一个命名空间狂热者,如果你没有使用任何一个代码,那么隔离你的代码是一个很好的做法。再次,我有我的公共库,这就是为什么我把它放在Common下面。如果需要,您可以创建自己的命名空间。
正如您所看到的,我们不会继承MonoBehaviour类,因为我们将拥有一个更新函数,该函数将在AI的更新下调用,而不是更新FSM。
现在让我们为FSM制作一些变量和函数。
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
|
private
readonly
string
name
;
public
string
Name
{
get
{
return
name
;
}
}
///<summary>
/// This is the constructor that will initialize the FSM and give it
/// a unique name or id.
///</summary>
public
FSM
(
string
name
)
{
this
.
name
=
name
;
}
///<summary>
/// This initializes the FSM. We can indicate the starting State of
/// the Object that has an FSM.
///</summary>
public
void
Start
(
)
{
}
///<summary>
/// This changes the state of the Object. This also calls the exit
/// state before doing the next state.
///</summary>
public
void
ChangeToState
(
)
{
}
///<summary>
/// This changes the state of the Object. It is not advisable to
/// call this to change state.
///</summary>
public
void
EnterState
(
)
{
}
private
void
ExitState
(
)
{
}
///<summary>
/// Call this under a MonoBehaviour's Update.
///</summary>
public
void
Update
(
)
{
}
///<summary>
/// This handles the events that is bound to a state and changes
/// the state.
///</summary>
public
void
SendEvent
(
)
{
}
|
只是一个提示,如果您认为代码由于所有摘要而开始看起来很脏,只需启用monodevelop的代码colding。只需转到工具/选项/文本编辑器/常规,然后选中“启用代码折叠”
现在我们可以在代码本身中折叠摘要。
在我们继续之前,让我们创建FSMState和FSMAction,因为我们需要在FSM中创建将返回它们的函数。让我们从FSMAction开始吧。
FSMAction将是我们操作的基类,因此是虚函数。如果你不熟悉虚函数,这里是一个谈论多态性的Unity教程。
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
|
using
UnityEngine
;
using
System
.
Collections
;
namespace
Common
.
FSM
{
public
class
FSMAction
{
private
readonly
FSMState
owner
;
public
FSMAction
(
FSMState
owner
)
{
this
.
owner
=
owner
;
}
public
FSMState
GetOwner
(
)
{
return
owner
;
}
///<summary>
/// Starts the action.
///</summary>
public
virtual
void
OnEnter
(
)
{
}
///<summary>
/// Updates the action.
///</summary>
public
virtual
void
OnUpdate
(
)
{
}
///<summary>
/// Finishes the action.
///</summary>
public
virtual
void
OnExit
(
)
{
}
}
}
|
现在让我们启动FSMState,让我们称之为FSMState.cs:
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
|
using
UnityEngine
;
using
System
;
using
System
.
Collections
.
Generic
;
namespace
Common
.
FSM
{
public
class
FSMState
{
private
List
<
FSMAction
>
actions
;
/// <summary>
/// Initializes a new instance of the <see cref="Common.FSM.FSMState"/> class.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="owner">Owner.</param>
public
FSMState
(
string
name
,
FSM
owner
)
{
}
/// <summary>
/// Adds the transition.
/// </summary>
public
void
AddTransition
(
string
id
,
FSMState
destinationState
)
{
}
/// <summary>
/// Gets the transition.
/// </summary>
public
FSMState
GetTransition
(
string
eventId
)
{
}
/// <summary>
/// Adds the action.
/// </summary>
public
void
AddAction
(
FSMAction
action
)
{
}
/// <summary>
/// This gets the actions of this state
/// </summary>
/// <returns>The actions.</returns>
public
IEnumerable
<
FSMAction
>
GetActions
(
)
{
return
actions
;
}
/// <summary>
/// Sends the event.
/// </summary>
public
void
SendEvent
(
string
eventId
)
{
}
}
}
|
现在我们已经开始了所有事情,让我们完成FSM课程。
让我们在我们的类中添加ff变量。
1
2
3
|
private
readonly
string
name
;
private
FSMState
currentState
;
private
readonly
Dictionary
<
string
,
FSMState
>
stateMap
;
|
当然,名称将是FSM的名称。stateMap将包含一个键,它将是FSMState的事件id,以及FSMState本身,以便我们可以将它们绑定在一起,并通过SendEvent()函数转换到另一个状态。
1
2
3
4
5
|
public
string
Name
{
get
{
return
name
;
}
}
|
当然是FSM的名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
private
delegate
void
StateActionProcessor
(
FSMAction
action
)
;
/// <summary>
/// This gets all the actions that is inside the state and loop them.
/// </summary>
/// <param name="state">State.</param>
/// <param name="actionProcessor">Action processor.</param>
private
void
ProcessStateAction
(
FSMState
state
,
StateActionProcessor
actionProcessor
)
{
FSMState
currentStateOnInvoke
=
this
.
currentState
;
IEnumerable
<
FSMAction
>
actions
=
state
.
GetActions
(
)
;
foreach
(
FSMAction
action
in
actions
)
{
if
(
this
.
currentState
!=
currentStateOnInvoke
)
{
break
;
}
actionProcessor
(
action
)
;
}
}
|
我们将创建一个动作处理器。这将使我们能够动态调用状态内的不同操作,并执行操作。如果您不熟悉代表,请参阅此内容。
现在让我们填充我们之前做过的构造函数,并为我们的FSM创建一个初始化程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
///<summary>
/// This is the constructor that will initialize the FSM and give it
/// a unique name or id.
///</summary>
public
FSM
(
string
name
)
{
this
.
name
=
name
;
this
.
currentState
=
null
;
stateMap
=
new
Dictionary
<
string
,
FSMState
>
(
)
;
}
///<summary>
/// This initializes the FSM. We can indicate the starting State of
/// the Object that has an FSM.
///</summary>
public
void
Start
(
string
stateName
)
{
if
(
!
stateMap
.
ContainsKey
(
stateName
)
)
{
Debug
.
LogWarning
(
"The FSM doesn't contain: "
+
stateName
)
;
return
;
}
ChangeToState
(
stateMap
[
stateName
]
)
;
}
|
如您所知,构造函数使得创建类的实例更容易,更干净,更少耦合,因为此类的变量设置为private。为其他程序员保留不必要的变量。
我们现在将为ChangeToState(),EnterState()和ExitState()添加一个FSMState参数
1
2
3
4
5
6
7
8
9
10
11
12
13
|
///<summary>
/// This changes the state of the Object. This also calls the exit
/// state before doing the next state.
///</summary>
public
void
ChangeToState
(
FSMState
state
)
{
if
(
this
.
currentState
!=
null
)
{
ExitState
(
this
.
currentState
)
;
}
this
.
currentState
=
state
;
EnterState
(
this
.
currentState
)
;
}
|
现在检查状态以避免混淆并避免错误非常重要。
Enter,Exit和Update将是相同的,将由我们的动作处理器处理。
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
|
///<summary>
/// This changes the state of the Object. It is not advisable to
/// call this to change state.
///</summary>
public
void
EnterState
(
FSMState
state
)
{
ProcessStateAction
(
state
,
delegate
(
FSMAction
action
)
{
action
.
OnEnter
(
)
;
}
)
;
}
private
void
ExitState
(
FSMState
state
)
{
FSMState
currentStateOnInvoke
=
this
.
currentState
;
ProcessStateAction
(
state
,
delegate
(
FSMAction
action
)
{
if
(
this
.
currentState
!=
currentStateOnInvoke
)
Debug
.
LogError
(
"State cannont be changed on exit of the specified state"
)
;
action
.
OnExit
(
)
;
}
)
;
}
///<summary>
/// Call this under a MonoBehaviour's Update.
///</summary>
public
void
Update
(
)
{
if
(
this
.
currentState
==
null
)
return
;
ProcessStateAction
(
this
.
currentState
,
delegate
(
FSMAction
action
)
{
action
.
OnUpdate
(
)
;
}
)
;
}
|
现在我们已经创建了循环。让我们开始编写我们的FSMState。首先是它的构造函数。
1
2
3
4
5
6
7
|
public
FSMState
(
string
name
,
FSM
owner
)
{
this
.
name
=
name
;
this
.
owner
=
owner
;
this
.
transitionMap
=
new
Dictionary
<
string
,
FSMState
>
(
)
;
this
.
actions
=
new
List
<
FSMAction
>
(
)
;
}
|
在我们进一步讨论之前,让我们谈谈过渡。按其名称进行的转换是两个州之间的变化。我们将使用字符串作为在状态之间切换的键。所有这些都包含在字典中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/// <summary>
/// Adds the transition.
/// </summary>
public
void
AddTransition
(
string
id
,
FSMState
destinationState
)
{
if
(
transitionMap
.
ContainsKey
(
id
)
)
{
Debug
.
LogError
(
string
.
Format
(
"state {0} already contains transition for {1}"
,
this
.
name
,
id
)
)
;
return
;
}
transitionMap
[
id
]
=
destinationState
;
}
/// <summary>
/// Gets the transition.
/// </summary>
public
FSMState
GetTransition
(
string
eventId
)
{
if
(
transitionMap
.
ContainsKey
(
eventId
)
)
{
return
transitionMap
[
eventId
]
;
}
return
null
;
}
|
现在让我们创建一个向我们的状态添加动作的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/// <summary>
/// Adds the action.
/// </summary>
public
void
AddAction
(
FSMAction
action
)
{
if
(
actions
.
Contains
(
action
)
)
{
Debug
.
LogWarning
(
"This state already contains "
+
action
)
;
return
;
}
if
(
action
.
GetOwner
(
)
!=
this
)
{
Debug
.
LogWarning
(
"This state doesn't own "
+
action
)
;
}
actions
.
Add
(
action
)
;
}
|
现在让我们为FSM和FSMState添加事件处理程序。让我们首先回到FSM.cs并向该类添加新函数。
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
|
///<summary>
/// This handles the events that is bound to a state and changes
/// the state.
///</summary>
public
void
SendEvent
(
string
eventId
)
{
FSMState
transitonState
=
ResolveTransition
(
eventId
)
;
if
(
transitonState
==
null
)
Debug
.
LogWarning
(
"The current state has no transition for event "
+
eventId
)
;
else
ChangeToState
(
transitonState
)
;
}
/// <summary>
/// This gets the next state from the current state.
/// </summary>
/// <returns>The transition.</returns>
/// <param name="eventId">Event identifier.</param>
private
FSMState
ResolveTransition
(
string
eventId
)
{
FSMState
transitionState
=
this
.
currentState
.
GetTransition
(
eventId
)
;
if
(
transitionState
==
null
)
return
null
;
else
return
transitionState
;
}
|
让我们通过添加事件处理的实现来最终确定FSMState。
1
2
3
4
|
public
void
SendEvent
(
string
eventId
)
{
this
.
owner
.
SendEvent
(
eventId
)
;
}
|
最后,让我们创建一个在FSM.cs下自动为我们创建状态的函数。
1
2
3
4
5
6
7
8
9
10
11
|
public
FSMState
AddState
(
string
name
)
{
if
(
stateMap
.
ContainsKey
(
name
)
)
{
Debug
.
LogWarning
(
"The FSM already contains: "
+
name
)
;
return
null
;
}
FSMState
newState
=
new
FSMState
(
name
,
this
)
;
stateMap
[
name
]
=
newState
;
return
newState
;
}
|
通过调用此函数,这将使我们能够创建状态而无需在AI类本身上创建。
所以你有它。我们的FSM系统现已完成。请留意我们的下一个博客实施教程。如果您对此主题有疑问,请发表评论。
在本教程的下一部分和最后一部分中,我们将为我们制作的这个FSM引擎进行两种实现。我们将创建一个AI类,它将无限期地写入我们的控制台,另一个AI将无限期地移动并写入我们的控制台。