讓遊戲程序員在代碼面前不再止步

熱薦新書

本週推薦一本新書《遊戲編程模式》,閱讀羣體:遊戲領域的設計人員、開發人員,還是想要進入遊戲開發領域的學生和普通程序員,都可以閱讀本書。

讓遊戲程序員在高度複雜的代碼庫面前不再止步的一本書


全書共分20章,通過三大部分內容全面介紹了與遊戲編程模式相關的各類知識點。第一部分介紹了基礎知識和框架;第二部分深入探索設計模式,並介紹了模式與遊戲開發之間的關聯;第三部分介紹了13種有效的遊戲設計模式。

本書和設計模式有什麼聯繫

任何名字中帶有“模式”的編程書籍都和經典圖書《設計模式:可複用面向對象軟件的基礎》有所聯繫。這本書由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides編著(這4人也稱爲“Gang of Four”,即本書所提到的“GoF”四人組)。

設計模式一書本身也源自前人的靈感。創造一種模式語言來描述問題的開放性解決方案,該想法來自《A Pattern Language》 ,由 Christopher Alexander(和Sarah Ishikawa、Murray Silverstein一起)完成。

這是一本關於框架結構的書(就像真正的建築結構中建築與牆體和材料之間的關係),作者希望他人能夠將其運用作其他領域問題的解決方案。設計模式(Design Patterns)正是GoF在軟件領域的一個嘗試。

本書的英文原名是Game Programming Design Patterns,並不是說GoF的書不適用於遊戲。恰恰相反,在本書第2篇中介紹了衆多來自GoF著作的設計模式,同時強調了在它們遊戲開發中的運用。

從另一面說,我覺得這本書也適用於非遊戲軟件。我也可以把這本書命名爲《More Design Patterns》 ,但我認爲遊戲開發有更多迷人的例子。難道你真的想要閱讀的另外一本關於員工記錄和銀行賬戶例子的設計模式圖書嗎?

也就是說,儘管這裏介紹的模式在其他軟件中也是有用的,但我覺得它們特別適合應對遊戲工程中普遍會遇到的挑戰,例如:

時間和順序往往是一個遊戲的架構的核心部分。事情必須依照正確的順序和正確的時間發生。

開發週期被高度壓縮。衆多程序員必須在不牽涉他人代碼、不污染代碼庫的前提下對一套龐大而錯雜的行爲體系進行快速的構建與迭代。

所有這些行爲被定義後,遊戲便開始互動。怪物撕咬英雄,藥水混合在一起,炸彈炸到敵人和朋友……諸如此類。這些交互必須很好地進行下去,可不能把代碼庫給攪成一團毛線球。

最後,性能在遊戲中至關重要。遊戲開發者永遠在榨取平臺性能這件事上賽跑。多削掉一個CPU週期,你的遊戲就有可能從掉幀和差評邁入A級遊戲和百萬銷量的天堂。


市面上已有的書籍

目前市面已經有數十多本遊戲編程的書籍。爲什麼還要再寫一本?

我見過的大多數遊戲編程書籍無非兩類。

關於特定領域的書籍。這些針對性較強的書籍帶領你深入地探索遊戲開發的一些特定方面。它們會教你3D圖形、實時渲染、物理仿真、人工智能或音頻處理。這些是衆多遊戲程序員在自己的職業生涯中所專注的領域。

關於整個遊戲引擎的書籍。相反,這些圖書試圖涵蓋整個遊戲引擎的各個部分。它們的目標是構建一整套適合某個特殊遊戲類型的引擎系統,這類通常是3D第一人稱射擊遊戲。

我喜歡這兩類書,但我覺得它們仍留下了一些空白。講特定領域的書很少會談及你的代碼塊如何與遊戲的其他部分交互。你可能擅長物理和渲染,但是你知道如何優雅地將它們拼合起來嗎?

這種分類講解風格的另外一個例子,就是廣受大家喜愛的《遊戲編程精粹》系列。

第二類書籍涵蓋了這類問題,但我往往發現這類書通常都太過龐大、太過空泛。特別是隨着移動和休閒遊戲的興起,我們正處在衆多類型的遊戲共同發展的時代。我們不再只是照搬Quake

了。當你的遊戲不適合這個模型時,這類闡述單一引擎的書籍就不再合適了。

相反,這裏我想要做的,更傾向於分門別類。本書的每個章節都是一個獨立的思路,你可以將它應用到你的代碼裏。你也可以針對自己製作的遊戲來決定以最恰當的方式將它們進行混搭。

作者介紹


Robert Nystrom是一位具備超過20年職業編程經驗的開發者,而其中大概一半時間用於從事遊戲開發。在藝電(Electronic Arts)的8年時間裏,他曾參與勁爆美式足球(Madden)系列這樣龐大的項目,也曾投身於亨利•海茨沃斯大冒險(Henry Hatsworth in the Puzzling Adventure)這樣稍小規模的遊戲開發之中。他所開發的遊戲遍及PC、GameCube、PS2、XBox、X360以及DS平臺。但最傲人之處在於,他爲開發者們提供了開發工具和共享庫。他熱衷於尋求易用的、漂亮的代碼來延伸和增強開發者們的創造力。

Robert與他的妻子和兩個女兒定居於西雅圖,在那裏你很有可能會見到他正在爲朋友們下廚,或者在爲他們上啤酒。

章節欣賞:狀態模式(選摘)

“允許一個對象在其內部狀態改變時改變自身的行爲。對象看起來好像是在修改自身類。”

交代一下:我寫的有些過頭了,我在本章裏面添加了太多東西。表面上這一章是介紹狀態模式[的,但是我不能拋開遊戲裏面的有限狀態機(finite state machines,FSM)而單獨只談“狀態模式”。不過,當我講到FSM的時候,我發覺我還有必要再介紹一下層次狀態機(hierarchical state machine)和下推自動機(pushdown automata)。

因爲有太多東西需要講,所以我試圖壓縮本章的內容。本章中的代碼片斷沒有涉及很細節的東西,所以,這些省略的部分需要靠讀者來腦補。我希望它們仍然足夠清楚到能讓你掌握關鍵點(big picture)。

層次狀態機和下推自動機這對術語指的是早期的人工智能。在20世紀50年代和60年代,大部分AI研究關注的是語言處理。許多現在用來解析編程語言的編譯器被髮明用來解析人類語言。

如果你從未聽說過狀態機,也不要感到沮喪。它們對於人工智能領域的開發者和編譯器黑客來說非常熟悉,不過在其他編程領域可能不是那麼被人熟知了。我覺得它應該被更多的人瞭解,因此,我將從一個不同的應用領域的視角來介紹它。

7.1 我們曾經相遇過

假設我們現在正在開發一款橫版遊戲。我們的任務是實現女主角——遊戲世界中玩家的圖像。我們需要根據玩家的輸入來控制主角的行爲。當按下B鍵的時候,她應該跳躍。我們可以這樣實現:

void Heroine::handleInput(Input input){ if (input == PRESS_B) {  yVelocity_ = JUMP_VELOCITY;  setGraphics(IMAGE_JUMP); }}

找找看,bug在哪裏?

這裏應該還有如果主角着地將isJumping_設置回false的代碼。爲了簡潔起見,我省略了。

我們沒有阻止主角“在空中跳躍”——當主角跳起來後持續按下B鍵。這樣會導致她一直飄在空中,簡單的修復方法可以是:在Heroine類中添加一個isJumping_布爾值變量來跟蹤主角的跳躍,然後這麼做:

void Heroine::handleInput(Input input){ if (input == PRESS_B) {  if (!isJumping_)  {   isJumping_ = true;   // Jump...  } }}

接下來,我們想實現主角的閃避動作。當主角站在地面上的時候,如果玩家按下下方向鍵,則躲避,如果鬆開此鍵,則站立。

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.玩家還在空中的時候鬆開下鍵。

此時,當女主角在跳躍狀態的時候,顯示的是站立的圖像。是時候添加另外一個布爾標誌位來解決該問題了······

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可能會更多。

7.2 救星:有限狀態機

爲了消除你心中的疑惑,你可以準備一張紙和一支筆,讓我們一起來畫一張流程圖。對於女主角能夠進行的動作畫一個“矩形”:站立、跳躍、躲避和俯衝。當你可以按下一個鍵讓主角從一個狀態切換到另一個狀態的時候,我們畫一個箭頭,讓它從一個矩形指向另一個矩形。同時在箭頭上面添加文本,表示我們按下的按鈕。

恭喜,你剛剛已經成功創建了一個有限狀態機。有限狀態機借鑑了計算機科學裏的自動機理論(automata theory)中的一種數據結構(圖靈機)思想。有限狀態機(FSMs)可以看作是最簡單的圖靈機(如圖7-1所示)。


圖7-1 一張狀態機的圖表

其表達的是:

關於有限狀態機我最喜歡的比喻就是它是像Zork一樣的古老的文字冒險遊戲。遊戲中有着由出口連接着的一些房間。你可以通過輸入像“往北前進”這樣的命令來進行探索。

這其實就是一個狀態機:每一個房間是一個狀態。你所在的房間就是當前的狀態。每個房間的出口就是它的轉換,導航命令就是輸入。

  • 你擁有一組狀態,並且可以在這組狀態之間進行切換。比如:站立、跳躍、躲避和俯衝。

  • 狀態機同一時刻只能處於一種狀態。女主角無法同時跳躍和站立。事實上,防止同時存在兩個狀態是我們使用有限狀態機的原因。

  • 狀態機會接收一組輸入或者事件。在我們這個例子中,它們就是按鈕的按下和釋放。

  • 每一個狀態有一組轉換,每一個轉換都關聯着一個輸入並指向另一個狀態。當有一個輸入進來的時候,如果輸入與當前狀態的其中一個轉換匹配上,則狀態機便會轉換狀態到輸入事件所指的狀態。

在我們的例子中,在站立狀態的時候如果按下向下方向鍵,則狀態轉換到躲避狀態。如果在跳躍狀態的時候按下向下方向鍵,則會轉換到俯衝攻擊狀態。如果對於每一個輸入事件沒有對應的轉換,則這個輸入就會被忽略。

簡而言之,整個狀態機可以分爲:狀態、輸入和轉換。你可以通過畫狀態流程圖來表示它們。不幸的是,編譯器並不認識狀態圖,所以,我們接下來要介紹如何實現。GoF的狀態模式是一種實現方法,但是讓我們先從更簡單的方法開始。

7.3 枚舉和分支

一個問題是,Heroine類有一些布爾類型的成員變量:isJumping_isDucking_,但是這兩個變量不應該同時爲true。當你有一系列的標記成員變量,而它們只能有且僅有一個爲true時,這表明我們需要把它們定義成枚舉(enum)。

在這個例子當中,我們的有限狀態機的每一個狀態可以用一個枚舉來表示,所以,讓我們定義以下枚舉:

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;  // Other states... }}

我們可以像下面設置其他狀態:

void Heroine::handleInput(Input input){ switch (state_) {  // Standing state...  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類中添加一個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... }}

總之,爲了添加蓄能攻擊,我們不得不修改兩個方法,並且添加一個chargeTime_成員變量給主角,儘管這個成員變量只有在主角處於躲避狀態的時候纔有效。其實我們真正想要的是把所有這些和與之相關的數據和代碼封裝起來。接下來,我們介紹GoF的狀態模式來解決這個問題。

7.4 狀態模式

對於熟知面向對象方法的人來說,每一個條件分支都可以用動態分發來解決(換句話說,都可以用C++裏面的虛函數來解決)。但是,如果這樣做,你可能會把簡單問題複雜化。有時候,一個簡單的if語句就足夠了。

狀態模式的由來也有一些歷史原因。許多面向對象設計的擁護者—— GoF和重構的作者Martin Fowler都是Smalltalk出身。在那裏,如果有一個ifThen語句,我們便可以用一個表示truefalse的對象來操作。

但是,在我們這個例子當中,我們發現面對對象設計也就是狀態模式更合適。

GoF描述的狀態模式在應用到我們的例子中時如下。

7.4.1 一個狀態接口

首先,我們爲狀態定義一個接口。每一個與狀態相關的行爲都定義成虛函數。在我們的例子中,就是handleInput()update()函數。

class HeroineState{public: virtual ~HeroineState() {} virtual void handleInput(Heroine& heroine,                 Input input) {} virtual void update(Heroine& heroine) {}};

7.4.2 爲每一個狀態定義一個類

對於每一個狀態,我們定義了一個類並繼承此狀態接口。它的方法定義主角對應此狀態的行爲。換句話說,把之前的switch語句裏面的每一個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(躲避狀態)類中。這樣非常好,因爲這個變量只是對躲避狀態有意義,現在把它定義在這裏,正好顯式地反映了我們的對象模型。

7.4.3 狀態委託

接下來,我們在主角類中定義一個指針變量,讓它指向當前的狀態。我們把之前那個很大的switch語句去掉,並讓它去調用狀態接口的虛函數,最終這些虛方法就會動態地調用具體子狀態的相應函數。

狀態委託看起來很像策略模式和類型對象模式(第13章)。在這三個模式中,你會有一個主對象委託給另外的附屬對象。它們三者的區別主要在於目的不同:

  • 策略模式的目標是將主類與它的部分行爲進行解耦。

  • 類型對象模式的目標是使得多個對象通過共享相同類型對象的引用來表現出相似性。

  • 狀態模式的目標是通過改變主對象代理的對象來改變主對象的行爲。

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狀態對象。至此,我們的狀態模式就講完了。

7.5 狀態對象應該放在哪裏呢

我這裏忽略了一些細節。爲了修改一個狀態,我們需要給state_指針賦值爲一個新的狀態,但是這個新的狀態對象要從哪裏來呢?我們之前的枚舉方法是定義一些數字。但是,現在我們的狀態是類,我們需要獲取這些類的實例。通常來說,有兩種實現方法。

7.5.1 靜態狀態

如果一個狀態對象沒有任何數據成員,那麼它的唯一數據成員便是虛表指針了。那樣的話,我們就沒有必要創建此狀態的多個實例了,因爲它們的每一個實例都是相同的。

在那種情況下,我們可以定義一個靜態實例。即使你有一系列的FSM在同時運轉,所有的狀態機也能同時指向這一個唯一的實例。

如果你的狀態類沒有任何數據成員,並且只有一個虛函數方法。那麼我們還可以進一步簡化此模式。我們可以使用一個普通的狀態函數來替換狀態類。這樣的話,我們的state_變量就變成一個狀態函數指針。

這個就是享元模式。(第3章)

你把靜態方法放置在哪裏,這個由你自己來決定。如果沒有任何特殊原因的話,我們可以把它放置到基類狀態類中:

class HeroineState{public: static StandingState standing; static DuckingState ducking; static JumpingState jumping; static DivingState diving; // Other code...};

每一個靜態成員變量都是對應狀態類的一個實例。如果我們想讓主角跳躍,那麼站立狀態應該是這樣子:

if (input == PRESS_B){ heroine.state_ = &HeroineState::jumping; heroine.setGraphics(IMAGE_JUMP);}

7.5.2 實例化狀態

有時候上面的方法可能不行。一個靜態狀態對於躲避狀態而言是行不通的。因爲它有一個chargeTime_成員變量,所以這個具體取決於每一個躲避狀態下的主角類。如果我們的遊戲裏面只有一個主角的話,那麼定義一個靜態類也是沒有什麼問題的。但是,如果我們想加入多個玩家,那麼此方法就行不通了。

當你爲狀態實例動態分配空間時,你不得不考慮碎片化問題了。對象池模式(第19章)可以幫助到你。

在那種情況下,我們不得不在狀態切換的時候動態地創建一個躲避狀態實例。這樣,我們的有限狀態機就擁有了它自己的實例。當然,如果我們又動態分配了一個新的狀態實例,則要負責清理老的狀態實例。這裏必須相當小心,因爲修改狀態的函數是在當前狀態裏面,所以我們需要小心地處理刪除的順序。

另外,我們也可以選擇在HeroineState類中的handleInput()方法裏面可選地返回一個新的狀態。當這個狀態返回的時候,主角將會刪除老的狀態並切換到這個新的狀態,如下所示:

void Heroine::handleInput(Input input){ HeroineState* state = state_−>handleInput(        *this, input); if (state != NULL) {  delete state_;  state_ = state; }}

那樣的話,我們只有在從handleInput方法返回的時候纔有可能去刪除前面的狀態對象。現在,站立狀態可以通過創建一個躲避狀態的實例來切換狀態了。

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

通常情況下,我傾向於使用靜態狀態。因爲它們不會佔用太多的CPU和內存資源。

7.6 進入狀態和退出狀態的行爲

狀態模式的目標就是將每個狀態相關的所有的數據和行爲封裝到相關類裏面。萬里長征,我們僅僅邁出去了一步,我們還有更多路要走。

當主角更改狀態的時候,我們也會切換它的貼圖。現在,這段代碼包含在它要切換的狀態的上一個狀態裏面。當她從躲避狀態切換到站立狀態時,躲避狀態將會修改它的圖像:

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

我們希望的是,每一個狀態控制自己的圖像。我們可以通過給每一個狀態添加一個entey行爲。

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...}

它所做的就是切換到站立狀態,然後站立狀態會自己設置圖像。現在,我們的狀態已經封裝好了。entry動作的一個最大的好處就是它不用關心上一個狀態是什麼,它只需要根據自己的狀態來處理圖像和行爲就可以了。

大部分的真實狀態圖裏面,我們有多個狀態對應同一個狀態。比如,我們的女主角會在她俯衝或者跳躍之後站立在地面上。這意味着,我們可能會在每一個狀態發生變化的時候重複寫很多代碼。但是,entry動作幫我們很好地解決了這個問題。

當然,我們也可以擴展這個功能來支持退出狀態的行爲。我們可以定義一個exit函數來定義一些在狀態改變前的處理。

7.7 有什麼收穫嗎

一個有限狀態機甚至都不是圖靈完備的。自動機理論使用一系列抽象的模型來描述計算,並且每一個模型都比先前的模型更復雜。而圖靈機只是這裏面最具有表達力的模型之一。

“圖靈完備”意味着一個系統(通常指的是一門編程語言)是足夠強大的,強大到它可以實現一個圖靈機。這也意味着,所有圖靈完備的編程語言,在某些程度上其表達力是相同的。但有限狀態機由於其不夠靈活,並不在其中。

我已經花了大量的時間來介紹有限狀態機。現在我們一起來捋一捋。到目前爲止,我跟你講的所有事情都是對的,有限狀態機對於某些應用來講是非常合適的。但是,最大的優點往往也是最大的缺點。

狀態機幫助你把千絲萬縷的邏輯判斷代碼封裝起來。你需要的只是一組調整好的狀態,一個當前狀態和一些硬編碼的狀態切換。

如果你想要用一個狀態機來表示一些複雜的遊戲AI,則可能會面臨這個模型的一些限制。幸運的是,我們的前輩們已經發現了一些不錯的解決方案。我將會在本章的最後簡單地介紹它們。

喜歡看書和分享的朋友可以加羣--> 程序員書屋羣:255082518

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章