FSM狀態(tài)機以及超級控制器API的使用

在上一篇文章留了一個FSM有限自動機狀態(tài)機的坑惭嚣,在這里填上必尼,對這部分不感興趣的可以直接看后面的API和使用方法

FSM有限自動狀態(tài)機

簡單來說狀態(tài)機是一種維護狀態(tài)遷移的精簡并邏輯性很強的模式窜醉,在游戲開發(fā)中被廣泛運用在AI邏輯和狀態(tài)變化中涎永,設想一種情況桑涎,我們需要維護角色運動狀態(tài)的變化倔叼,如行走竭宰,跳躍空郊,掉落等等份招,一種可行的方式是用一些if,else語句,如這種在某些教程中經常出現(xiàn)的代碼:

if(Input.GetKeyDown(KeyCode.W))
{
     transform.positon += new Vector3(0,0,1); 
}
if(Input.GetKeyDown(KeyCode.S))
{
     transform.positon += new Vector3(0,0,-1); 
}

這樣的代碼在處理單純的行走時是可行的狞甚,但是當動作增多锁摔、邏輯增多以后這樣的代碼變得不可行,就算只有行走哼审、跳躍谐腰、掉落這三種動作,帶來的邏輯也是相當多的涩盾,比如角色在輸入跳躍后角色將跳躍十气,角色在空中時不可再跳躍,跳躍著地以后才能繼續(xù)行走等等等
為了解決大量的狀態(tài)變化春霍,我們引入了狀態(tài)機
事實上我相信每個Unity開發(fā)人員都接觸過狀態(tài)機砸西,Unity編輯器中對動畫的處理就是采用狀態(tài)機的(5.x版本以后)


一個狀態(tài)機的例子

這張圖很好的說明了狀態(tài)機在做什么,其實就是在管理狀態(tài)以及狀態(tài)之間的遷移址儒,Idle狀態(tài)在輸入前進方向后會遷移到Walk狀態(tài)芹枷,而Walk狀態(tài)在脫離地面則會進入Fall狀態(tài)等等
有兩條原則是確保狀態(tài)機可以簡單實現(xiàn)的:

  • 每一時刻狀態(tài)機的當前狀態(tài)只能是確定的一種狀態(tài)
  • 對當前狀態(tài)進行遷移時必須遷移至確定的狀態(tài)
    下面是實現(xiàn)的設計架構:



    即一個StateMachine類控制狀態(tài),一個State的基類表示狀態(tài)莲趣,所有具體的狀態(tài)繼承自這個基類

使用方法

添加動作

  • 在StateMachineEnum腳本中為StateID枚舉類添加新的狀態(tài)項

  • 在CharacterStateMachine腳本中添加自定義狀態(tài)類并繼承FSMState

    public class CharacterIdelState : FSMState{
      public CharacterIdleState()
      {
         stateID = StateID.CharacterIdle;
      }
      public override void Reason()
      {
          
      }
    
      public override void Act()
      {
          
      }
    }
    

    其中需要重載構造函數(shù)杖狼、Reason函數(shù)、Act函數(shù)妖爷,構造函數(shù)中為stateID賦值為自定義添加的狀態(tài)值

    Reason函數(shù)代表狀態(tài)經過特定的事件會發(fā)生遷移

    Act函數(shù)代表該狀態(tài)下執(zhí)行的行為

  • 在CharacterStateMachine類的構造函數(shù)中執(zhí)行AddState()函數(shù)蝶涩,參數(shù)為新建狀態(tài)的實例

    AddState(new CharacterIdleState());

  • 同樣在SuperMachineEnum類中Transition枚舉類中添加自定義的狀態(tài)遷移

  • 在自定義狀態(tài)的類的初始化函數(shù)中添加AddTransition(),參數(shù)分別為狀態(tài)遷移值和目標狀態(tài)值

API

SuperCharacterController:

  • EnableClamping():使角色吸附到地面

  • DisableClamping():使角色不吸附地面

  • EnableSlopeLimit():使角色計算坡度限制

  • DisableSlopeLimit():使角色不計算坡度限制

  • IsClamping():返回角色是否吸附地面

  • MoveHorizontal(Vector2 direction,float speed,float WalkAcceleration):朝一個方向按一定速度移動(以一定加速度加速絮识,方向為相對于角色的方向)

  • MoveVertical(float Acceleration,float finalSpeed):在角色垂直值按一定最終速度進行加速移動

  • Ronate(Quaternion target,float maxDelta):以給定的角速度旋轉到目標方向(四元數(shù)表示)

  • GetRight():獲得右方向

  • GetForword():獲得前方向

  • GetUp():獲得上方向

  • AcquiringGround():返回是否接觸地面

  • MaintainingGround():返回是否接近地面

  • PointBelowHead(Vector3 point):返回點是否在頭部以下

  • PointAboveFeet(Vector3 point):返回點是否在腳以下

  • MoveToTarget(Vector3 target):將角色移動到目標位置

在跳躍和降落狀態(tài)前記得應用

    controller.DisableClamping();
    controller.DisableSlopeLimit();

FSM:

FSMSystem:

  • AddState(FSMState s):為狀態(tài)機添加一個新的狀態(tài)
  • DeleteState(StateID id):刪除StateId為id的狀態(tài)
  • PerformTransition(Transition trans):嘗試對當前狀態(tài)執(zhí)行trans的狀態(tài)遷移

FSMState:

  • AddTransition(Transition trans, StateID id):為狀態(tài)添加一個trans狀態(tài)遷移绿聘,目標狀態(tài)的StateID為id
  • DeleteTransition(Transition trans):刪除trans的狀態(tài)遷移
  • GetOutputState(Transition trans):獲得trans狀態(tài)遷移的目標狀態(tài)
  • DoBeforeEntering():重載,在進入狀態(tài)前執(zhí)行
  • DoBeforeLeaving():重載次舌,在狀態(tài)轉移前執(zhí)行
  • Reason():必需重載熄攘,代表狀態(tài)何時遷移的代碼
  • Act():必需重載,代表狀態(tài)執(zhí)行的邏輯代碼

使用

  FSMSystem fsm = new FSMSystem();
  void Update()
  {
      fsm.CurrentState.Reason();
      fsm.CurrentState.Act();
  }

示例

下面我拿Walk狀態(tài)舉例

public class CharacterStateMachine : FSMSystem {
public CharacterStateMachine(SuperCharacterController controller)
{
    AddState(new CharacterIdleState(controller,this));
    AddState(new CharacterWalkState(controller, this));
    AddState(new CharacterJumpState(controller, this));
    AddState(new CharacterFallState(controller, this));
}
}

首先我們需要定義一個類來繼承FSMSystem類彼念,并定義構造函數(shù)挪圾,在構造函數(shù)中添加所有我們需要的狀態(tài)

public class CharacterWalkState : FSMState
{
public SuperCharacterController controller;
public CharacterWalkState(SuperCharacterController c, FSMSystem f)
{
    fsm = f;
    controller = c;
    stateID = StateID.CharacterWalk;
    AddTransition(Transition.CharacterWalkToIdle, StateID.CharacterIdle);
    AddTransition(Transition.CharacterJump, StateID.CharacterJump);
    AddTransition(Transition.CharacterFall, StateID.CharacterFall);
}

public override void Reason()
{
    if (!controller.MaintainingGround())
    {
        fsm.PerformTransition(Transition.CharacterFall);
    }
    if (InputController.GetKey<bool>("Jump"))
    {
        fsm.PerformTransition(Transition.CharacterJump);
    }
    if (InputController.GetKey<Vector2>("inputV").magnitude <= 0.1f)
    {
        fsm.PerformTransition(Transition.CharacterWalkToIdle);
    }
}

public override void Act()
{
    
    float walkSpeed = 5;
    float walkAcc = 1;
    float angleDelta = 30;
    Vector2 inputV = InputController.GetKey<Vector2>("inputV");
    Transform camera = Camera.main.transform;//模擬照相機
    Vector3 screenForword = controller.transform.position - camera.position;
    Vector3 pScreenForword = Math3d.ProjectVectorOnPlane(controller.up, screenForword);
    Quaternion inputQua = Quaternion.FromToRotation(new Vector3(0,0,1), new Vector3(inputV.x, 0, inputV.y));
    if(inputV == new Vector2(0,-1))
    {
        inputQua = Quaternion.AngleAxis(180, controller.up);
    }
    Vector3 target = inputQua * pScreenForword;
    controller.Ronate(Quaternion.FromToRotation(Vector3.forward, target), angleDelta);
    Vector2 direction = new Vector2(0, 1);
    controller.MoveHorizontal(direction, walkSpeed, walkAcc);
}
public override void DoBeforeEntering()
{
    controller.EnableClamping();
    controller.EnableSlopeLimit();
}
}

接下來定義一個Walk類繼承自FSMState類,在構造函數(shù)中使用AddTransiton方法將所有可能的狀態(tài)遷移添加進去逐沙。
重載Reason和Act函數(shù)哲思,這兩個函數(shù)分別代表狀態(tài)在經歷什么樣的事件會進行狀態(tài)遷移以及在這個狀態(tài)下會執(zhí)行什么樣的邏輯代碼

public override void Reason()
{
    if (!controller.MaintainingGround())
    {
        fsm.PerformTransition(Transition.CharacterFall);
    }
    if (InputController.GetKey<bool>("Jump"))
    {
        fsm.PerformTransition(Transition.CharacterJump);
    }
    if (InputController.GetKey<Vector2>("inputV").magnitude <= 0.1f)
    {
        fsm.PerformTransition(Transition.CharacterWalkToIdle);
    }
}

Reason函數(shù)的邏輯很簡單,如果我們的角色脫離了地面將進入Fall狀態(tài)吩案,如果輸入了跳躍動作將進入跳躍狀態(tài)棚赔,如果輸入的行走向量長度過小將進入靜止狀態(tài)

public override void Act()
{
    
    float walkSpeed = 5;
    float walkAcc = 1;
    float angleDelta = 30;
    Vector2 inputV = InputController.GetKey<Vector2>("inputV");
    Transform camera = Camera.main.transform;//模擬照相機
    Vector3 screenForword = controller.transform.position - camera.position;
    Vector3 pScreenForword = Math3d.ProjectVectorOnPlane(controller.up, screenForword);
    Quaternion inputQua = Quaternion.FromToRotation(new Vector3(0,0,1), new Vector3(inputV.x, 0, inputV.y));
    if(inputV == new Vector2(0,-1))
    {
        inputQua = Quaternion.AngleAxis(180, controller.up);
    }
    Vector3 target = inputQua * pScreenForword;
    controller.Ronate(Quaternion.FromToRotation(Vector3.forward, target), angleDelta);
    Vector2 direction = new Vector2(0, 1);
    controller.MoveHorizontal(direction, walkSpeed, walkAcc);
}
public override void DoBeforeEntering()
{
    controller.EnableClamping();
    controller.EnableSlopeLimit();
}
}

旋轉

Act函數(shù)中執(zhí)行了使角色移動的邏輯代碼,我采用了模擬搖桿的方式,首先計算出從向量(0靠益,0丧肴,1)到輸入的搖桿向量的旋轉變化值inputQua(對四元數(shù)不太清楚的讀者可以暫時跳過),然后通過inputQua * pScreenForword計算要旋轉到的目的方向(pScreenForword表示從攝像機位置到角色位置的向量在xz平面上的投影胧后,如果角色處在攝像機中心位置的話芋浮,這樣的旋轉方式是很人性化的),最后使用Quaternion.FromToRotation(Vector3.forward, target)計算出這一旋轉對應的四元數(shù)值壳快,并調用Ronate方法進行旋轉

移動

移動要簡單得多纸巷,就是以一定速度和加速度調用MoveHorizontal方法使角色朝前方移動
最后的DoBeforeEntering即是字面理解的意思,這里使角色啟用附著地面和坡度限制

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末濒憋,一起剝皮案震驚了整個濱河市何暇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凛驮,老刑警劉巖裆站,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異黔夭,居然都是意外死亡宏胯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門本姥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肩袍,“玉大人,你說我怎么就攤上這事婚惫》沾停” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵先舷,是天一觀的道長艰管。 經常有香客問我,道長蒋川,這世上最難降的妖魔是什么牲芋? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮捺球,結果婚禮上缸浦,老公的妹妹穿的比我還像新娘。我一直安慰自己氮兵,他們只是感情好裂逐,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胆剧,像睡著了一般絮姆。 火紅的嫁衣襯著肌膚如雪醉冤。 梳的紋絲不亂的頭發(fā)上秩霍,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天篙悯,我揣著相機與錄音,去河邊找鬼铃绒。 笑死鸽照,一個胖子當著我的面吹牛,可吹牛的內容都是我干的颠悬。 我是一名探鬼主播矮燎,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赔癌!你這毒婦竟也來了诞外?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤灾票,失蹤者是張志新(化名)和其女友劉穎峡谊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊苍,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡既们,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了正什。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啥纸。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖婴氮,靈堂內的尸體忽然破棺而出斯棒,到底是詐尸還是另有隱情,我是刑警寧澤主经,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布荣暮,位于F島的核電站,受9級特大地震影響旨怠,放射性物質發(fā)生泄漏渠驼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一鉴腻、第九天 我趴在偏房一處隱蔽的房頂上張望迷扇。 院中可真熱鬧,春花似錦爽哎、人聲如沸蜓席。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厨内。三九已至祈秕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雏胃,已是汗流浹背请毛。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞭亮,地道東北人方仿。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像统翩,于是被迫代替她去往敵國和親仙蚜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容