日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

游戏编程设计模式-state

發布時間:2024/8/26 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 游戏编程设计模式-state 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

認錯時刻:在這一章里,我有的包裝和修飾有點過分了。看起來是在講狀態模式,但是我發現如果不講有限狀態機的基本概念,我幾乎沒法討論狀態模式和游戲。但是一旦我講了,感覺就像是在介紹分層狀態機和下推自動機。

這樣牽扯的太多了,所以為了盡量保持簡潔,實例代碼留下了一些細節需要你自己去填寫。我希望他們仍然能夠表達清楚大體意思。

如果你沒聽說過狀態機也不要慌。雖然在AI和編譯器黑客們那里很常見,但是其他領域的程序員可能不熟悉。我想他們應該被廣泛認知,所以我將在不同的問題中拋出他們。

其實我們早就知道

我們在做一個橫向卷軸的小游戲。我們的工作是實現一個女英雄(heroine),就是玩家在游戲世界的阿凡達。也就是說她要相應玩家的輸入。按B鍵她就會跳。很簡單:

  • void Heroine::handleInput(Input input){
  • ? ? if (input == PRESS_B){
  • ? ?? ???yVelocity_ = JUMP_VELOCITY;
  • ? ?? ???setGraphics(IMAGE_JUMP);
  • ? ? }
  • }
  • 復制代碼


    出Bug了?

    沒有東西能夠組織“跳躍”動作——當她在空中的時候持續按B鍵,她就永遠懸在空中了。簡單地解決辦法是為Heroine類添加一個isJumping_布爾值,當她起跳的時候設為true,然后:

  • void Heroine::handleInput(Input input){
  • ? ? if (input == PRESS_B){
  • ? ?? ???if (!isJumping_){
  • ? ?? ?? ?? ?isJumping_ = true;
  • ? ?? ?? ?? ?// Jump...
  • ? ?? ???}
  • ? ? }
  • }
  • 復制代碼


    然后,當她站立時,一旦玩家按向下鍵,我們想讓heroine下水。放開按鍵后回到站立狀態。

  • void Heroine::handleInput(Input input){
  • ? ? if (input == PRESS_B){
  • ? ?? ???// Jump if not jumping...
  • ? ? }else if (input == PRESS_DOWN){
  • ? ?? ???if (!isJumping_){
  • ? ?? ?? ?? ?setGraphics(IMAGE_DUCK);
  • ? ?? ???}
  • ? ? }else if (input == RELEASE_DOWN){
  • ? ?? ???setGraphics(IMAGE_STAND);
  • ? ? }
  • }
  • 復制代碼


    仍然有bug?

    這些代碼,玩家可以:

    1、按向下鍵入水。
    2、按B鍵從潛水的位置上起跳。
    3、在空中釋放向下鍵。

    heroine就會在跳躍中間變成站立樣子。應該加入另一個標志了:

  • void Heroine::handleInput(Input input){
  • ? ? if (input == PRESS_B){
  • ? ?? ???if (!isJumping_ && !isDucking_){
  • ? ?? ?? ?? ?// Jump...
  • ? ?? ???}
  • ? ? }else if (input == PRESS_DOWN){
  • ? ?? ???if (!isJumping_){
  • ? ?? ?? ?? ?isDucking_ = true;
  • ? ?? ?? ?? ?setGraphics(IMAGE_DUCK);
  • ? ?? ???}
  • ? ? }else if (input == RELEASE_DOWN){
  • ? ?? ???if (isDucking_){
  • ? ?? ?? ?? ?isDucking_ = false;
  • ? ?? ?? ?? ?setGraphics(IMAGE_STAND);
  • ? ?? ???}
  • ? ? }
  • }
  • 復制代碼


    下一步,如果玩家在跳躍的中間按向下鍵,進行一個俯沖攻擊,那一定很酷:

  • void Heroine::handleInput(Input input){
  • ? ? if (input == PRESS_B){
  • ? ?? ???if (!isJumping_ && !isDucking_){
  • ? ?? ?? ?? ?// Jump...
  • ? ?? ???}
  • ? ? }else if (input == PRESS_DOWN){
  • ? ?? ???if (!isJumping_){
  • ? ?? ?? ?? ?isDucking_ = true;
  • ? ?? ?? ?? ?setGraphics(IMAGE_DUCK);
  • ? ?? ???}else{
  • ? ?? ?? ?? ?isJumping_ = false;
  • ? ?? ?? ?? ?setGraphics(IMAGE_DIVE);
  • ? ?? ???}
  • ? ? }else if (input == RELEASE_DOWN){
  • ? ?? ???if (isDucking_){
  • ? ?? ?? ?? ?// Stand...
  • ? ?? ???}
  • ? ? }
  • }
  • 復制代碼


    又到了找bug時間了。

    我們發現你再跳躍時不能再次跳躍,但是在俯沖和其他狀態下可以…

    通過我們的方法,能找到一些明顯的錯誤。每次我們動一動這些棘手的代碼,都會引起一些錯誤。我們需要加入更多地運動——我們甚至還沒有加入“走路”——但是照這樣下去,在我們處理它之前,就會引起一系列的bug。

    有限狀態機救駕

    面對這種挫折,你要清理掉桌面上的所有東西,只剩下一支筆和一張紙,開始畫流程圖。你畫一個矩形代表heroine可以做的每一個動作:站立、跳躍、下潛和俯沖。當她能從一種狀態響應一個按鍵消息,你畫一個從這個矩形出發的箭頭,并且用這個按鈕來標記箭頭,用它來指向下一個狀態。
    ?


    恭喜,你已經創建了一個有限狀態機(FSM)。這出自計算機科學的一個叫做自動機理論的分支。這個家族里里還有大名鼎鼎的圖靈機。FSM是這個家族最簡單地一個成員。要點如下:

    1、狀態機的狀態集合是確定的。例如在我們的例子里,有站立、跳躍、下蹲和俯沖。

    2、狀態機在某一時刻只能處于一種狀態。我們的heroine不能同時跳躍和站立。其實,避免出現這種狀況正是我們使用FSM的原因。

    3、一連串的輸入或者事件發送到這個狀態機。我們的例子里,就是一串按鍵和釋放消息。

    4、每一種狀態都由一些轉化路徑,每一個路徑都通過一個輸入關聯到另外一種狀態。當一個輸入進來后,如果正好跟一個當前的轉化路徑匹配,狀態機就會轉到它所指向的另一個狀態。

    例如,站立的時候按向下鍵,轉換到下蹲狀態,跳躍的時候按向下鍵,轉換到俯沖狀態。如果當前狀態下沒有定義某種操作相應的轉化,那這種操作將會被忽略。

    理想狀態下,狀態機里就只有:狀態,輸入,轉化這些東西了。你可以畫一個很小的圖表示出來。但是編譯器并不能識別我們的草圖,我們要怎么實現他們呢?GOF的State模式就是一種方法,下面將會講到,讓我從簡單的入手。

    枚舉與開關

    我們的Heroine類有一個問題就是無法使用這幾個布爾值得組合,例如isJumping_和isDucking_永遠不可能都為true。當你確定這些標志不可能同時為true時,這就表示你真正應該使用的是枚舉。

    在這個例子中,枚舉就是我們FSM的狀態集合,所以我們可以這樣定義:

  • enum State{
  • ? ? STATE_STANDING,
  • ? ? STATE_JUMPING,
  • ? ? STATE_DUCKING,
  • ? ? STATE_DIVING
  • };
  • 復制代碼


    Heroine類不再需要一堆標志了,取而代之的是一個state_屬性。并且需要顛倒代碼的順序。在前面的代碼里,我們先判斷輸入,然后是狀態。這把一個鍵的相關代碼放在了一起,但是關于狀態的代碼被分散了。我們希望讓它們集中起來,所以先判斷狀態,代碼如下:

  • void Heroine::handleInput(Input input){
  • ? ? switch (state_){
  • ? ?? ???case STATE_STANDING:
  • ? ?? ?? ?? ?if (input == PRESS_B){
  • ? ?? ?? ?? ?? ? state_ = STATE_JUMPING;
  • ? ?? ?? ?? ?? ? yVelocity_ = JUMP_VELOCITY;
  • ? ?? ?? ?? ?? ? setGraphics(IMAGE_JUMP);
  • ? ?? ?? ?? ?}else if (input == PRESS_DOWN){
  • ? ?? ?? ?? ?? ? state_ = STATE_DUCKING;
  • ? ?? ?? ?? ?? ? setGraphics(IMAGE_DUCK);
  • ? ?? ?? ?? ?}
  • ? ?? ?? ?? ?break;
  • ? ?? ???case STATE_JUMPING:
  • ? ?? ?? ?? ?if (input == PRESS_DOWN){
  • ? ?? ?? ?? ?? ? state_ = STATE_DIVING;
  • ? ?? ?? ?? ?? ? setGraphics(IMAGE_DIVE);
  • ? ?? ?? ?? ?}
  • ? ?? ?? ?? ?break;
  • ? ?? ???case STATE_DUCKING:
  • ? ?? ?? ?? ?if (input == RELEASE_DOWN){
  • ? ?? ?? ?? ?? ? state_ = STATE_STANDING;
  • ? ?? ?? ?? ?? ? setGraphics(IMAGE_STAND);
  • ? ?? ?? ?? ?}
  • ? ?? ?? ?? ?break;
  • ? ?? ???}
  • ? ? }
  • }
  • 復制代碼



    這看起來很散亂,但是其實已經比前面的代碼有很大進步了。我們仍然有很多判斷分支,但是我們把分散的狀態歸納到了一個地方。處理同一個狀態的代碼被很好的集中在一起。這是實現狀態機的最簡單的方式,而且在某些應用場景工作的很好。

    但是你遇到的問題會超出這個解決方案的范圍。比如說手游賬號買號,我們想添加一個改動,讓heroine能夠下蹲蓄力然后發出一個特殊的大招。當她下蹲的時候,我們要記錄時間。

    我們在Heroine中添加一個 chargeTime_屬性,用來記錄她下蹲了多長時間。假如我們有一個update()函數,每幀調用一次。我們在其中加入代碼:

  • void Heroine::update(){
  • ? ? if (state_ == STATE_DUCKING){
  • ? ?? ???chargeTime_++;
  • ? ?? ???if (chargeTime_ > MAX_CHARGE){
  • ? ?? ?? ?? ?superBomb();
  • ? ?? ???}
  • ? ? }
  • }
  • 復制代碼


    我們需要在她開始下蹲前重置這個計時器,所以修改handleInput函數:

  • void Heroine::handleInput(Input input)
  • {
  • ??switch (state_)
  • ??{
  • ? ? case STATE_STANDING:
  • ? ?? ?if (input == PRESS_DOWN)
  • ? ?? ?{
  • ? ?? ???state_ = STATE_DUCKING;
  • ? ?? ???chargeTime_ = 0;
  • ? ?? ???setGraphics(IMAGE_DUCK);
  • ? ?? ?}
  • ? ?? ?// Handle other inputs...
  • ? ?? ?break;
  • ? ?? ?// Other states...
  • ??}
  • }
  • 復制代碼


    總之,為了添加大招,我們必須修改兩個方法,并且在Heroine類中加入chargeTime_屬性。盡管它只在下蹲狀態下有意義。我們最理想的情況是把這些代碼和數據封裝在一起,GOF該出場了。

    State模式

    面向對象思想已經深入人心,每一種判斷分支都提供了一個動態分配的機會(C++中用的是另外一個說法叫虛函數調用)。我想這是個坑,優勢其實你所需要的只是一個if語句。

    但是在我們的實例中,恰好更適合面向對象。它讓我們能夠使用State模式,GOF的描述是:

    允許一個對象在內部狀態改變時,改變其行為。這個對象會表現為改變它的類型。

    這并沒有告訴我們太多信息。搞笑的是swtch卻做到了。這個模式的描述如果應用到我們的heroine上,會是這一個樣子:

    一個state 接口

    首先,我們定義一個state的接口。每一個狀態依賴的方法——之前我們放switch語句的地方——變成了一個虛方法。在這里就是handleInput() 和 update()

  • class HeroineState
  • {
  • public:
  • ??virtual ~HeroineState() {}
  • ??virtual void handleInput(Heroine& heroine, Input input) {}
  • ??virtual void update(Heroine& heroine) {}
  • };
  • 復制代碼


    每一個狀態對應的類

    對每種狀態我們定義一個類去實現這個接口。它的方法定義了heroine在這個狀態下的行為。也就是說,我們把原來swtich語句中每個case下的代碼,移到了他們各自狀態對應的類中。例如:

  • class DuckingState : public HeroineState
  • {
  • public:
  • ??DuckingState()
  • ??: chargeTime_(0)
  • ??{}
  • ??virtual void handleInput(Heroine& heroine, Input input) {
  • ? ? if (input == RELEASE_DOWN)
  • ? ? {
  • ? ?? ?// Change to standing state...
  • ? ?? ?heroine.setGraphics(IMAGE_STAND);
  • ? ? }
  • ??}
  • ??virtual void update(Heroine& heroine) {
  • ? ? chargeTime_++;
  • ? ? if (chargeTime_ > MAX_CHARGE)
  • ? ? {
  • ? ?? ?heroine.superBomb();
  • ? ? }
  • ??}
  • private:
  • ??int chargeTime_;
  • };
  • 復制代碼


    注意我們把chargeTime_從Heroine類中移到了DuckingState類中。這是極好的——這條數據只在這個狀態下有意義,現在我們的對象模型很明顯得反應了這一點。

    狀態的代理

    下一步,我們在Heroine中添加一個指向當前狀態的指針,去掉那些大switch語句,把他們代理給state:

  • class Heroine
  • {
  • public:
  • ??virtual void handleInput(Input input)
  • ??{
  • ? ? state_->handleInput(*this, input);
  • ??}
  • ??virtual void update()
  • ??{
  • ? ? state_->update(*this);
  • ??}
  • ??// Other methods...
  • private:
  • ??HeroineState* state_;
  • };
  • 復制代碼


    如果要切換狀態,我們只需要將state_指針指向另外一個HeroineState對象即可。這就是State模式的

    State對象在哪里

    我在這里隱藏了一點細節。為了改變狀態,我們需要讓state_指向一個新的State對象,但是這些對象從哪里來呢?在我們的枚舉實現中,他們是一些簡單的數。但是現在狀態是類,這就意味著我們需要指向一個實在的對象實例。這里有兩種答案:

    靜態狀態

    如果狀態對象沒有其他的屬性,那么他只會存儲一個指向內部虛函數表的指針。這樣,沒有理由創建多個實例。每一個狀態的實例都應該是唯一的。

    這樣,你可以用一個靜態實例。即使你有一堆FSM 都用到了同一個狀態,他們也可以指向同一個狀態對象,因為它沒有對某個狀態機進行特化。

    這些靜態實例放在什么地方,取決于你。找一個合適的地方。如果沒有特殊原因,我們可以放在基類里:

  • class HeroineState
  • {
  • public:
  • ??static StandingState standing;
  • ??static DuckingState ducking;
  • ??static JumpingState jumping;
  • ??static DivingState diving;
  • ??// Other code...
  • };
  • 復制代碼


    每一個靜態域都是游戲中用到的狀態對象。為了讓heroine跳躍,站立狀態可以像這樣:

  • class HeroineState
  • {
  • public:
  • ??static StandingState standing;
  • ??static DuckingState ducking;
  • ??static JumpingState jumping;
  • ??static DivingState diving;
  • ??// Other code...
  • };
  • 復制代碼


    實例化狀態

    有時這種方法并不可行。靜態狀態不適合下蹲狀態,它有一個chargeTime_屬性,這個屬性綁定在下蹲的heroine。不過這個恰巧在我們的游戲中也能用,因為我們只有一個heroine,但如果我們要加入兩個玩家的玩法,在同一個名目中有兩個heroine,就會出問題了。

    這種情況下,我們必須在切換到一個狀態的時候,創建它。這樣才能做到每一個FSM都有專屬自己的狀態實例。當然,每當我們構建一個新狀態,就要釋放掉當前的狀態對象。這里需要小心,因為觸發切換的代碼是在當前的狀態對象中,我們不希望在這些對象中用delete this的方式銷毀自己。

    取而代之的是我們讓HeroineState中的handleInput()方法能夠返回一個新狀態。如果返回了,Heroine就銷毀老狀態,切換到新狀態上,就像這樣:

  • void Heroine::handleInput(Input input)
  • {
  • ??HeroineState* state = state_->handleInput(*this, input);
  • ??if (state != NULL)
  • ??{
  • ? ? delete state_;
  • ? ? state_ = state;
  • ??}
  • }
  • 復制代碼


    這樣,我們直到返回一個新狀態后,才銷毀掉前一個狀態。現在站立狀態可以用創建新對象的方式切換到下蹲狀態。

  • HeroineState* StandingState::handleInput(Heroine& heroine,
  • ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???Input input)
  • {
  • ??if (input == PRESS_DOWN)
  • ??{
  • ? ? // Other code...
  • ? ? return new DuckingState();
  • ??}
  • ??// Stay in this state.
  • ??return NULL;
  • }
  • 復制代碼


    如果可能,我會盡量使用靜態狀態,因為他們不消耗更多的內存和用來申請內存的CPU時鐘。對狀態來說,這些消耗太大了,盡管這也是一個方法。

    進入和退出動作

    State模式的目的在于把一種狀態的代碼和數據歸到一個單獨的類中。我們基本實現了,但是還有一些收尾工作。

    當heroine改變狀態,我們還要切換她的圖片?,F在這些代碼被放在了前一個狀態中。從下蹲到站立的過程,在下蹲的狀態中設置站立的圖片:

  • HeroineState* DuckingState::handleInput(Heroine& heroine,
  • ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? Input input)
  • {
  • ??if (input == RELEASE_DOWN)
  • ??{
  • ? ? heroine.setGraphics(IMAGE_STAND);
  • ? ? return new StandingState();
  • ??}
  • ??// Other code...
  • }
  • 復制代碼


    我們想要的是每一個狀態都控制它自己的圖片,我們可以給狀態添加一個entry動作,來做這個事情:

  • class StandingState : public HeroineState
  • {
  • public:
  • ??virtual void enter(Heroine& heroine)
  • ??{
  • ? ? heroine.setGraphics(IMAGE_STAND);
  • ??}
  • ??// Other code...
  • };
  • 復制代碼


    回到Heroine,我們修改代碼,把狀態改變后的變化放在新狀態的調用中:

  • void Heroine::handleInput(Input input)
  • {
  • ??HeroineState* state = state_->handleInput(*this, input);
  • ??if (state != NULL)
  • ??{
  • ? ? delete state_;
  • ? ? state_ = state;
  • ? ? // Call the enter action on the new state.
  • ? ? state_->enter(*this);
  • ??}
  • }
  • 復制代碼


    現在我們就可以簡化下蹲代碼了:

  • HeroineState* DuckingState::handleInput(Heroine& heroine,
  • ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? Input input)
  • {
  • ??if (input == RELEASE_DOWN)
  • ??{
  • ? ? return new StandingState();
  • ??}
  • ??// Other code...
  • }
  • 復制代碼


    它只需要切換到站立狀態,而讓站立狀態自己去關心圖像。現在我們的狀態才算真正的歸類了。進入事件只需要關注進入狀態,而不用關心是從哪個狀態轉化而來。

    現實中的狀態圖中,會有多個轉化路徑到同一個狀態。例如,heroine跳躍和俯沖后都會進入站立狀態。這就意味著我們會把一些相同的代碼散布在這些地方。進入事件讓我們可以把它們歸攏到一個地方。

    當然,我們也可以支持退出事件。其實就是一個離開一個狀態時調用的方法。

    有坑嗎?

    我用了大量的時間向大家兜售FSM,現在我要收一收了。到目前為止我說的都對,FSM也很好得解決了一些問題。但是他們最大的優點也是最大的缺點。

    狀態機幫助你整理一些雜亂的代碼,用的方法把他們強制套用到一個結構中。你得到的就是一些狀態集合,一個當前狀態,和一些寫死的轉化。

    如果你試著用狀態機去做一些復雜的工作,如游戲AI,你會被它帶來的很多限制甩一臉。幸運的是,我們的前輩們已經找到了很多方法去避開這些坑。在最后,我帶大家來看看這些辦法。

    并發狀態機

    我們決定讓heroine能帶槍。當她帶槍的時候,她仍然可以做他之前能做的所有動作:跑、跳、下蹲等等。但是她需要在做這些動作的同時開槍。

    如果我們嚴格按照FSM來做,我們必須有原來兩倍數量的狀態機。對每個已有的狀態,我們都要有一個對應的持槍動作:站立,持槍站立,跳躍,持槍跳躍。。。你懂得。

    添加更多的武器,會組合出更多的狀態。不止會產生大量的狀態,還會產生大量的冗余。有武器和沒有武器的狀態幾乎是相同的,除了少數的開火代碼。

    問題是我們把兩種狀態——她做了什么和她拿著什么——混淆進了一個狀態機。為了產生所有組合,我們需要為每一對組合產生一個狀態。解決辦法也很明顯:用兩個分離的狀態機。

    我們保留原有的狀態機,支持原來的功能。然后為她攜帶的武器單獨定義一個狀態機。Heroine將會兩個狀態引用,就像這樣:

  • class Heroine
  • {
  • ??// Other code...
  • private:
  • ??HeroineState* state_;
  • ??HeroineState* equipment_;
  • };
  • 復制代碼


    當heroine響應狀態輸入的時候,她要處理兩者:

  • void Heroine::handleInput(Input input)
  • {
  • ??state_->handleInput(*this, input);
  • ??equipment_->handleInput(*this, input);
  • }
  • 復制代碼


    每個狀態機都可以響應輸入,產生行為,并且變換狀態時相互沒有依賴。如果兩個撞他集合幾乎沒有聯系,它們會工作的很好。

    在實際應用中,你會發現有的情況下這些狀態會有交集。例如,可能她在跳躍的時候不能開火,或者可能她有裝備的時候不能俯沖。要處理這個問題,在一種狀態的代碼里,你可能加入一些if語句去判斷另一個狀態機的狀態。這不是一個優雅的解決方案,但是有效。

    分層狀態機

    在給我們的heroine加強了一些行為以后,她擁有了一些相似的狀態。例如,她會有站立,行走,跑,滑行等狀態。它們都是按B跳躍,按向下就下蹲。

    用簡單的狀態機實現方法,我們必須把這些代碼在每一個狀態中寫一遍。如果我們能實現一遍然后在所有的狀態中復用,那就更好了。

    如果這只是一些面向對象的代碼而不是狀態機,一個復用代碼的方法就是用繼承。我們需要定義一個底層的狀態,處理跳躍和下蹲。站立,行走,跑和滑行要繼承它,并且加入他們附加的行為。

    原來,這是一個常見的結構叫做分層狀態機。一個狀態可以有一個超狀態(這樣它就成了子狀態)。當一個事件進來后,如果子狀態不去處理它,事件將會沿著鏈條傳遞給超狀態。另一種說法,它就像覆蓋繼承方法。

    事實上,如果我們用到State模式去實現FSM,我們可以使用類繼承去實現層次化。定義一個超狀態基類:

  • class OnGroundState : public HeroineState
  • {
  • public:
  • ??virtual void handleInput(Heroine& heroine, Input input)
  • ??{
  • ? ? if (input == PRESS_B)
  • ? ? {
  • ? ?? ?// Jump...
  • ? ? }
  • ? ? else if (input == PRESS_DOWN)
  • ? ? {
  • ? ?? ?// Duck...
  • ? ? }
  • ??}
  • };
  • 復制代碼


    然后每個子狀態繼承它:

  • class DuckingState : public OnGroundState
  • {
  • public:
  • ??virtual void handleInput(Heroine& heroine, Input input)
  • ??{
  • ? ? if (input == RELEASE_DOWN)
  • ? ? {
  • ? ?? ?// Stand up...
  • ? ? }
  • ? ? else
  • ? ? {
  • ? ?? ?// Didn't handle input, so walk up hierarchy.
  • ? ?? ?OnGroundState::handleInput(heroine, input);
  • ? ? }
  • ??}
  • };
  • 復制代碼


    這當然不是實現層次化的唯一方法。如果你不用GOF的State模式,這就不會奏效。然而,你可以用顯式得使用狀態棧來表示當前的超狀態鏈,在宿主類(Heroine)中用它來替換那個單一的狀態(state_)。

    當前的狀態就是棧頂的那個狀態,它下面的就是它的直接超狀態,然后是超狀態的超狀態,以此類推。當你遇到這個狀態對應的行為時,從棧頂開始往下傳遞,直到有一個狀態處理了它。(如果沒有,就忽略。)

    下推自動機

    有限狀態機另外有一個擴展就是使用棧式狀態。不過容易使人迷惑的是,棧代表了完全不同的東西,經常被用來解決其他問題。

    有限狀態機的一個問題在于它不保留歷史記錄。你知道你當前的狀態是什么,但是不記得之前的狀態。并且沒有返回到之前狀態的簡單辦法。

    這里有一個例子:前面,我們把我們無畏的heroine武裝到了牙齒。當她開火的時候,我們需要一個新狀態來播放開火動畫,發射子彈和一些視覺特效。所以我們把他們放在FiringState中,在所有能夠開火的狀態中,添加一個到開火狀態的轉化。

    問題是,當她開火完了之后,進入什么狀態呢?她在站立、跑動、跳躍或者下蹲的時候,都能突然開火。當開火的一系列動作完成后,她應該返回到原來的的狀態下。

    如果我們必須要使用傳統FSM,就已經忘掉她之前的狀態了。為了解決這個問題,我們必須定義一些特定的狀態——站著射擊,跑著射擊,跳著射擊等等——這僅僅為了能寫死一些代碼,使得能轉化回原來的狀態。

    我們真正希望的是一種能夠記住射擊前狀態的方法,供后面使用。自動機就是一個有用的策略。相應的數據結構叫做下推自動機。

    有限狀態機有一個指向狀態的指針,而下推自動機卻有一個由這些指針構成的棧。在FSM中,轉換狀態用的是用一個新狀態替換原來的。一個下推自動機也允許你這么做,不過它還提供另外一種操作:

    1、你可以在棧中壓入一個新狀態,當前狀態始終位于棧頂,所以這也相當于轉換了新狀態。但這把原狀態留在了它下面,而不是直接丟棄掉。

    2、你可以彈出棧頂的狀態,丟掉,它下面的狀態就變成了當前狀態。
    ?


    這正是我們開火所需要的。我們只需要創建一個開火狀態。當在其他狀態下開火鍵被按下時,我們把開火狀態壓入到棧中。等到開火動作結束后,我們彈出這個狀態,然后下推自動機會自動得幫我們把狀態轉換到原有狀態上。

    那么他們有用嗎?

    即使那些擴展過的狀態機,也有很多限定條件。當今游戲AI發展趨勢是那些更吸引人的東西,如行為樹,計劃系統。如果你對更復雜的AI感興趣,本章的內容旨在喚起你的興趣。你需要閱讀其他書籍來滿足你的需求。

    這不說明有限狀態機,下推自動機,和其他簡單系統沒有用。他們是對一些特定的問題是一個很好的模型工具。有限狀態機在下面的情況下比較有用。

    1、你有一個東西,它的行為是基于一些內部狀態的。

    2、這些狀態能夠很容易地分離出少量明確的選項。

    3、隨著時間變化,這個東西要相應一系列輸入和事件。

    在游戲中,最有名的用法是在AI中。而在其他用法也很常見,如處理用戶輸入,菜單導航,解析文本,網絡協議,以及一些其他異步行為。

    總結

    以上是生活随笔為你收集整理的游戏编程设计模式-state的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。