简单的有限状态机设计---上

本文通过实例演示如何使用有限状态机制作敌方AI行为。包括巡逻、追逐、攻击三种状态及状态间的转换逻辑,并提供了Unity代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大家好,这次给大家分享下最近学习到的有限状态机的简单制作,我们先介绍下什么是有限状态机。
   有限状态机(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反应了。   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值