什么是FSM
FSM 即有限状态机,它是一个状态管理系统,表示一个对象的几种状态在指定条件下转移行为,即随着条件的不断改变内部状态不断地切换。
FSM用处或者使用背景
通常使用FSM去实现一些简单的AI逻辑,对于游戏中的每个对象都可以在其生命周期中分出一些状态,比如一个小兵,他可能在休息,或者巡逻,当敌人出现时,他的状态可能切换为追逐敌人或者攻击敌人,当某些条件成立时,状态机从当前状态转移到下一状态,在不同状态下有不同的任务,所以要使用有限状态机去实现。
FSM使用的必要性
当需要实现角色的状态时以及状态间的切换时,相信第一时间想到的应该是if-else,但是如果状态的切换条件表达式过于复杂,if-else就显得臃肿麻烦了。再加上所有条件的判断全在一起,状态一多也容易出现Bug,扩展也不好,使用if-else就会相当臃肿,所以在这种条件下,有限状态机的使用很有必要,当然如果相对简单的状态切换,使用有限状态机就没必要了,需要根据个人需求。
FSM使用注意点
FSM有两个重要的概念:状态和转移,转移是切换条件,必须有一个初始状态,并保存当前状态,以及注意每种状态的转移的必要条件。
FSM优点
使整个状态切换逻辑比较清晰,增强代码的可扩展性,也便于后期维护。
具体实现代码
下面就实现一下FSM,使用怪物AI的例子
1.创建状态基类FSMstate ,此类负责处理一个状态的周期,状态的进入前,状态中,离开状态等。以及状态切换条件的增删。具体如下:
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4 /// <summary>
5 /// 状态ID
6 /// </summary>
7 public enum StateID
8 {
9 NoneStateID,
10 Parol,//巡逻状态
11 Chase,//追逐状态
12 }
13 /// <summary>
14 /// 状态切换条件
15 /// </summary>
16 public enum Transition
17 {
18 NoneTransition,
19 SeePlayer,//看到玩家
20 LosePlayer,//看不到玩家
21 }
22
23 /// <summary>
24 /// FSM中状态基类(实现状态的基本方法)
25 /// </summary>
26 public abstract class FSMstate {
27
28 protected StateID stateID;//状态对应的ID
29 public StateID ID { get { return stateID; } }
30 protected Dictionary<Transition, StateID> Transition_StateIDDic = new Dictionary<Transition, StateID>();//存储转换条件和状态的ID
31 protected FSMsystem fsmSystem;//管理状态对象(因为要状态更新需要通过FSMsystem去管理实现的,所以需要一个管理对象)
32
33 public FSMstate(FSMsystem fsm)
34 {
35 this.fsmSystem = fsm;
36 }
37 /// <summary>
38 ///增加转条件
39 /// </summary>
40 /// <param name="trans"></param>
41 /// <param name="id"></param>
42 public void AddTransition(Transition trans,StateID id)
43 {
44 if (trans==Transition.NoneTransition)
45 {
46 Debug.Log("添加的转换条件不能为null");
47 return;
48 }
49 if (id==StateID.NoneStateID)
50 {
51 Debug.Log("添加的状态ID不能为null");
52 return;
53 }
54 if (Transition_StateIDDic.ContainsKey(trans))
55 {
56 Debug.Log("添加转换条件的时候," + trans + "已经存在于Transition_StateIDDic中");
57 return;
58 }
59 Transition_StateIDDic.Add(trans,id);
60 }
61 /// <summary>
62 /// 删除转换条件
63 /// </summary>
64 /// <param name="trans"></param>
65 public void DeleteTransition(Transition trans)
66 {
67 if (trans == Transition.NoneTransition)
68 {
69 Debug.Log("删除的转换条件不能为null");
70 return;
71 }
72 if (!Transition_StateIDDic.ContainsKey(trans))
73 {
74 Debug.Log("删除转换条件的时候," + trans + "不存在于Transition_StateIDDic中");
75 return;
76 }
77 Transition_StateIDDic.Remove(trans);
78 }
79 /// <summary>
80 /// 根据转换条件获得状态ID
81 /// </summary>
82 /// <param name="trans"></param>
83 /// <returns></returns>
84 public StateID GetStateID(Transition trans)
85 {
86 if (Transition_StateIDDic.ContainsKey(trans))
87 {
88 return Transition_StateIDDic[trans];
89 }
90 return StateID.NoneStateID;
91 }
92
93 /// <summary>
94 ///转换到此状态前要执行的逻辑
95 /// </summary>
96 public virtual void DoBeforeEnterAcion() { }
97 /// <summary>
98 /// 离开此状态前要执行的逻辑
99 /// </summary>
100 public virtual void DoAfterLevAction(){ }
101 /// <summary>
102 /// 处在本状态时要执行的逻辑
103 /// </summary>
104 /// <param name="TargetObj"></param>
105 public abstract void CurrStateAction(GameObject TargetObj);
106 /// <summary>
107 /// 切换到下一状态需要执行的逻辑
108 /// </summary>
109 /// <param name="TargetObj"></param>
110 public abstract void NextStateAction(GameObject TargetObj);
111
112
113 }
2. 创建状态管理类FSMSystem的创建。用来管理所有的状态(状态的添加,删除,切换,更新等)。
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4 /// <summary>
5 /// 状态处理类(添加,删除,切换,更新等管理所有状态)
6 /// </summary>
7 public class FSMsystem {
8
9 private Dictionary<StateID, FSMstate> StateDic = new Dictionary<StateID, FSMstate>();//保存状态ID以及ID对应的状态
10 private StateID _CurrentStateID;//当前处于的状态ID
11 private FSMstate _CurrentState;//当前处于的状态
12
13 /// <summary>
14 /// 添加状态
15 /// </summary>
16 /// <param name="state">需管理的状态</param>
17 public void AddState(FSMstate state) {
18 if (state==null)
19 {
20 Debug.Log("添加的状态"+state+"不能为null");
21 return;
22 }
23 if (_CurrentState==null)
24 {
25 _CurrentState = state;
26 _CurrentStateID = state.ID;
27 }
28 if (StateDic.ContainsKey(state.ID))
29 {
30 Debug.Log("状态机 "+state.ID+"已经存在,无法添加");
31 return;
32 }
33 StateDic.Add(state.ID,state);
34 }
35 /// <summary>
36 /// 删除状态
37 /// </summary>
38 /// <param name="stateID">删除要管理状态的ID</param>
39 public void DeleteState(StateID stateID)
40 {
41 if (stateID==StateID.NoneStateID)
42 {
43 Debug.Log("无法删除Null的状态");
44 return;
45 }
46 if (!StateDic.ContainsKey(stateID) )
47 {
48 Debug.Log("无法删除不存在的状态:" + stateID);
49 return;
50 }
51 StateDic.Remove(stateID);
52 }
53 /// <summary>
54 /// 状态转换(状态的切换是根据转换条件的变化)
55 /// </summary>
56 /// <param name="trans">转换条件</param>
57 public void PerformTranstion(Transition trans)
58 {
59 if (trans == Transition.NoneTransition)
60 {
61 Debug.Log("无法执行NULL的转换条件");
62 return;
63 }
64 StateID stateId = _CurrentState.GetStateID(trans);
65 if (stateId==StateID.NoneStateID)
66 {
67 Debug.Log("要转换的状态ID为null");
68 return;
69 }
70 if (!StateDic.ContainsKey(stateId))
71 {
72 Debug.Log("状态机中没找到状态ID "+stateId+" 无法转换状态");
73 return;
74 }
75 FSMstate state = StateDic[stateId];//根据状态ID获取要转换的状态
76 _CurrentState.DoAfterLevAction();//执行离开上一状态逻辑
77 _CurrentState = state;//更新当前状态
78 _CurrentStateID = stateId;//更新当前状态ID
79 _CurrentState.DoBeforeEnterAcion();//执行进入当前状态前要执行的逻辑
80 }
81
82 /// <summary>
83 /// 更新当前状态行为
84 /// </summary>
85 /// <param name="TargetObj"></param>
86 public void UpdateState(GameObject TargetObj)
87 {
88 _CurrentState.CurrStateAction(TargetObj);
89 _CurrentState.NextStateAction(TargetObj);
90 }
91
92 }
3.那我们用怪物巡逻,追逐玩家的例子来实现状态机。
3.1. 增加一个PartalState类,用怪物的巡逻。
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4
5 /// <summary>
6 /// 怪物巡逻状态类
7 /// </summary>
8 public class PatrolState : FSMstate {
9 private List<Transform> path = new List<Transform>();//巡逻点
10 private int index = 0;
11 private Transform PlayerTrasform;
12
13 /// <summary>
14 /// 初始化巡逻状态数据
15 /// </summary>
16 /// <param name="fsm"></param>
17 public PatrolState(FSMsystem fsm):base(fsm)
18 {
19 stateID = StateID.Parol;
20 //路点
21 Transform pathTransform = GameObject.Find("Path").transform;
22 Transform[] children = pathTransform.GetComponentsInChildren<Transform>();
23 foreach (Transform child in children)
24 {
25 if (child != pathTransform)
26 {
27 path.Add(child);
28 }
29 }
30 PlayerTrasform = GameObject.Find("Player").transform;
31 }
32
33 /// <summary>
34 /// 当前状态(巡逻状态)执行的逻辑
35 /// </summary>
36 /// <param name="TargetObj"></param>
37 public override void CurrStateAction(GameObject TargetObj)
38 {
39 TargetObj.transform.LookAt(path[index].position);
40 TargetObj.transform.Translate(Vector3.forward * Time.deltaTime * 3);
41 if (Vector3.Distance(TargetObj.transform.position, path[index].position) < 1)
42 {
43 index++;
44 index %= path.Count;
45 }
46 }
47 /// <summary>
48 /// 切换到追逐状态(下一状态)执行的的逻辑
49 /// </summary>
50 /// <param name="TargetObj"></param>
51 public override void NextStateAction(GameObject TargetObj)
52 {
53 if (Vector3.Distance(PlayerTrasform.position, TargetObj.transform.position) < 3)
54 {
55 fsmSystem.PerformTranstion(Transition.SeePlayer);
56 }
57 }
58
59
60 }
3.2.增加一个ChaseState类,用怪物的追逐。
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using UnityEngine;
5
6 /// <summary>
7 /// 追逐状态类
8 /// </summary>
9 public class ChaseState : FSMstate {
10
11 private Transform PlayerTransForm;//玩家位置信息
12
13 public ChaseState(FSMsystem fsm):base(fsm)
14 {
15 stateID = StateID.Chase;
16 PlayerTransForm = GameObject.Find("Player").transform;
17 }
18 /// <summary>
19 /// 追逐状态下执行的逻辑
20 /// </summary>
21 /// <param name="targrtObj"></param>
22 public override void CurrStateAction(GameObject targrtObj)
23 {
24 targrtObj.transform.LookAt(PlayerTransForm.transform.position);
25 targrtObj.transform.Translate(Vector3.forward*2*Time.deltaTime);
26 }
27 /// <summary>
28 /// 切换到下一状态(巡逻)前要执行的逻辑
29 /// </summary>
30 /// <param name="targrtObj"></param>
31 public override void NextStateAction(GameObject targrtObj)
32 {
33 if (Vector3.Distance(PlayerTransForm.position,targrtObj.transform.position)>6)
34 {
35 fsmSystem.PerformTranstion(Transition.LosePlayer);
36 }
37 }
38 }
3.3 创建一个控制怪物的脚本Enemy
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4 /// <summary>
5 /// 怪物类
6 /// </summary>
7 public class Enemy : MonoBehaviour {
8
9 private FSMsystem fsmsystem; //在Enemy类中,实例化FSMSystem对象,添加巡逻和追逐状态,还有之间的转换条件
10 void Start () {
11 InitFsm();//创建状态机
12 }
13
14 void Update () {
15 fsmsystem.UpdateState(this.gameObject);//检查更新状态
16 }
17 /// <summary>
18 /// 创建状态机
19 /// 怪物有两种状态分别是巡逻和追逐玩家
20 /// 如果怪物初始状态(设置为Parol状态)一旦SeePlayer 切换状态被激活后,就切换到Chase状态
21 /// 如果他在Chase状态一旦LosePlayer状态被激活了,它就转变到Parol状态
22 /// </summary>
23 void InitFsm()
24 {
25 fsmsystem = new FSMsystem();
26 FSMstate PatrolState = new PatrolState(fsmsystem);
27 PatrolState.AddTransition(Transition.SeePlayer, StateID.Chase);
28 FSMstate ChaseState = new ChaseState(fsmsystem);
29 ChaseState.AddTransition(Transition.LosePlayer, StateID.Parol);
30 fsmsystem.AddState(PatrolState);//初始状态
31 fsmsystem.AddState(ChaseState);
32 }
33 }
以上就用FSM有限状态机实现了怪物AI,具体代码中都有注释,有理解不到位的地方,望请指正。
欲戴王冠 必承其重