Unity中FSM有限状态机

什么是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,具体代码中都有注释,有理解不到位的地方,望请指正。

posted @ 2019-05-31 16:29  望景思心  阅读(1500)  评论(0编辑  收藏  举报