什么是FSM
FSM(Finite State Machine),无限状态机。一个状态机就是一个设备,具有有限的状态数量,在任何给定的时间根据输入和自己预先定好的状态转换规则,从一个状态跳转到另一个状态。一个有限状态机在同一时间只能处于某一个状态。
- 一个简单的例子:一个灯泡的开关就是一个状态机。该状态机有开状态(亮)和关状态(暗),当该状态机接受关闭输入的时候,灯泡从开状态转换到关状态,也就是灯泡会灭掉。当该状态机接受打开输入的时候,灯泡从关状态转换到开状态,也就是说灯泡会变亮。
从基类State说起
基类state可以是一个抽象函数也可以是一个纯虚函数,定义了四个行为方法:
entity:表示一个具有若干个状态的实体,比如npc在逃跑,ncp对应实体,逃跑对应状态。
- IntoState(T entity):
- 当进入该状态时调用的方法,只调用一次。
- 比如,要实现npc进入<逃跑>状态时,会尖叫一声,那么尖叫这个动作就可以在IntoState()方法里面实现
- Execute(T entity):
-
- 当进入该状态后每一帧都会调用的方法,该状态一般用来实现状态的跳转逻辑或者待在这个状态的持续时间的计数。
- 比如,npc处于<逃跑>状态时,逃到安全位置时,npc会跳转到<Idle>状态,这个逻辑可以在Execute()实现,if(没有威胁){切换状态到Idle;}
-
- Exit(T entity):
-
- 当从该状态切换到另一个状态之前,会调用该函数,只调用一次。
- 比如,想要实现npc从<逃跑>状态切换到<Idle>状态的时候,会做出一个擦冷汗的动作,可以在Exit()方法里面实现。
-
- OnMessage(T entity, Telegram msg):
-
- 当实体(entity)处于该状态时,刚好有消息(Telegram)发到该entity,会调用该方法。
- 比如,想要实现npc处于<逃跑>状态时,如果这时给npc发送一条摔倒的消息时,npc会感到绝望,并说“我要完蛋了”,可以在OnMessage()里面实现。总的来说,该方法可以实现实体(entity)处于某状态对某条特定的消息进行特殊的处理。
-
下面贴出基类state的代码
1 /// <summary> 2 /// 状态类的基类 3 /// 所有的角色状态都继承于它 4 /// </summary> 5 public class State<T> { 6 7 /// <summary> 8 /// 进入改状态时,所做的初始化动作 9 /// </summary> 10 /// <param name="entity"></param> 11 public virtual void IntoState(T entity) { } 12 13 /// <summary> 14 /// 处于该状态时,要做的游戏逻辑 15 /// </summary> 16 /// <param name="entity"></param> 17 public virtual void Execute(T entity){} 18 19 /// <summary> 20 /// 退出该状态时,做的收尾工作 21 /// </summary> 22 /// <param name="entity"></param> 23 public virtual void Exit(T entity) { } 24 25 /// <summary> 26 /// 获取消息,并对消息进行处理 27 /// </summary> 28 /// <param name="entity"></param> 29 /// <param name="msg"></param> 30 /// <returns></returns> 31 public virtual bool OnMessage(T entity, Telegram msg) 32 { 33 return true; 34 } 35 36 }
Telegram类表示一条消息体,具体的成员和说明如下:
- int Sender:发送消息的实体(entity)的id
- int Receiver:处理消息的实体(entity)的id
- MessageTypes msg:一个enum类型的消息类型,实体(entity)根据不同的消息类型进行不同的处理
- float DispatchTime:时间戳,当时间到达该时间戳,消息才会分发出去
- object ExtraInfo:消息附带的额外信息,这个可以根据需求自定义,也可以为null
1 public class Telegram { 2 //发送消息的实体(entity)的id 3 public int Sender; 4 //处理消息的实体(entity)的id 5 public int Receiver; 6 //一个enum类型的消息类型,实体(entity)根据不同的消息类型进行不同的处理 7 public MessageTypes Msg; 8 //时间戳,当时间到达该时间戳,消息才会分发出去 9 public float DispatchTime; 10 //消息附带的额外信息,这个可以根据需求自定义,也可以为null 11 public object ExtraInfo; 12 13 public Telegram() { } 14 public Telegram(MessageTypes msg, int s_id, int r_id, float send_time, object info) 15 { 16 Msg = msg; 17 Sender = s_id; 18 Receiver = r_id; 19 DispatchTime = send_time; 20 ExtraInfo = info; 21 } 22 } 23 24 //Telegram的比较规则类,消息队列里的Telegram按照时间戳进行排序 25 public class TelegramCompare : IComparer<Telegram> 26 { 27 public int Compare(Telegram a, Telegram b) 28 { 29 return a.DispatchTime.CompareTo(b.DispatchTime); 30 } 31 } 32 33 //以下是我自定义的一些消息类型 34 public enum MessageTypes 35 { 36 Msg_Shooted, // 被子弹击中 37 Msg_HeroHurt, 38 Msg_EnemeyAnimationHurt, 39 Msg_PlayerNormalAttack,//玩家普通攻击 40 Msg_PlayerSkillHurt, //玩家技能一伤害 41 Msg_PlayerAddHp, // 使用药品 42 Msg_PlayerAddMp, 43 Msg_KnockBack, // 震退信息 44 // Msg_PlayerSkillHurt_2, //玩家技能二伤害 45 Msg_UseNuQi, //使用怒气 46 Msg_EnemyDie // 敌人死亡 47 }
既然是发送消息,那负责发送消息的类是谁?答案是MessageDispatcher单例类,后面会详解这个类的原理,先看一行发送消息的代码。
public class MessageDisPatcher { .................................... /// <summary> /// msg: 消息类型 /// senderId: 发送者ID /// receiverId: 接受者ID /// delay: 延迟时间,例如,delay = 2 表示延迟 2 秒再发送 /// info: 该消息所附带的额外信息,可以是任何类型 /// </summary> public void DispatchMesssage(MessageTypes msg, int senderId, int receiverId, float delay, object info) { ........................... } .................................. }
//npc给自己发一条逃跑的消息,额外信息为null MessageDisPatcher.Instance.DisPatchMessage(Run_Away, npc, npc, 0f, null);