大家好,这次给大家分享下最近学习到的有限状态机的简单制作,我们先介绍下什么是有限状态机。
有限状态机(Finite State Machine) FSM 就是由一组状态组成,状态机会根据状态 、情况改变当下的状态。
比如我们要设计一个怪物的AI,首先这个怪物有好几组状态: 空闲状态 Idle、攻击状态Attack、巡逻状态、死亡状态。
当然我们还可以加入更多状态,再根据逻辑中的实现,让怪物判断当前环境需要使用什么状态,比如在看到玩家的时候,首先查看自己的血量超不超过50%,如果超过则改变到攻击状态和玩家战斗,否则进行逃跑状态,等。。
这个判断就是一个有限状态机的行为过程。我们可以用if elseif elseif 。。。大量的if else来实现,也或者使用switch case 来实现判断,另外一种就是使用通用的框架来实现,我们首先使用switch来尝试制作。
首先我们制作的一个例子,有一个AI角色,它的逻辑有
1,初始状态是巡逻状态 Patrol,这时候它会选择一个我们设定好的点循环移动,(当然我们可以自己修改得更复杂点)如果发现了玩家,它会进入追逐状态 Chase。
2 在追逐状态中,如果玩家离开了AI的攻击范围,则继续追逐,如果在攻击范围内,则进入攻击状态Attack ,发射子弹。
3 在攻击状态下,如果玩家离开攻击范围,再次追逐,如果玩家很远,离开了我们预设的距离,则需要忘记玩家,重新回到巡逻状态。
然后我们开始搭建一个简单的环境来演示一下 ,先添加一个地面,添加Tag:Player(玩家),PatrolPoint(寻路点),Bullet(子弹)
EnemyAI (怪物AI)
创建一个空物体,然后添加几个立方体,都添加上PatrolPoint标签,我们的怪物AI就可以把这些点当做巡逻点移动了
然后我们再创建一个子弹预设物,使用一个球形sphere 添加刚体,然后在添加一个脚本 bullet.cs
脚本的代码应该是这样的
接下来我们创建一个敌人AI,为了能简单演示,我就直接创建一个第一人称角色控制器 加上碰撞盒,然后添加一个脚本SimpleFSM.cs 。
该脚本能控制敌人AI的移动,这个脚本基于FSM状态制作的,所以还需要继承一个FSM的父类脚本FSM.cs
SimpleFSM.cs的代码
父类FSM.cs的代码
最后,我们实现一下玩家,同样,我们为了简单些,直接使用一个第一人称角色控制器,将Tag选择为Player 具体脚本就不写了,我们直接在Scene 场景中拖动player 玩家移动,
就可以看到我们的怪物AI的一系列AI反应了。
有限状态机(Finite State Machine) FSM 就是由一组状态组成,状态机会根据状态 、情况改变当下的状态。
比如我们要设计一个怪物的AI,首先这个怪物有好几组状态: 空闲状态 Idle、攻击状态Attack、巡逻状态、死亡状态。
当然我们还可以加入更多状态,再根据逻辑中的实现,让怪物判断当前环境需要使用什么状态,比如在看到玩家的时候,首先查看自己的血量超不超过50%,如果超过则改变到攻击状态和玩家战斗,否则进行逃跑状态,等。。
这个判断就是一个有限状态机的行为过程。我们可以用if elseif elseif 。。。大量的if else来实现,也或者使用switch case 来实现判断,另外一种就是使用通用的框架来实现,我们首先使用switch来尝试制作。
首先我们制作的一个例子,有一个AI角色,它的逻辑有
1,初始状态是巡逻状态 Patrol,这时候它会选择一个我们设定好的点循环移动,(当然我们可以自己修改得更复杂点)如果发现了玩家,它会进入追逐状态 Chase。
2 在追逐状态中,如果玩家离开了AI的攻击范围,则继续追逐,如果在攻击范围内,则进入攻击状态Attack ,发射子弹。
3 在攻击状态下,如果玩家离开攻击范围,再次追逐,如果玩家很远,离开了我们预设的距离,则需要忘记玩家,重新回到巡逻状态。

然后我们开始搭建一个简单的环境来演示一下 ,先添加一个地面,添加Tag:Player(玩家),PatrolPoint(寻路点),Bullet(子弹)
EnemyAI (怪物AI)
创建一个空物体,然后添加几个立方体,都添加上PatrolPoint标签,我们的怪物AI就可以把这些点当做巡逻点移动了

然后我们再创建一个子弹预设物,使用一个球形sphere 添加刚体,然后在添加一个脚本 bullet.cs

脚本的代码应该是这样的
[C#]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
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
|
using
UnityEngine;
using
System.Collections;
public
class
Bullet : MonoBehaviour
{
public
float
LifeTime = 3.0f;
public
int
damage = 50;
public
float
beamVelocity = 100;
public
int
speed = 5;
public
void
Go ()
{
GetComponent<Rigidbody>().AddForce(transform.forward * 10, ForceMode.VelocityChange);
}
void
FixedUpdate()
{
GetComponent<Rigidbody>().AddForce(transform.forward * beamVelocity, ForceMode.Acceleration);
}
void
Start()
{
Destroy(gameObject, LifeTime);
}
void
Update()
{
//transform.position += transform.forward * speed * Time.deltaTime;
}
void
OnCollisionEnter(Collision collision)
{
Destroy(gameObject);
}
}
|
接下来我们创建一个敌人AI,为了能简单演示,我就直接创建一个第一人称角色控制器 加上碰撞盒,然后添加一个脚本SimpleFSM.cs 。
该脚本能控制敌人AI的移动,这个脚本基于FSM状态制作的,所以还需要继承一个FSM的父类脚本FSM.cs
SimpleFSM.cs的代码
[C#]
纯文本查看
复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|
using
UnityEngine;
using
System.Collections;
public
class
SimpleFSM : FSM
{
public
enum
FSMState
{
Patrol,
Chase,
Attack,
Dead,
}
public
float
chaseDistance = 40.0f;
public
float
attackDistance = 20.0f;
public
float
arriveDistance = 3.0f;
public
Transform bulletSpawnPoint;
//自身角色控制器
private
CharacterController controller;
private
Animation animComponent;
//AI的状态
public
FSMState curState;
//自身移动的速度
public
float
walkSpeed = 80.0f;
public
float
runSpeed = 160.0f;
//自转速度
public
float
curRotSpeed = 6.0f;
//子弹
public
GameObject Bullet;
//是否死亡和血量
private
bool
bDead;
private
int
health;
//初始化状态,我们初始化为巡逻状态
protected
override
void
Initialize()
{
curState = FSMState.Patrol;
bDead =
false
;
elapsedTime = 0.0f;
shootRate = 1.0f;
health = 100;
//取巡逻路点
pointList = GameObject.FindGameObjectsWithTag(
"PatrolPoint"
);
//取角色控制器
controller = GetComponent<CharacterController>();
//取自身的动画,我们为了演示简单些,这里就不使用
//animComponent = GetComponent<Animation>();
//寻找下一个路点
FindNextPoint();
//取玩家
GameObject objPlayer = GameObject.FindGameObjectWithTag(
"Player"
);
playerTransform = objPlayer.transform;
if
(!playerTransform)
Debug.Log(
"玩家不存在"
);
}
protected
override
void
FSMUpdate()
{
switch
(curState)
{
case
FSMState.Patrol:
UpdatePatrolState();
break
;
case
FSMState.Chase:
UpdateChaseState();
break
;
case
FSMState.Attack:
UpdateAttackState();
break
;
case
FSMState.Dead:
UpdateDeadState();
break
;
}
//循环时间
elapsedTime += Time.deltaTime;
//如果血量小于0切换到死亡状态
if
(health <= 0)
curState = FSMState.Dead;
}
protected
void
UpdatePatrolState()
{
//如果当前的点已经到达,寻找下一个随机巡逻点
if
(Vector3.Distance(transform.position, destPos) <= arriveDistance)
{
print (
"Reached to the destination point, calculating the next point"
);
FindNextPoint();
}
else
if
(Vector3.Distance(transform.position, playerTransform.position) <= chaseDistance)
{
//检查距离,如果玩家在附近,切换到追逐状态
print (
"Switch to chase state"
);
curState = FSMState.Chase;
}
//自身旋转
Quaternion targetRotation = Quaternion.LookRotation(destPos - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime*curRotSpeed);
//向前移动
controller.SimpleMove(transform.forward * Time.deltaTime * walkSpeed);
//播放前进动画
//animComponent.CrossFade("Walk");
}
protected
void
UpdateChaseState()
{
//设置玩家位置为目标位置
destPos = playerTransform.position;
//检查玩家距离,过近则切换到攻击状态
//如果超出距离则重新回到巡逻状态
float
dist = Vector3.Distance(transform.position, playerTransform.position);
if
(dist <= attackDistance)
{
curState = FSMState.Attack;
}
else
if
(dist >= chaseDistance)
{
curState = FSMState.Patrol;
}
//自身旋转
Quaternion targetRotation = Quaternion.LookRotation(destPos - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime*curRotSpeed);
//向前移动
controller.SimpleMove(transform.forward * Time.deltaTime * runSpeed);
//播放追逐动画
//animComponent.CrossFade("Run");
}
protected
void
UpdateAttackState()
{
Quaternion targetRotation;
//设置玩家位置为目标位置
destPos = playerTransform.position;
//检查玩家距离,过近则攻击
float
dist = Vector3.Distance(transform.position, playerTransform.position);
if
(dist >= attackDistance && dist < chaseDistance)
{
curState = FSMState.Chase;
//FSMState.Attack;
return
;
}
else
if
(dist >= chaseDistance)
//距离过远则切换追逐状态
{
curState = FSMState.Patrol;
return
;
}
targetRotation = Quaternion.LookRotation(destPos - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime*curRotSpeed);
ShootBullet();
//播放攻击动画
//animComponent.CrossFade("StandingFire");
}
private
void
ShootBullet()
{
if
(elapsedTime >= shootRate)
{
GameObject bulletObj = Instantiate(Bullet, bulletSpawnPoint.position, transform.rotation)
as
GameObject;
bulletObj.GetComponent<Bullet>().Go();
elapsedTime = 0.0f;
}
}
protected
void
UpdateDeadState()
{
//播放死亡动画
if
(!bDead)
{
bDead =
true
;
//animComponent.CrossFade("death");
}
}
void
onCollisionEnter(Collision collision)
{
//被攻击
if
(collision.gameObject.tag ==
"Bullet"
)
{
health -= collision.gameObject.GetComponent<Bullet>().damage;
}
}
protected
void
FindNextPoint()
{
print (
"寻找下一个巡逻点"
);
int
rndIndex = Random.Range(0, pointList.Length);
destPos = pointList[rndIndex].transform.position ;
}
}
|
父类FSM.cs的代码
[C#]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
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
|
using
UnityEngine;
using
System.Collections;
public
class
FSM : MonoBehaviour
{
//玩家位置
protected
Transform playerTransform;
//下一个巡逻点
protected
Vector3 destPos;
//巡逻点表单
protected
GameObject[] pointList;
//子弹信息
protected
float
shootRate;
protected
float
elapsedTime;
protected
virtual
void
Initialize() {}
protected
virtual
void
FSMUpdate() {}
protected
virtual
void
FSMFixedUpdate() {}
//初始化信息
void
Start()
{
Initialize();
}
// 循环执行子类FSMUpdate方法
void
Update ()
{
FSMUpdate();
}
void
FixedUpdate()
{
FSMFixedUpdate();
}
}
|
最后,我们实现一下玩家,同样,我们为了简单些,直接使用一个第一人称角色控制器,将Tag选择为Player 具体脚本就不写了,我们直接在Scene 场景中拖动player 玩家移动,
就可以看到我们的怪物AI的一系列AI反应了。
