推薦閱讀
- CSDN主頁
- GitHub開源地址
- Unity3D插件分享
- 簡書地址
- 我的個人博客
- QQ群:1040082875
一、前言
Hello,大家好,我是☆恬靜的小魔龍☆,正所謂學而不思則罔,思而不學則殆,最近專案開發中,人物的動作特別多,用狀態機去切換感覺太麻煩,然后切換的效果也并不理想,
比如下面的狀態機:

每次“走→站立→跑”,都一些卡頓,沒有那么絲滑,所以就想學習一下FSM(有限狀態機),
二、有限狀態機
什么是有限狀態機:
如其名有限狀態機,就是可以列舉出有限個狀態,然后狀態直接可以進行切換的狀態機,總體來說,有限狀態機就是在不同階段呈現出不同的運行狀態的系統,這些狀態是有限的、不重疊的,這樣的系統在某一時刻回應其狀態中的一個狀態,
為啥需要有限狀態機:
在開發中,一個角色或者AI會有很多的狀態,也會產生很多的影片,比如攻擊、移動、巡邏、追擊、逃跑、死完,這些影片狀態會不停地切換,簡單的方式就是使用Switch進行判斷,然后每一個case去進行相應的狀態處理和轉換條件判斷,
這種方式有點事簡單方便,缺點就是不利于擴展,所以就出現了有限狀態機去管理,
總結來說就是,為了解決游戲過于麻煩的狀態轉換
有限狀態機與設計模式
細節依賴抽象,抽象不依賴細節,基于抽象編程,讓框架先跑起來,
有限狀態機參考的是狀態設計模式的理念,允許物件在內部狀態發生改變時改變它的行為,物件看起來好像修改了它的類,類的行為是基于它的狀態改變的,
有限狀態機,類比于Switch就是將cast中的邏輯封裝到各個State物件中了,這樣做可以方便擴展,后期如果需要增加新狀態,只需要繼承基類,添加實作就好,不用修改原來的代碼,這就是開閉原則,
源工程下載:
https://download.csdn.net/download/q764424567/20600344
三、有限狀態機的實作
3-1、有限狀態機基類
首先,我們需要一個基類,讓其他的State物件進行繼承:
/// <summary>
/// 狀態抽象類
/// </summary>
public abstract class FSMState
{
// 所屬的狀態機
protected FSMSystem m_FSM = null;
protected FSMState(FSMSystem fsm)
{
m_FSM = fsm;
}
/// <summary>
/// 進入狀態
/// </summary>
public virtual void OnEnter() { }
/// <summary>
/// 狀態中進行的動作
/// </summary>
public abstract void Action();
/// <summary>
/// 檢測狀態轉換
/// </summary>
public abstract void Check();
/// <summary>
/// 退出狀態
/// </summary>
public virtual void OnExit() { }
}
3-2、有限狀態機系統
接著,需要一個有限狀態機系統去管理控制這些狀態機,包含添加狀態、洗掉狀態、改變狀態、狀態改變等,
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 有限狀態機系統
/// </summary>
public class FSMSystem
{
/// <summary>
/// 狀態機所屬游戲物體
/// </summary>
public GameObject OwnerGo { get; private set; }
public Animation Ani { get; private set; }
// 用字典存盤每個狀態ID對應的狀態
private Dictionary<StateID, FSMState> m_StateMap = new Dictionary<StateID, FSMState>();
public FSMSystem(GameObject ownerGo)
{
OwnerGo = ownerGo;
Ani = OwnerGo.GetComponent<Animation>();
}
/// <summary>
/// 當前狀態ID
/// </summary>
public StateID CurrentStateID { get; private set; }
/// <summary>
/// 當前狀態
/// </summary>
public FSMState CurrentState { get; private set; }
/// <summary>
/// 添加狀態
/// </summary>
/// <param name="id">添加的狀態ID</param>
/// <param name="state">對應的狀態物件</param>
public void AddState(StateID id, FSMState state)
{
// 如果當前狀態為空,就設定為默認狀態
if (CurrentState == null)
{
CurrentStateID = id;
CurrentState = state;
}
if (m_StateMap.ContainsKey(id))
{
Debug.LogErrorFormat("狀態ID:{0}已經存在,不能重復添加!", id);
return;
}
m_StateMap.Add(id, state);
}
/// <summary>
/// 移除狀態
/// </summary>
/// <param name="id">要移除的狀態ID</param>
public void RemoveState(StateID id)
{
if (!m_StateMap.ContainsKey(id))
{
Debug.LogWarningFormat("狀態ID:{0}不存在,不需要移除", id);
return;
}
m_StateMap.Remove(id);
}
/// <summary>
/// 改變狀態
/// </summary>
/// <param name="id">需要轉換到的目標狀態ID</param>
public void ChangeState(StateID id)
{
if (id == CurrentStateID) return;
if (!m_StateMap.ContainsKey(id))
{
Debug.LogErrorFormat("狀態ID:{0}不存在!", id);
return;
}
if (CurrentState != null)
CurrentState.OnExit();
CurrentStateID = id;
CurrentState = m_StateMap[id];
CurrentState.OnEnter();
}
/// <summary>
/// 更新,在狀態機持有者物體的Update中呼叫
/// </summary>
public void Update()
{
CurrentState.Action();
CurrentState.Check();
}
}
3-3、添加狀態
這時候,就需要看一下我們的模型有多少個影片了,比如我這個模型:

共12個影片片段,但是我只需要里面的9個影片片段,所以先定義一個列舉,將所有用到的影片片段列舉出來:
/// <summary>
/// 狀態ID
/// </summary>
public enum StateID
{
Stand,
Standwait,
Walk,
Jump1,
Run,
Jump2,
Down,
Take,
Work
}
接著就添加狀態:
/// <summary>
/// 站立
/// </summary>
public class StandState : FSMState
{
private float m_Timer = 0f;
public StandState(FSMSystem fsm) : base(fsm)
{
Debug.Log("StandState");
}
public override void Action()
{
m_FSM.Ani.Play("Stand");
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果旋轉達到1秒
if (m_Timer > 0.25f)
{
// 轉換到移動狀態
m_FSM.ChangeState(StateID.Standwait);
}
}
public override void OnExit()
{
m_Timer = 0;
}
}
/// <summary>
/// 站立等待
/// </summary>
public class StandWaitState : FSMState
{
public StandWaitState(FSMSystem fsm) : base(fsm)
{
Debug.Log("StandWaitState");
}
public override void Action()
{
m_FSM.Ani.Play("Stand-wait");
}
public override void Check() { }
}
/// <summary>
/// 走
/// </summary>
public class WalkState : FSMState
{
public WalkState(FSMSystem fsm) : base(fsm)
{
Debug.Log("WalkState");
}
public override void Action()
{
m_FSM.Ani.Play("walk");
}
public override void Check() { }
}
/// <summary>
/// 小跳
/// </summary>
public class Jump1State : FSMState
{
private float m_Timer = 0f;
public Jump1State(FSMSystem fsm) : base(fsm)
{
Debug.Log("Jump1State");
}
public override void Action()
{
m_FSM.Ani.Play("jump1");
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果旋轉達到1秒
if (m_Timer > 0.6f)
{
// 轉換到移動狀態
m_FSM.ChangeState(StateID.Standwait);
}
}
public override void OnExit()
{
m_Timer = 0;
}
}
/// <summary>
/// 跑
/// </summary>
public class Run2State : FSMState
{
public Run2State(FSMSystem fsm) : base(fsm)
{
Debug.Log("Run2State");
}
public override void Action()
{
m_FSM.Ani.Play("run");
}
public override void Check() { }
}
/// <summary>
/// 大跳
/// </summary>
public class Jump2State : FSMState
{
private float m_Timer = 0f;
public Jump2State(FSMSystem fsm) : base(fsm)
{
Debug.Log("Jump2State");
}
public override void Action()
{
m_FSM.Ani.Play("jump2");
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果旋轉達到1秒
if (m_Timer > 0.75f)
{
// 轉換到移動狀態
m_FSM.ChangeState(StateID.Standwait);
}
}
public override void OnExit()
{
m_Timer = 0;
}
}
/// <summary>
/// 拿取
/// </summary>
public class TakeState : FSMState
{
public TakeState(FSMSystem fsm) : base(fsm)
{
Debug.Log("TakeState");
}
public override void OnEnter()
{
m_FSM.Ani.Play("take");
}
public override void Action() { }
public override void Check() { }
public override void OnExit() { }
}
/// <summary>
/// 作業
/// </summary>
public class WorkState : FSMState
{
public WorkState(FSMSystem fsm) : base(fsm)
{
Debug.Log("WorkState");
}
public override void OnEnter()
{
m_FSM.Ani.Play("work");
}
public override void Action() { }
public override void Check() { }
public override void OnExit() { }
}
/// <summary>
/// 蹲
/// </summary>
public class DownState : FSMState
{
public DownState(FSMSystem fsm) : base(fsm)
{
Debug.Log("DownState");
}
public override void OnEnter()
{
m_FSM.Ani.Play("Down");
}
public override void Action() { }
public override void Check() { }
public override void OnExit() { }
}
我為了方便,將有限狀態機的基類、狀態列舉、狀態物件都寫到一個腳本里面了,大家覺得不爽也可以分開來,
3-4、呼叫影片
新建一個Character類,用來呼叫影片:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Character : MonoBehaviour
{
// 有限狀態機
private FSMSystem m_FSM = null;
private void Start()
{
// 實體化有限狀態機
m_FSM = new FSMSystem(this.gameObject);
// 添加狀態到有限狀態機中
m_FSM.AddState(StateID.Stand, new StandState(m_FSM));
m_FSM.AddState(StateID.Standwait, new StandWaitState(m_FSM));
m_FSM.AddState(StateID.Walk, new WalkState(m_FSM));
m_FSM.AddState(StateID.Jump1, new Jump1State(m_FSM));
m_FSM.AddState(StateID.Run, new Run2State(m_FSM));
m_FSM.AddState(StateID.Jump2, new Jump2State(m_FSM));
m_FSM.AddState(StateID.Down, new DownState(m_FSM));
m_FSM.AddState(StateID.Take, new TakeState(m_FSM));
m_FSM.AddState(StateID.Work, new WorkState(m_FSM));
}
private void Update()
{
// 呼叫有限狀態機中的Update方法
if (m_FSM != null)
m_FSM.Update();
if (Input.GetKey(KeyCode.Alpha1))
{
m_FSM.ChangeState(StateID.Stand);
}
if (Input.GetKey(KeyCode.Alpha2))
{
m_FSM.ChangeState(StateID.Standwait);
}
if (Input.GetKey(KeyCode.Alpha3))
{
m_FSM.ChangeState(StateID.Walk);
}
if (Input.GetKey(KeyCode.Alpha4))
{
m_FSM.ChangeState(StateID.Jump1);
}
if (Input.GetKey(KeyCode.Alpha5))
{
m_FSM.ChangeState(StateID.Run);
}
if (Input.GetKey(KeyCode.Alpha6))
{
m_FSM.ChangeState(StateID.Jump2);
}
if (Input.GetKey(KeyCode.Alpha7))
{
m_FSM.ChangeState(StateID.Down);
}
if (Input.GetKey(KeyCode.Alpha8))
{
m_FSM.ChangeState(StateID.Take);
}
if (Input.GetKey(KeyCode.Alpha9))
{
m_FSM.ChangeState(StateID.Work);
}
}
}
掛載到物件上:

效果圖:

四、后言
這個例子沒有太多炫技的代碼,作為一個學習有限狀態機的入門例子來看還是很不錯的,
希望大家今天也有學習哦,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/291125.html
標籤:其他
上一篇:基于EasyX的推箱子游戲
下一篇:python實作21根火柴游戲
