吃饭,睡觉,打豆豆
现在要实现一个游戏中的一个NPC的AI,他只做三件事,吃饭,睡觉,打豆豆,最直接,最简答想到的代码应该是这样。
这样的代码是工作,但是,当NPC的状态和行为不断复杂化的时候,慢慢的,你就会添加各种条件变量,慢慢的,你就会用上了各种switch case,慢慢的,判断层级越来越多,慢慢的....
这个时候你就需要一个状态机了。
FSM简介
FSM定义:
一个有限状态机是一个设备,或者是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得一个状态变换到另一个状态,或者是使一个输入或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。
它的优点:
1.编程快速简单,2易于。调试,3。很少的计算开销,4。直觉性,5。灵活性。
简单的框架
主要的两个类,FSMState表示状态,FSMSystem里面维护了一个状态的列表,最后需要一个StateController作为状态的控制器。
代码清单
FSMState.cs
- 使用 UnityEngine;
- 使用 系统;
- 使用 System.Collections.Generic;
- 使用 System.Collections;
- 公共枚举 过渡
- {
- NullTransition = 0, //使用此转换来表示系统中不存在的转换
- SawPlayer,
- LostPlayer,
- NoHealth,
- ReadytoAim,
- ReadytoShot,
- ReadytoIdle,
- ReadytoAttack,
- ReadytoChasing
- }
- public enum StateID
- {
- NullStateID = 0, //使用此ID表示系统中不存在的状态
- 闲,
- 追逐 //跳
- 攻击,
- 射击,
- 瞄准,
- BacktoIdle,//跳
- 死,
- }
- 公共抽象类 FSMState {
- protected Dictionary <Transition,StateID> map = new Dictionary <Transition,StateID>();
- 受保护的状态 ID stateID;
- public StateID ID { get { return stateID; }}
- public void AddTransition(Transition trans,StateID id)
- {
- //检查任何args是否无效
- if (trans == Transition.NullTransition)
- {
- Debug.LogError(“ FSMState ERROR:NullTransition不允许实际转换” );
- 返回;
- }
- if (id == StateID.NullStateID)
- {
- Debug.LogError(“ FSMState ERROR:NullStateID不允许为真实的ID” );
- 返回;
- }
- //因为这是一个确定性的FSM,
- //检查当前的转换是否已经在地图内
- if (map.ContainsKey(trans))
- {
- Debug.LogError(“ FSMState ERROR:State” + stateID.ToString()+ “has has transition” + trans.ToString()+
- “不可能分配到另一个状态” );
- 返回;
- }
- map.Add(trans,id);
- }
- /// <summary>
- ///这个方法从这个状态的地图中删除一个转换状态。
- ///如果转换不在状态图中,则会打印出一条错误消息。
- /// </ summary>
- public void DeleteTransition(Transition trans)
- {
- //检查NullTransition
- if (trans == Transition.NullTransition)
- {
- Debug.LogError(“ FSMState ERROR:NullTransition不允许” );
- 返回;
- }
- //在删除之前检查该对是否在映射内
- if (map.ContainsKey(trans))
- {
- map.Remove(反式);
- 返回;
- }
- Debug.LogError(“ FSMState ERROR:Transition” + trans.ToString()+ “传递给” + stateID.ToString()+
- “不在国家的过渡名单” );
- }
- /// <summary>
- ///这个方法返回FSM应该是if的新状态
- ///这个状态接收到转换
- /// </ summary>
- public StateID GetOutputState(Transition trans)
- {
- //检查地图是否具有此转换
- if (map.ContainsKey(trans))
- {
- 返回 地图[trans];
- }
- return StateID.NullStateID;
- }
- /// <summary>
- ///此方法用于在进入状态之前设置状态。
- ///在分配前由FSMSystem类自动调用它
- ///到当前状态。
- /// </ summary>
- public virtual void DoBeforeEntering(){}
- /// <summary>
- ///这个方法用来做任何必要的改变变量
- ///在FSMSystem变为另一个之前。它被自动调用
- ///在FSMS系统变为新状态之前。
- /// </ summary>
- public virtual void DoBeforeLeaving(){}
- /// <summary>
- ///此方法决定状态是否应该在其列表上转换到另一个
- /// NPC是对由该类控制的对象的引用
- /// </ summary>
- public abstract void Reason(GameObject player,GameObject npc);
- /// <summary>
- ///这个方法控制游戏世界中NPC的行为。
- /// NPC所做的每一个动作,动作或沟通应放在这里
- /// NPC是对由该类控制的对象的引用
- /// </ summary>
- public abstract void Act(GameObject player,GameObject npc);
- }
FSMSystem.cs
- 使用 UnityEngine;
- 使用 System.Collections;
- 使用 System.Collections.Generic;
- 公共类 FSMSystem {
- 私人 列表<FSMState>状态;
- //可以改变FSM状态的唯一方法是执行转换
- //不要直接更改CurrentState
- private StateID currentStateID;
- public StateID CurrentStateID { get { return currentStateID; }}
- 私人 FSMState currentState;
- public FSMState CurrentState { get { return currentState; }}
- public StateID defaultState { set {defaultState = value;} get { return defaultState;}}
- public void resetToDefaultState()
- {
- currentState = states [0];
- currentStateID = states [0] .ID;
- / * for(int i = 0; i <states.Count; i ++)
- {
- if(states [i] .ID == defaultState)
- {
- currentState = states [i];
- currentStateID = states [i] .ID;
- }
- } * /
- }
- 公共 FSMSystem()
- {
- states = new List <FSMState>();
- }
- /// <summary>
- ///此方法将新状态置于FSM中,
- ///如果状态已经在列表中,则打印出ERROR消息。
- ///添加的第一个状态也是初始状态。
- /// </ summary>
- public void AddState(FSMState s)
- {
- //删除前检查Null引用
- if (s == null )
- {
- Debug.LogError(“FSM ERROR:Null reference is not allowed” );
- }
- //插入的第一个状态也是初始状态,
- //机器在模拟开始时所处的状态
- if (states.Count == 0)
- {
- states.Add(一个或多个);
- currentState = s;
- currentStateID = s.ID;
- 返回;
- }
- //如果状态不在列表中,则将状态添加到列表中
- 的foreach (FSMState状态 在 状态)
- {
- if (state.ID == s.ID)
- {
- Debug.LogError(“FSM ERROR:不可能添加状态” + s.ID.ToString()+
- “因为状态已经添加” );
- 返回;
- }
- }
- states.Add(一个或多个);
- }
- /// <summary>
- ///此方法从FSM列表中删除状态(如果存在)
- ///如果状态不在列表中,则打印错误消息。
- /// </ summary>
- public void DeleteState(StateID id)
- {
- //在删除之前检查NullState
- if (id == StateID.NullStateID)
- {
- Debug.LogError(“FSM ERROR:NullStateID不允许为真实状态” );
- 返回;
- }
- //搜索列表并删除状态,如果它在里面
- 的foreach (FSMState状态 在 状态)
- {
- if (state.ID == id)
- {
- states.Remove(状态);
- 返回;
- }
- }
- Debug.LogError(“FSM ERROR:不可能删除状态” + id.ToString()+
- “不在州名单上” );
- }
- /// <summary>
- ///这个方法试图改变FSM所处的状态
- ///当前状态和转换已过。如果当前状态
- ///没有转换的目标状态通过,
- ///打印出错误信息。
- /// </ summary>
- public void PerformTransition(Transition trans)
- {
- //在更改当前状态之前检查NullTransition
- if (trans == Transition.NullTransition)
- {
- Debug.LogError(“FSM错误:NullTransition不允许实际转换” );
- 返回;
- }
- //检查currentState是否具有作为参数传递的转换
- StateID id = currentState.GetOutputState(trans);
- if (id == StateID.NullStateID)
- {
- Debug.LogError(“FSM ERROR:State” + currentStateID.ToString()+ “没有目标状态” +
- “for transition” + trans.ToString());
- 返回;
- }
- //更新currentStateID和currentState
- currentStateID = id;
- 的foreach (FSMState状态 在 状态)
- {
- if (state.ID == currentStateID)
- {
- //在设置新的状态之前进行后处理
- currentState.DoBeforeLeaving();
- currentState = state;
- //将状态重置为所需状态,然后才能推理或行动
- currentState.DoBeforeEntering();
- 打破;
- }
- }
- } // PerformTransition()
- }
角色控制状态机
角色的控制器通常也是通过状态机来实现。
首先要定义出角色的各种状态已经状态间的转换条件,就像这样:
接下来就是用代码定义各种状态的执行逻辑,跳转条件等。有些复杂的游戏还有通过分层的概念来处理角色的。
下面是最简单的两个状态,空闲和移动。
IdleState.cs
- 使用 UnityEngine;
- 使用 System.Collections;
- 命名空间 CharacterFSM
- {
- 公共类 IdleState:CharacterState
- {
- 浮动 水平移动;
- 浮动 垂直移动;
- public IdleState(Character _host)
- {
- host = _host;
- stateID = CharacterStateID.Idle;
- }
- public override void HandleInput(MovementInput movementInput)
- {
- horizontalMove = movingInput.moveStrafe;
- verticalMove = movingInput.moveForward;
- }
- public override void Act()
- {
- }
- public override void Reason()
- {
- if (horizontalMove * horizontalMove + verticalMove * verticalMove <0.1f)
- {
- 返回;
- }
- 其他
- {
- host.stateController.SetTransition(CharacterStateTransition.ToMove);
- }
- }
- public override void DoBeforeEntering()
- {
- host.animator.SetBool(“Static_b” , true );
- host.animator.SetFloat(“Speed_f” ,0);
- }
- }
- }
MoveState.cs
- 使用 UnityEngine;
- 使用 System.Collections;
- 命名空间 CharacterFSM
- {
- 公共类 MoveState:CharacterState
- {
- 漂浮 stepDelta
- 浮动 stepMark
- public MoveState(Character _host)
- {
- stepMark = -1f;
- stepDelta = 0.3f;
- host = _host;
- stateID = CharacterStateID.Move;
- }
- public override void HandleInput(MovementInput movementInput)
- {
- float maxSpeed = host.MaxSpeed * Mathf.Sqrt(movingInput.moveStrafe * movingInput.moveStrafe + movingInput.moveForward * movingInput.moveForward);
- host.CurrentSpeed - = 2 * host.Acceleration * Time.deltaTime;
- host.CurrentSpeed = Mathf.Max(maxSpeed,host.CurrentSpeed);
- Vector2 tmp = new Vector2(movingInput.moveStrafe,movingInput.moveForward).normalized * host.CurrentSpeed;
- host.CurrentVelocity = new Vector3(tmp.x,0,tmp.y);
- host.animationController.SetSpeed(host.CurrentSpeed);
- }
- public override void Act()
- {
- }
- public override void Reason()
- {
- if (host.CurrentSpeed <0.01f)
- {
- host.stateController.SetTransition(CharacterStateTransition.ToIdle);
- }
- }
- public override void DoBeforeLeaving()
- {
- }
- public override void DoBeforeEntering()
- {
- host.animationController.PerformMove();
- }
- }
- }
还有一个比较重要的类,CharacterStateController
- 使用 UnityEngine;
- 使用 System.Collections;
- 使用 CharacterFSM;
- public class CharacterStateController {
- CharacterFSMSystem characterFsm;
- 性格特征
- public CharacterStateController(Character _character)
- {
- character = _character;
- }
- public CharacterStateID GetCurrentStateID()
- {
- 返回 characterFsm.CurrentStateID;
- }
- public void Init()
- {
- ConstructFSM();
- }
- //每帧调用一次更新
- public void Update(){
- 的debug.log(GetCurrentStateID());
- //Debug.Log(character.movementInput.moveForward +“”+ character.movementInput.moveStrafe);
- characterFsm.CurrentState.HandleInput(character.movementInput);
- characterFsm.CurrentState.Reason();
- characterFsm.CurrentState.Act();
- }
- public void SetTransition(CharacterStateTransition t)
- {
- if (characterFsm!= null )
- {
- characterFsm.PerformTransition(T);
- }
- }
- void ConstructFSM()
- {
- IdleState idleState = new IdleState(character);
- idleState.AddTransition(CharacterStateTransition.ToMove,CharacterStateID.Move);
- MoveState moveState = new MoveState(character);
- moveState.AddTransition(CharacterStateTransition.ToIdle,CharacterStateID.Idle);
- characterFsm = new CharacterFSMSystem();
- characterFsm.AddState(idleState);
- characterFsm.AddState(moveState);
- }
- }
这个类没必要声明称Monobehavior,只需要作为汉字的一个成员来处理就可以了。运行的效果是这样的