喫飯,睡覺,打豆豆
現在要實現一個遊戲中的一個NPC的AI, 他只做三件事,喫飯,睡覺,打豆豆,最直接,最簡答想到的代碼應該是這樣。
void Update()
{
if(Hungry)
{
Eat();
return;
}
if(Sleepy)
{
Sleep();
return;
}
if(Bored)
{
KickDD();
return;
}
}
這樣的代碼是Work,但是,當NPC的狀態和行爲不斷複雜化的時候,慢慢的,你就會添加各種條件變量,慢慢的,你就會用上了各種switch case,慢慢的,判斷層級越來越多,慢慢的....
這個時候你就需要一個狀態機了。
FSM簡介
FSM定義:
一個有限狀態機是一個設備,或者是一個設備模型,具有有限數量的狀態,它可以在任何給定的時間根據輸入進行操作,使得一個狀態變換到另一個狀態,或者是使一個輸入或者一種行爲的發生。一個有限狀態機在任何瞬間只能處在一種狀態。
它的優點:
1.編程快速簡單,2.易於調試,3.很少的計算開銷,4.直覺性,5.靈活性。
簡單的框架
主要的兩個類,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