IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]一个通用的有限状态机(FSM)框架

    qp120291570发表于 2016-04-19 02:08:34
    love 0

    吃饭,睡觉,打豆豆

    现在要实现一个游戏中的一个NPC的AI, 他只做三件事,吃饭,睡觉,打豆豆,最直接,最简答想到的代码应该是这样。

    void Update()
    {
    	if(Hungry)
    	{
    		Eat();
    		return;
    	}
    
    	if(Sleepy)
    	{
    		Sleep();
    		return;
    	}
    
    	if(Bored)
    	{
    		KickDD();
    		return;
    	}
    	
    }



    这样的代码是Work,但是,当NPC的状态和行为不断复杂化的时候,慢慢的,你就会添加各种条件变量,慢慢的,你就会用上了各种switch case,慢慢的,判断层级越来越多,慢慢的....


    这个时候你就需要一个状态机了。


    简单的框架



    主要的两个类,FSMState表示状态,FSMSystem里面维护了一个状态的列表,最后需要一个StateController作为状态的控制器。

    代码清单

    FSMState.cs

    using UnityEngine;
    using System;
    using System.Collections.Generic;
    using System.Collections;
    
    public enum Transition
    {
    	NullTransition = 0, // Use this transition to represent a non-existing transition in your system
    	SawPlayer,
    	LostPlayer,
    	NoHealth,
    	ReadytoAim,
    	ReadytoShot,
    	ReadytoIdle,
    	ReadytoAttack,
    	ReadytoChasing
    }
    
    public enum StateID
    {
    	NullStateID = 0, // Use this ID to represent a non-existing State in your system
    	Idle,
    	Chasing, // jump
    	Attack,
    	Shooting,
    	Aiming,
    	BacktoIdle,//jump
    	Dead,
    }
    
    
    public abstract class FSMState{
    	protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    	protected StateID stateID;
    	public StateID ID { get { return stateID; } }
    
    	public void AddTransition(Transition trans, StateID id)
    	{
    		// Check if anyone of the args is invalid
    		if (trans == Transition.NullTransition)
    		{
    			Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
    			return;
    		}
    
    		if (id == StateID.NullStateID)
    		{
    			Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
    			return;
    		}
    
    		// Since this is a Deterministic FSM,
    		//   check if the current transition was already inside the map
    		if (map.ContainsKey(trans))
    		{
    			Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
    						   "Impossible to assign to another state");
    			return;
    		}
    
    		map.Add(trans, id);
    	}
    
    	/// <summary>
    	/// This method deletes a pair transition-state from this state's map.
    	/// If the transition was not inside the state's map, an ERROR message is printed.
    	/// </summary>
    	public void DeleteTransition(Transition trans)
    	{
    		// Check for NullTransition
    		if (trans == Transition.NullTransition)
    		{
    			Debug.LogError("FSMState ERROR: NullTransition is not allowed");
    			return;
    		}
    
    		// Check if the pair is inside the map before deleting
    		if (map.ContainsKey(trans))
    		{
    			map.Remove(trans);
    			return;
    		}
    		Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
    					   " was not on the state's transition list");
    	}
    
    	/// <summary>
    	/// This method returns the new state the FSM should be if
    	///    this state receives a transition and 
    	/// </summary>
    	public StateID GetOutputState(Transition trans)
    	{
    		// Check if the map has this transition
    		if (map.ContainsKey(trans))
    		{
    			return map[trans];
    		}
    		return StateID.NullStateID;
    	}
    
    	/// <summary>
    	/// This method is used to set up the State condition before entering it.
    	/// It is called automatically by the FSMSystem class before assigning it
    	/// to the current state.
    	/// </summary>
    	public virtual void DoBeforeEntering() { }
    
    	/// <summary>
    	/// This method is used to make anything necessary, as reseting variables
    	/// before the FSMSystem changes to another one. It is called automatically
    	/// by the FSMSystem before changing to a new state.
    	/// </summary>
    	public virtual void DoBeforeLeaving() { }
    
    	/// <summary>
    	/// This method decides if the state should transition to another on its list
    	/// NPC is a reference to the object that is controlled by this class
    	/// </summary>
    	public abstract void Reason(GameObject player, GameObject npc);
    
    	/// <summary>
    	/// This method controls the behavior of the NPC in the game World.
    	/// Every action, movement or communication the NPC does should be placed here
    	/// NPC is a reference to the object that is controlled by this class
    	/// </summary>
    	public abstract void Act(GameObject player, GameObject npc);
    }
    


    FSMSystem.cs

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    
     
    public class FSMSystem{
    	private List<FSMState> states;
    	// The only way one can change the state of the FSM is by performing a transition
    	// Don't change the CurrentState directly
    	private StateID currentStateID;
    	public StateID CurrentStateID { get { return currentStateID; } }
    	private 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;
    			}
    		}*/
    	}
    
    	public FSMSystem()
    	{
    		states = new List<FSMState>();
    	}
    
    	/// <summary>
    	/// This method places new states inside the FSM,
    	/// or prints an ERROR message if the state was already inside the List.
    	/// First state added is also the initial state.
    	/// </summary>
    	public void AddState(FSMState s)
    	{
    		// Check for Null reference before deleting
    		if (s == null)
    		{
    			Debug.LogError("FSM ERROR: Null reference is not allowed");
    		}
    
    		// First State inserted is also the Initial state,
    		//   the state the machine is in when the simulation begins
    		if (states.Count == 0)
    		{
    			states.Add(s);
    			currentState = s;
    			currentStateID = s.ID;
    			return;
    		}
    
    		// Add the state to the List if it's not inside it
    		foreach (FSMState state in states)
    		{
    			if (state.ID == s.ID)
    			{
    				Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() +
    							   " because state has already been added");
    				return;
    			}
    		}
    		states.Add(s);
    	}
    
    	/// <summary>
    	/// This method delete a state from the FSM List if it exists, 
    	///   or prints an ERROR message if the state was not on the List.
    	/// </summary>
    	public void DeleteState(StateID id)
    	{
    		// Check for NullState before deleting
    		if (id == StateID.NullStateID)
    		{
    			Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
    			return;
    		}
    
    		// Search the List and delete the state if it's inside it
    		foreach (FSMState state in states)
    		{
    			if (state.ID == id)
    			{
    				states.Remove(state);
    				return;
    			}
    		}
    		Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
    					   ". It was not on the list of states");
    	}
    
    	/// <summary>
    	/// This method tries to change the state the FSM is in based on
    	/// the current state and the transition passed. If current state
    	///  doesn't have a target state for the transition passed, 
    	/// an ERROR message is printed.
    	/// </summary>
    	public void PerformTransition(Transition trans)
    	{
    		// Check for NullTransition before changing the current state
    		if (trans == Transition.NullTransition)
    		{
    			Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
    			return;
    		}
    
    		// Check if the currentState has the transition passed as argument
    		StateID id = currentState.GetOutputState(trans);
    		if (id == StateID.NullStateID)
    		{
    			Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
    						   " for transition " + trans.ToString());
    			return;
    		}
    
    		// Update the currentStateID and currentState		
    		currentStateID = id;
    		foreach (FSMState state in states)
    		{
    			if (state.ID == currentStateID)
    			{
    				// Do the post processing of the state before setting the new one
    				currentState.DoBeforeLeaving();
    
    				currentState = state;
    
    				// Reset the state to its desired condition before it can reason or act
    				currentState.DoBeforeEntering();
    				break;
    			}
    		}
    
    	} // PerformTransition()
    }
    


    角色控制状态机

    角色的控制器通常也是通过状态机来实现。

    首先要定义出角色的各种状态已经状态间的转换条件,就像这样:




    接下来就是用代码定义各种状态的执行逻辑,跳转条件等。有些复杂的游戏还有通过分层的概念来处理角色的。

    下面是最简单的两个状态,Idle和Move。

    IdleState.cs

    using UnityEngine;
    using System.Collections;
    
    namespace CharacterFSM
    {
    	public class IdleState : CharacterState
    	{
            float horizontalMove;
            float verticalMove;
    
            public IdleState(Character _host)
    		{
    			host = _host;
    			stateID = CharacterStateID.Idle;
    		}
    
    		public override void HandleInput(MovementInput movementInput)
    		{
    			horizontalMove = movementInput.moveStrafe;
                verticalMove = movementInput.moveForward;
            }
    
            public override void Act()
    		{
    
    		}
    		public override void Reason()
    		{
                if (horizontalMove * horizontalMove + verticalMove * verticalMove < 0.1f)
                {
                    return;
                }
                else
                {
                    host.stateController.SetTransition(CharacterStateTransition.ToMove);
                }
            }
    
            public override void DoBeforeEntering()
            {
                host.animator.SetBool("Static_b", true);
                host.animator.SetFloat("Speed_f", 0);
            }
        }
    
    }
    


    MoveState.cs

    using UnityEngine;
    using System.Collections;
    
    namespace CharacterFSM
    {
    	public class MoveState  : CharacterState
    	{
    		float stepDelta;
    		float 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(movementInput.moveStrafe * movementInput.moveStrafe + movementInput.moveForward * movementInput.moveForward);
    
                host.CurrentSpeed -= 2 * host.Acceleration * Time.deltaTime;
                host.CurrentSpeed = Mathf.Max(maxSpeed, host.CurrentSpeed);
    
                Vector2 tmp = new Vector2(movementInput.moveStrafe, movementInput.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

    using UnityEngine;
    using System.Collections;
    using CharacterFSM;
    
    public class CharacterStateController {
    
    	CharacterFSMSystem characterFsm;
        Character character;
    
        public CharacterStateController(Character _character)
        {
            character = _character;
        }
    
    	public CharacterStateID GetCurrentStateID()
    	{
    		return characterFsm.CurrentStateID;
    	}
    
        public void Init()
        {
            ConstructFSM();
        }
    	
    	// Update is called once per frame
    	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,只需要作为Character的一个成员来处理就可以了。运行的效果是这样的。



    参考

    Unity 4.x Game AI Programming

    Game programming pattern - State



沪ICP备19023445号-2号
友情链接