筆記42 FSM有限狀態機
傳統編碼
拖放模型。設定動畫。
代碼:Player1Controller+出現的小bug
//拿到動畫控制器
private Animator ani;
void Start()
{
ani = GetComponent<Animator>();
}
void Update()
{
//拿到軸
float vertical = Input.GetAxis("Vertical");
float horizontal = Input.GetAxis("Horizontal");
//做成向量
Vector3 dir = new Vector3(horizontal,0, vertical);
//移動
if (dir != Vector3.zero)
{
//旋轉
transform.rotation = Quaternion.LookRotation(dir);
//前進
transform.Translate(Vector3.forward * 3 * Time.deltaTime);
//播放跑步動畫
ani.SetBool("isRun", true);
}
else
{
ani.SetBool("isRun", false);
}
//攻擊
if (Input.GetKeyDown(KeyCode.Space))
{
//SetTrigger觸發
ani.SetTrigger("attack");
}
//再有類似的,就繼續添加if(){}
}
缺點多於優點
再有類似的,就繼續添加if(){}。
整個Update裏寫了一堆if。
一個if就代表一個狀態。
如果寫了二十多個if,一是,修改起來麻煩;
二是,它是按照順序來的,有時修改了中間的代碼,下面的代碼可能出現不知解的bug。
三是,用這種方式寫連擊,很麻煩。
四是,比如攻擊動畫沒做完,按跳躍是可以跳躍的,這是不允許的。
有個優點,就是與玩家相關的代碼全寫在這一個文件裏了。
有限狀態機有兩種簡單方式:方法、類
法一:方法+代碼:Player2Controller
//枚舉。玩家的狀態
public enum PlayerState
{
idle,
run,
attack
}
public class Player2Controller : MonoBehaviour
{
//動畫
private Animator ani;
//當前的狀態。默認是站立
private PlayerState state = PlayerState.idle;
void Start()
{
//獲取ani
ani = GetComponent<Animator>();
}
void Update()
{
//switch:切換小括號裏內容的取值。點擊“補齊代碼”。
switch (state)
{
case PlayerState.idle:
//補充:如果是站立,就調用站立的方法。
idle();
break;
case PlayerState.run:
run();
break;
case PlayerState.attack:
attack();
break;
}
}
//有幾個狀態,就寫幾個方法。此處有三個。
//每個裏面,寫每個要做的事。
void idle()
{
//站立動畫
ani.SetBool("isRun", false);
//得到軸,形成向量
float vertical = Input.GetAxis("Vertical");
float horizontal = Input.GetAxis("Horizontal");
Vector3 dir = new Vector3(horizontal, 0, vertical);
//判斷向量不爲零,說明按了AWSD某個按鍵了,跑步。
if (dir != Vector3.zero)
{
//切換到跑步狀態
state = PlayerState.run;
}
//攻擊
if (Input.GetKeyDown(KeyCode.Space))
{
//切換到攻擊狀態
state = PlayerState.attack;
}
}
void run()
{
//跑步動畫
ani.SetBool("isRun", true);
//得到軸,形成向量
float vertical = Input.GetAxis("Vertical");
float horizontal = Input.GetAxis("Horizontal");
Vector3 dir = new Vector3(horizontal, 0, vertical);
//判斷向量不爲零,說明按了AWSD某個按鍵了,跑步。
if (dir != Vector3.zero)
{
//朝向向量
transform.rotation = Quaternion.LookRotation(dir);
//前進
transform.Translate(Vector3.forward * 3 * Time.deltaTime);
}
else
{
//切換動畫狀態
state = PlayerState.idle;
}
}
void attack()
{
//觸發攻擊
ani.SetTrigger("attack");
//如果當前狀態名稱(方塊上的名稱)不是攻擊,說明攻擊的動畫播放完了。
//if如果 (!不是ani動畫.GetCurrentAnimatorStateInfo狀態(0).IsName名稱("attack1"攻擊))
if (!ani.GetCurrentAnimatorStateInfo(0).IsName("attack1"))
{
//切換站立狀態
state = PlayerState.idle;
}
}
}
法二:類+代碼
需要寫幾個類
代碼:FSMState
//abstract抽象類(不能被實例化。必須有子類繼承,對應關鍵詞override)
public abstract class FSMState
{
//當前狀態ID。它是int類型,因爲玩家的狀態我們是寫在枚舉裏的。
public int stateID;
//狀態擁有者。需要把角色控制類傳過來
public MonoBehaviour mono;
//狀態所屬管理器
public FSMManager fsmManager;
//初始化方法
public FSMState(int stateID,MonoBehaviour mono,FSMManager manager)
{
//賦值
this.stateID = stateID;
this.mono = mono;
this.fsmManager = manager;
}
//抽象方法(子類必須實現的方法)只能包含在抽象類當中。
//用虛方法(允許子類重寫方法)也可以,類就不用寫成抽象類了。
public abstract void OnEnter();
public abstract void OnUpdate();
}
代碼:FSMManager
public class FSMManager
{
//狀態列表
public List<FSMState> stateList = new List<FSMState>();
//當前的狀態(索引)。-1表示沒給狀態。
public int currentIndex = -1;
//改變狀態
public void ChangeState(int StateID)
{
//當前狀態=傳進來的狀態。
currentIndex = StateID;
//stateList[currentIndex]從數組裏取出對應的狀態
//.OnEnter()調用一次OnEnter方法,確保切換到新狀態的第一時間執行OnEnter方法
stateList[currentIndex].OnEnter();
}
//更新方法。這個Update是不會調用的,需要玩家裏的Update協助調用。
public void Update()
{
//如果當前有狀態
if (currentIndex != -1)
{
//stateList[currentIndex]從列表裏拿到當前狀態
//.OnUpdate()調這個狀態裏的OnUpdate方法,確保OnUpdate會被一直調用。
stateList[currentIndex].OnUpdate();
}
}
}
代碼:IdleState : FSMState
/繼承於抽象類FSMState。點擊“生成構造函數”“實現抽象類”
public class IdleState : FSMState
{
//必須實現構造方法。爲了new的時候更方便。 new IdleState(小括號裏提示寫三個參數)
public IdleState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
{
}
//狀態的初始化方法,只調用一次
public override void OnEnter()
{
//mono狀態擁有者,其實就是PlayerController
mono.GetComponent<Animator>().SetBool("isRun", false);
}
//狀態的刷新方法,循環調用
public override void OnUpdate()
{
//監聽跑步狀態,切換。
//得到軸,組成向量,判斷
float vertical = Input.GetAxis("Vertical");
float horizontal = Input.GetAxis("Horizontal");
Vector3 dir = new Vector3(horizontal, 0, vertical);
if (dir != Vector3.zero)
{
//fsmManager狀態所屬於的管理類。(int)PlayerState.run把枚舉轉成int類型
fsmManager.ChangeState((int)PlayerState.run);
}
//監聽攻擊狀態,切換。
if (Input.GetKeyDown(KeyCode.Space))
{
fsmManager.ChangeState((int)PlayerState.attack);
}
}
}
代碼:RunState : FSMState
public class RunState : FSMState
{
public RunState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
{
}
public override void OnEnter()
{
//跑步動畫狀態
mono.GetComponent<Animator>().SetBool("isRun", true);
}
public override void OnUpdate()
{
//得到軸,形成向量,判斷,前進
float vertical = Input.GetAxis("Vertical");
float horizontal = Input.GetAxis("Horizontal");
Vector3 dir = new Vector3(horizontal, 0, vertical);
if (dir != Vector3.zero)
{
//朝向向量
mono.transform.rotation = Quaternion.LookRotation(dir);
//前進
mono.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
}
else
{
//切換到站立狀態
fsmManager.ChangeState((int)PlayerState.idle);
}
}
}
代碼:AttackState : FSMState
public class AttackState : FSMState
{
public AttackState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
{
}
public override void OnEnter()
{
mono.GetComponent<Animator>().SetTrigger("attack");
}
public override void OnUpdate()
{
//判斷當前狀態如果不是攻擊1,就切換到站立
if (!mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("attack1"))
{
fsmManager.ChangeState((int)PlayerState.idle);
}
}
}
代碼:PlayerState3
//枚舉。玩家的狀態。(因爲寫第二個恐龍時,寫了枚舉PlayerState,所以此處改名爲PlayerState3)
//這個枚舉不妨在這的話,在下面的那行代碼無用。
public enum PlayerState3
{
idle,
run,
attack
}
public class player3Controller : MonoBehaviour
{
//狀態管理類
private FSMManager fsmManager;
void Start()
{
//new一個
fsmManager = new FSMManager();
//實例化各種狀態。(0索引與枚舉裏的一致, this傳進來的所屬管理類, fsmManager屬於哪個管理器類,剛剛new的)
IdleState idle = new IdleState(0, this, fsmManager);
RunState run = new RunState(1, this, fsmManager);
AttackState attack = new AttackState(2, this, fsmManager);
//把三種狀態添加到管理類列表
fsmManager.stateList.Add(idle);
fsmManager.stateList.Add(run);
fsmManager.stateList.Add(attack);
//管理類默認切換成站立狀態
fsmManager.ChangeState((int)PlayerState.idle);
}
void Update()
{
//調用管理類裏的Update
//因爲FSMManager裏的Update方法不會自己運行,此處,幫助它運行。
//進而,它就會調用當前狀態裏的OnUpdate。
fsmManager.Update();
}
}