前言:狀態機模式是一個遊戲經常使用的經典設計模式,常被用做管理一種物體的各類狀態(例如管理人物的行走,站立,跳躍等狀態)。html
(Unity裏的Animator就是一種典型的狀態機,用於控制動畫狀態之間的切換)設計模式
假如咱們正在開發一款動做遊戲,當前的任務是實現根據輸入來控制主角的行爲——當按下B鍵時,他應該跳躍。ide
直觀的代碼:動畫
if (input == PRESS_B) { if (!m_isJumping) { m_isJumping = true; Jump();//跳躍的代碼 } }
後來咱們須要添加更多行爲了,全部行爲以下:ui
站立時按下 ↓ 鍵 =》 蹲下。this
蹲下時按下 ↓ 鍵 =》 站立。spa
站立時按下 B 鍵 =》 跳躍。設計
跳躍時按下 ↓ 鍵 =》觸發 俯衝。3d
if (input == PRESS_B) { //若是在站立時且沒在跳躍,則跳躍 if (!m_isJumping && m_isStanding) { m_isJumping = true; player.jump();//跳躍的代碼 } } else if (input == PRESS_DOWN) { //若是在跳躍時且沒在俯衝,則俯衝 if (m_isJumping && !m_isDiving) { m_isDiving player.dive();//俯衝的代碼 } //若是沒在跳躍 else if (!m_isJumping) { //若是站立時,則蹲下 if (m_isStanding) { m_isStanding = false; player.sneak();//蹲下的代碼 } //若是蹲下時,則站立 else { m_isStanding = true; player.stand();//站立的代碼 } } }
能夠看到一堆if-else語句很是複雜,要是添加更多行爲,其邏輯結構更加難以維護,並且主角的代碼又得從新編譯(耦合性大)code
有限狀態:有限數量的狀態。
一個可行的辦法是將這些 狀態&狀態切換&狀態對應的行爲 封裝成類,
(以下圖)
這時候能夠藉助狀態機這個設計模式來美化這段代碼。
上面的場景中,只有4個狀態(跳躍/下蹲/站立/俯衝),這就是有限狀態。
因而咱們設計出下面4個狀態類(加一個狀態的接口類):
//狀態接口類 class State { public: //處理輸入,而後根據輸入轉換相應的狀態 virtual void handleInput(Player& player,const Input& input) = 0; }; //站立狀態 class StandState : public State { public: void handleInput(Player& player, const Input& input) override{ if (input == PRESS_B) { player.jump();//角色跳躍的代碼 player.setState(JumpState()); } else if (input == PRESS_DOWN) { player.sneak();//角色蹲下的代碼 player.setState(SneakState()); } } }; //跳躍狀態 class JumpState : public State { public: void handleInput(Player& player, const Input& input) override { if (input == PRESS_DOWN) { player.dive();//角色俯衝的代碼 player.setState(DiveState()); } } }; //下蹲狀態 class SneakState : public State { public: void handleInput(Player& player, const Input& input) override { if (input == PRESS_DOWN) { player.stand();//角色站立的代碼 player.setState(StandState()); } } }; //俯衝狀態 class DiveState : public State { public: void handleInput(Player& player, const Input& input) override { } };
第一次進入遊戲時,給角色一個初始狀態
player.setState(new StandState());
而後每次接受輸入,讓角色當前的狀態對象去處理就能夠了。
player.getState().handleInput(player,input);
簡單小結:
能夠看到利用狀態類對象,咱們把負責的條件邏輯封裝到各個狀態類裏,讓代碼變得優雅,並且還減小了幾個變量的使用(m_isJumping等)。
此外因爲有限狀態對象的屬性是固定不變的,這意味着全部角色都能共享同一個狀態(當同種狀態時),
因此常見的狀態對象存儲方式是單例存儲或者靜態存儲(每種狀態只生成1個對象),避免了上文每次都要生成新狀態對象的開銷。
實際中,一些遊戲的類可能須要多個狀態(平行關係),因而能夠寫出如下代碼
class Player{ State* m_bodyState;//身體狀態 State* m_equipmentState;//裝備狀態 //.....其它代碼 };
而後即可以用下列方式處理狀態了
void Player::handleInput(const Input& input) { m_bodyState->handleInput(*this,input); m_equipmentState->handleInput(*this,input); }
把主角的行爲更加具象化之後,可能會包含大量類似的狀態,爲了重用代碼,便衍生層次狀態機的概念。
層次狀態主要思想是狀態類繼承,從而產生層次關係的狀態。
例如,蹲下狀態和站立狀態 繼承於 在地面狀態。
class OnGroundState : public State { void handleInput(Player& player, const Input& input) override { if (input == PRESS_B){}//....跳躍 } }; class StandState : public OnGroundState { void handleInput(Player& player, const Input& input) override { //當鬆開↓鍵,才蹲下去 if (input == RELEASE_DOWN) {}//...蹲下去的代碼... else {OnGroundState::handleInput(player,input);} } }; class SneakState : public OnGroundState { void handleInput(Player& player, const Input& input) override { //當鬆開↓鍵,才站起來 if (input == RELEASE_DOWN) {}//...站起來的代碼... else {OnGroundState::handleInput(player, input);} } };
下推狀態機,簡單來講,就是用棧結構存儲一系列狀態對象。
通常來講,一個角色只須要一個狀態對象,爲何要用棧結構存儲一堆狀態對象?
假設有一個射擊遊戲的角色,他現正在站立狀態,執行棧頂狀態中。
忽然遇到敵人進行開火,因而入棧一個開火狀態,並繼續執行新的棧頂狀態。
敵人被擊中死亡,開火狀態結束。爲了恢復到開火前的上一個狀態,因而去掉棧頂狀態。
這樣咱們利用棧就完美模擬了一我的開火以後恢復成站立狀態的過程。
簡單來講,
下推自動機適用於須要記憶狀態的狀態機,這在一些遊戲AI是經常使用的手法。(不過如今更流行的遊戲AI是用行爲樹實現)
遊戲設計模式系列-其餘文章: