《設計模式與游戲開發》是作者“十年磨一劍”,將設計模式理論巧妙地融合到實踐中的教材。 全書采用了整合式的項目教學,即以一個游戲的范例來應用23種設計模式的實現貫穿全書,讓讀者學習到整個游戲開發的全過程和作者想要傳承的經驗,并以淺顯易懂的比喻來解析難以理解的設計模式,讓想深入了解此領域的讀者更加容易上手。 本書既可以作為大學、專科和職業院校游戲程序設計專業的教材,也可以作為游戲從業人員提高游戲設計能力和規范運用設計模式的培訓教材,還可以作為有這方面職業興趣的讀者學習和提高的自學參考書。
十年磨一劍,作者將設計模式理論巧妙地融入到實踐中,以一個游戲的完整實現呈現設計模式的應用及經驗的傳承 《軒轅劍》之父——蔡明宏、博學游戲制作人——李佳澤、Product Evangelist at Unity Technologies——Kelvin Lo、信仁軟件設計創辦人—— 賴信仁、博學3D游戲美術——劉明愷 聯合推薦全書采用了整合式的項目教學,即以一個游戲的范例來應用23種設計模式的實現貫穿全書,讓讀者學習到整個游戲開發的全過程和作者想要傳承的經驗,并以淺顯易懂的比喻來解析難以理解的設計模式,讓想深入了解此領域的讀者更加容易上手。
蔡升達當年為實現游戲夢從學術界進入游戲業,累積了10年游戲開發經驗,包含多款大型多人線上游戲(MMORPG)、網頁游戲(Web Game)、移動平臺連線游戲(APP Game)。曾任職于臺灣地區知名游戲開發公司,擔任游戲設計師、專案程序統籌、研發技術中心經理等職務,現任知名新創團隊研發總監一職。擅長游戲程序設計、網絡程序設計、分布式系統設計、電腦圖學、影像搜索、游戲制作規劃及運作、游戲專案管理。喜歡閱讀,家中有上千本藏書,包含信息技術、文學、奇幻小說、歷史小說等,因為看了很多,所以也有很多想法想與人分享。構想多年之后,某日下午終于下筆開始了這本書。十年磨一劍,作者將設計模式理論巧妙地融入到實踐中,以一個游戲的完整實現呈現設計模式的應用及經驗的傳承
第1篇 設計模式與游戲設計
第1章 游戲實現中的設計模式 2
1.1 設計模式的起源 2
1.2 軟件的設計模式是什么? 3
1.3 面向對象設計中常見的設計原則 4
1.4 為什么要學習設計模式 7
1.5 游戲程序設計與設計模式 8
1.6 模式的應用與學習方式 10
1.7 結論 11
第2章 游戲范例說明 12
2.1 游戲范例 12
2.2 GoF的設計模式范例 15
第2篇 基礎系統
第3章 游戲場景的轉換——狀態模式(State) 20
3.1 游戲場景 20
3.1.1 場景的轉換 20
3.1.2 游戲場景可能的實現方式 23
3.2 狀態模式(State) 24
3.2.1 狀態模式(State)的定義 24
3.2.2 狀態模式(State)的說明 25
3.2.3 狀態模式(State)的實現范例 25
3.3 使用狀態模式(State)實現游戲場景的轉換 28
3.3.1 SceneState的實現 28
3.3.2 實現說明 29
3.3.3 使用狀態模式(State)的優點 35
3.3.4 游戲執行流程及場景轉換說明 36
3.4 狀態模式(State)面對變化時 37
3.5 結論 37
第4章 游戲主要類——外觀模式(Facade) 39
4.1 游戲子功能的整合 39
4.2 外觀模式(Facade) 41
4.2.1 外觀模式(Facade)的定義 41
4.2.2 外觀模式(Facade)的說明 42
4.2.3 外觀模式(Facade)的實現說明 43
4.3 使用外觀模式(Facade)實現游戲主程序 44
4.3.1 游戲主程序架構設計 44
4.3.2 實現說明 45
4.3.3 使用外觀模式(Facade)的優點 47
4.3.4 實現外觀模式(Facade)時的注意事項 48
4.4 外觀模式(Facade)面對變化時 48
4.5 結論 48
第5章 獲取游戲服務的對象——單例模式(Singleton) 50
5.1 游戲實現中的對象 50
5.2 單例模式(Singleton) 51
5.2.1 單例模式(Singleton)的定義 51
5.2.2 單例模式(Singleton)的說明 51
5.2.3 單例模式(Singleton)的實現范例 52
5.3 使用單例模式(Singleton)獲取的游戲服務對象 53
5.3.1 游戲服務類的單例模式實現 53
5.3.2 實現說明 54
5.3.3 使用單例模式(Singleton)后的比較 55
5.3.4 反對使用單例模式(Singleton)的原因 55
5.4 少用單例模式(Singleton)時如何方便地引用到單一對象 58
5.5 結論 63
第6章 游戲內各系統的整合——中介者模式(Mediator) 64
6.1 游戲系統之間的溝通 64
6.2 中介者模式(Mediator) 68
6.2.1 中介者模式(Mediator)的定義 69
6.2.2 中介者模式(Mediator)的說明 69
6.2.3 中介者模式(Mediator)的實現范例 69
6.3 中介者模式(Mediator)作為系統之間的溝通接口 72
6.3.1 使用中介者模式(Mediator)的系統架構 73
6.3.2 實現說明 73
6.3.3 使用中介者模式(Mediator)的優點 79
6.3.4 實現中介者模式(Mediator)時的注意事項 79
6.4 中介者模式(Mediator)面對變化時 80
6.5 結論 80
第7章 游戲的主循環——Game Loop 82
7.1 GameLoop由此開始 82
7.2 怎么實現游戲循環(Game Loop) 84
7.3 在Unity3D中實現游戲循環 85
7.4 P級陣地的游戲循環 89
7.5 結論 92
第3篇 角色的設計
第8章 角色系統的設計分析 94
8.1 游戲角色的架構 94
8.2 角色類的規劃 95
第9章 角色與武器的實現——橋接模式(Bridge) 98
9.1 角色與武器的關系 98
9.2 橋接模式(Bridge) 103
9.2.1 橋接模式(Bridge)的定義 103
9.2.2 橋接模式(Bridge)的說明 107
9.2.3 橋接模式(Bridge)的實現范例 108
9.3 使用橋接模式(Bridge)實現角色與武器接口 110
9.3.1 角色與武器接口設計 110
9.3.2 實現說明 111
9.3.3 使用橋接模式(Bridge)的優點 116
9.3.4 實現橋接模式(Bridge)的注意事項 116
9.4 橋接模式(Bridge)面對變化時 116
9.5 結論 117
第10章 角色屬性的計算——策略模式(Strategy) 118
10.1 角色屬性的計算需求 118
10.2 策略模式(Strategy) 121
10.2.1 策略模式(Strategy)的定義 122
10.2.2 策略模式(Strategy)的說明 122
10.2.3 策略模式(Strategy)的實現范例 123
10.3 使用策略模式(Strategy)實現攻擊計算 124
10.3.1 攻擊流程的實現 125
10.3.2 實現說明 125
10.3.3 使用策略模式(Strategy)的優點 132
10.3.4 實現策略模式(Strategy)時的注意事項 133
10.4 策略模式(Strategy)面對變化時 134
10.5 結論 135
第11章 攻擊特效與擊中反應——模板方法模式(Template Method) 137
11.1 武器的攻擊流程 137
11.2 模板方法模式(Template Method) 139
11.2.1 模板方法模式(Template Method)的定義 139
11.2.2 模板方法模式(Template Method)的說明 141
11.2.3 模板方法模式(Template Method)的實現范例 141
11.3 使用模板方法模式實現攻擊與擊中流程 142
11.3.1 攻擊與擊中流程的實現 143
11.3.2 實現說明 143
11.3.3 運用模板方法模式(Template Method)的優點 145
11.3.4 修改擊中流程的實現 145
11.4 模板方法模式(Template Method)面對變化時 147
11.5 結論 149
第12章 角色AI——狀態模式(State) 150
12.1 角色的AI 150
12.2 狀態模式(State) 158
12.3 使用狀態模式(State)實現角色AI 159
12.3.1 角色AI的實現 159
12.3.2 實現說明 160
12.3.3 使用狀態模式(State)的優點 169
12.3.4 角色AI執行流程 169
12.4 狀態模式(State)面對變化時 170
12.5 結論 172
第13章 角色系統 174
13.1 角色類 174
13.2 游戲角色管理系統 176
第4篇 角色的產生
第14章 游戲角色的產生——工廠方法模式(Factory Method) 183
14.1 產生角色 183
14.2 工廠方法模式(Factory Method) 188
14.2.1 工廠方法模式(Factory Method)的定義 188
14.2.2 工廠方法模式(Factory Method)的說明 189
14.2.3 工廠方法模式(Factory Method)的實現范例 189
14.3 使用工廠方法模式(Factory Method)產生角色對象 195
14.3.1 角色工廠類 195
14.3.2 實現說明 196
14.3.3 使用工廠方法模式(Factory Method)的優點 199
14.3.4 工廠方法模式(Factory Method)的實現說明 199
14.4 工廠方法模式(Factory Method)面對變化時 203
14.5 結論 205
第15章 角色的組裝——建造者模式(Builder) 206
15.1 角色功能的組裝 206
15.2 建造者模式(Builder) 213
15.2.1 建造者模式(Builder)的定義 213
15.2.2 建造者模式(Builder)的說明 214
15.2.3 建造者模式(Builder)的實現范例 215
15.3 使用建造者模式(Builder)組裝角色的各項功能 217
15.3.1 角色功能的組裝 218
15.3.2 實現說明 219
15.3.3 使用建造者模式(Builder)的優點 226
15.3.4 角色建造者的執行流程 226
15.4 建造者模式(Builder)面對變化時 227
15.5 結論 228
第16章 游戲屬性管理功能——享元模式(Flyweight) 229
16.1 游戲屬性的管理 229
16.2 享元模式(Flyweight) 236
16.2.1 享元模式(Flyweight)的定義 236
16.2.2 享元模式(Flyweight)的說明 237
16.2.3 享元模式(Flyweight)的實現范例 238
16.3 使用享元模式(Flyweight)實現游戲 242
16.3.1 SceneState的實現 242
16.3.2 實現說明 245
16.3.3 使用享元模式(Flyweight)的優點 250
16.3.4 享元模式(Flyweight)的實現說明 250
16.4 享元模式(Flyweight)面對變化時 252
16.5 結論 252
第5篇 戰爭開始
第17章 Unity3D的界面設計——組合模式(Composite) 254
17.1 玩家界面設計 254
17.2 組合模式(Composite) 259
17.2.1 組合模式(Composite)的定義 259
17.2.2 組合模式(Composite)的說明 260
17.2.3 組合模式(Composite)的實現范例 261
17.2.4 分了兩個子類但是要使用同一個操作界面 264
17.3 Unity3D游戲對象的分層式管理功能 265
17.3.1 游戲對象的分層管理 265
17.3.2 正確有效地獲取UI的游戲對象 266
17.3.3 游戲用戶界面的實現 267
17.3.4 兵營界面的實現 269
17.4 結論 274
第18章 兵營系統及兵營信息顯示 276
18.1 兵營系統 276
18.2 兵營系統的組成 277
18.3 初始兵營系統 281
18.4 兵營信息的顯示流程 287
第19章 兵營訓練單位——命令模式(Command) 288
19.1 兵營界面上的命令 288
19.2 命令模式(Command) 291
19.2.1 命令模式(Command)的定義 291
19.2.2 命令模式(Command)的說明 294
19.2.3 命令模式(Command)的實現范例 294
19.3 使用命令模式(Command)實現兵營訓練角色 297
19.3.1 訓練命令的實現 297
19.3.2 實現說明 298
19.3.3 執行流程 302
19.3.4 實現命令模式(Command)時的注意事項 303
19.4 命令模式(Command)面對變化時 305
19.5 結論 306
第20章 關卡設計——責任鏈模式(Chain of Responsibility) 307
20.1 關卡設計 307
20.2 責任鏈模式(Chain of Responsibility) 312
20.2.1 責任鏈模式(Chain of Responsibility)的定義 312
20.2.2 責任鏈模式(Chain of Responsibility)的說明 314
20.2.3 責任鏈模式(Chain of Responsibility)的實現范例 314
20.3 使用責任鏈模式(Chain of Responsibility)實現關卡系統 317
20.3.1 關卡系統的設計 317
20.3.2 實現說明 318
20.3.3 使用責任鏈模式(Chain of Responsibility)的優點 329
20.3.4 實現責任鏈模式(Chain of Responsibility)時的注意事項 329
20.4 責任鏈模式(Chain of Responsibility)面對變化時 330
20.5 結論 332
第6篇 輔助系統
第21章 成就系統—觀察者模式(Observer) 334
21.1 成就系統 334
21.2 觀察者模式(Observer) 338
21.2.1 觀察者模式(Observer)的定義 338
21.2.2 觀察者模式(Observer)的說明 340
21.2.3 觀察者模式(Observer)的實現范例 341
21.3 使用觀察者模式(Observer)實現成就系統 344
21.3.1 成就系統的新架構 344
21.3.2 實現說明 346
21.3.3 使用觀察者模式(Observer)的優點 358
21.3.4 實現觀察者模式(Observer)時的注意事項 358
21.4 觀察者模式(Observer)面對變化時 359
21.5 結論 361
第22章 存盤功能—備忘錄模式(Memento) 362
22.1 存儲成就記錄 362
22.2 備忘錄模式(Memento) 366
22.2.1 備忘錄模式(Memento)的定義 366
22.2.2 備忘錄模式(Memento)的說明 367
22.2.3 備忘錄模式(Memento)的實現范例 367
22.3 使用備忘錄模式(Memento)實現成就記錄的保存 371
22.3.1 成就記錄保存的功能設計 371
22.3.2 實現說明 371
22.3.3 使用備忘錄模式(Memento)的優點 374
22.3.4 實現備忘錄模式(Memento)的注意事項 374
22.4 備忘錄模式(Memento)面對變化時 374
22.5 結論 375
第23章 角色信息查詢—訪問者模式(Visitor) 376
23.1 角色信息的提供 376
23.2 訪問者模式(Visitor) 385
23.2.1 訪問者模式(Visitor)的定義 386
23.2.2 訪問者模式(Visitor)的說明 390
23.2.3 訪問者模式(Visitor)的實現范例 392
23.3 使用訪問者模式(Visitor)實現角色信息查詢 397
23.3.1 角色信息查詢的實現設計 397
23.3.2 實現說明 398
23.3.3 使用訪問者模式(Visitor)的優點 405
23.3.4 實現訪問者模式(Visitor)時的注意事項 405
23.4 訪問者模式(Visitor)面對變化時 405
23.5 結論 408
第7篇 調整與優化
第24章 前綴字尾—裝飾模式(Decorator) 410
24.1 前綴后綴系統 410
24.2 裝飾模式(Decorator) 415
24.2.1 裝飾模式(Decorator)的定義 415
24.2.2 裝飾模式(Decorator)的說明 418
24.2.3 裝飾模式(Decorator)的實現范例 419
24.3 使用裝飾模式(Decorator)實現前綴后綴的功能 422
24.3.1 前綴后綴功能的架構設計 423
24.3.2 實現說明 423
24.3.3 使用裝飾模式(
第3章游戲場景的轉換——狀態模式(State)
3.1 游戲場景本書使用Unity3D游戲引擎作為開發工具,而Unity3D是使用場景(Scene)作為游戲運行時的環境。開始制作游戲時,開發者會將游戲需要的素材(3D模型、游戲對象)放到一個場景中,然后編寫對應的程序代碼,之后只要單擊Play按鈕,就可以開始運行游戲。除了Unity3D之外,筆者過去開發游戲時使用的游戲引擎(Game Engine)或開發框架(SDK、Framework),多數也都存在“場景”的概念,例如:? 早期Java Phone的J2ME開發SDK中使用的Canvas類;? Android的Java開發SDK中使用的Activity類;? iOS上2D游戲開發工具Cocos2D中使用的CCScene類。雖然各種工具不見得都使用場景(Scene)這個名詞,但在實現上,一樣可使用相同的方式來呈現。而上面所列的各個類,都可以被拿來作為游戲實現中“場景”轉換的目標。3.1.1 場景的轉換當游戲比較復雜時,通常會設計成多個場景,讓玩家在幾個場景之間轉換,某一個場景可能是角色在一個大地圖上行走,而另一個場景則是在地下洞穴探險。這樣的設計方式其實很像是舞臺劇的呈現方式,編劇們設計了一幕幕的場景讓演員們在其間穿梭演出,而每幕之間的差異,可能是在布景擺設或參與演出角色的不同,但對于觀眾來說,同時間只會看到演員們在某一個場景中的演出。要怎樣才能真正應用“場景”來開發游戲呢?讀者可以回想一下,當我們打開游戲App或開始運行游戲軟件時,會遇到什么樣的畫面:出現游戲Logo、播放游戲片頭、加載游戲數據、出現游戲主畫面、等待玩家登錄游戲、進入游戲主畫面,接下來玩家可能是在大地圖上打怪或進入副本刷關卡……。游戲畫面轉換如圖3-1所示。 圖3-1 游戲畫面轉換就以上面的說明為例,我們可規劃出下面數個場景,每個場景分別負責多項功能的執行,如圖3-2所示。 圖3-2 每個場景負責執行的游戲功能? 登錄場景:負責游戲片頭、加載游戲數據、出現游戲主畫面、等待玩家登錄游戲。? 主畫面場景:負責進入游戲畫面、玩家在主城/主畫面中的操作、在地圖上打怪打寶……? 戰斗場景:負責與玩家組隊之后進入副本關卡、挑戰王怪……在游戲場景規劃完成后,就可以利用“狀態圖”將各場景的關系連接起來,并且說明它們之間的轉換條件以及狀態轉換的流程,如圖3-3所示。 圖3-3 各場景轉換條件以及狀態轉換的“狀態圖”即便我們換了一個游戲類型來實現,一樣可以使用相同的場景分類方式,將游戲功能進行歸類,例如:卡牌游戲可按如下分類:? 登錄場景:負責游戲片頭、加載游戲數據、出現游戲主畫面、等待玩家登錄游戲。? 主畫面場景:玩家抽卡片、合成卡牌、查看卡牌……? 戰斗場景:挑戰關卡、卡片對戰……轉珠游戲可按如下分類:? 登錄場景:負責游戲片頭、加載游戲數據、出現游戲主畫面、等待玩家登錄游戲。? 主畫面場景:查看關卡進度、關卡信息、商城、抽轉珠……? 戰斗場景:挑戰關卡、轉珠對戰……當然,如果是更復雜的單機版游戲或大型多人在線游戲(MMORPG),還可以再細分出多個場景來負責對應的游戲功能。切分場景的好處將游戲中不同的功能分類在不同的場景中來執行,除了可以將游戲功能執行時需要的環境明確分類之外,“重復使用”也是使用場景轉換的好處之一。從上面幾個例子中可以看出,“登錄場景”幾乎是每款游戲必備的場景之一。而一般在登錄場景中,會實現游戲初始化功能或玩家登錄游戲時需要執行的功能,例如:? 單機游戲:登錄場景可以有加載游戲數據、讓玩家選擇存盤、進入游戲等步驟。? 在線游戲:登錄場景包含了許多復雜的在線登錄流程,比如使用第三方認證系統、使用玩家自定義賬號、與服務器連接、數據驗證……對于大多數的游戲開發公司來說,登錄場景實現的功能,會希望通用于不同的游戲開發項目,使其保持流程的一致性。尤其對于在線游戲這種類型的項目而言,由于登錄流程較為復雜,若能將各項目共同的部分(場景)獨立出來,由專人負責開發維護并同步更新給各個項目,那么效率就能獲得提升,也是比較安全的方式。在項目開發時,若是能重復使用這些已經設計良好的場景,將會減少許多開發時間。更多的優點將在后續章節中說明。本書范例場景的規劃在本書范例中,《P級陣地》規劃了3個場景,如圖3-4所示。 圖3-4 《P級陣地》規劃的3個場景? 開始場景(StarScene):GameLoop游戲對象(GameObject)的所在,游戲啟動及相關游戲設置的加載。? 主畫面場景(MainMenuScene):顯示游戲名稱和“開始”按鈕。? 戰斗場景(BattleScene):游戲主要執行的場景。3.1.2 游戲場景可能的實現方式實現Unity3D的場景轉換較為直接的方式如下:Listing 3-1 一般場景控制的寫法public class SceneManager{ private string m_state = "開始"; // 改換場景 public void ChangeScene(string StateName) { m_state = StateName;
switch(m_state) { case "菜單": Application.LoadLevel(“MainMenuScene”); break; case "主場景": Application.LoadLevel(“GameScene”); break; } } // 更新 public void Update() { switch(m_state) { case "開始": //... break; case "菜單": //... break; case "主場景": //... break; } }}
上述的實現方式會有以下缺點:只要增加一個狀態,則所有switch(m_state)的程序代碼都需要增加對應的程序代碼。與每一個狀態有關的對象,都必須在SceneManager類中被保留,當這些對象被多個狀態共享時,可能會產生混淆,不太容易識別是由哪個狀態設置的,造成游戲程序調試上的困難。每一個狀態可能使用不同的類對象,容易造成SceneManager類過度依賴其他類,讓SceneManager類不容易移植到其他項目中。為了避免出現上述缺點,修正的目標會希望使用一個“場景類”來負責維護一個場景,讓與此場景相關的程序代碼和對象能整合在一起。這個負責維護的“場景類”,其主要工作如下:? 場景初始化;? 場景結束后,負責清除資源;? 定時更新游戲邏輯單元;? 轉換到其他場景;? 其他與該場景有關的游戲實現。由于在范例程序中我們規劃了3個場景,所以會產生對應的3個“場景類”,但如何讓這3個“場景類”相互合作、彼此轉換呢?我們可以使用GoF的狀態模式(State)來解決這些問題。3.2 狀態模式(State)狀態模式(State),在多數的設計模式書籍中都會提及,它也是游戲程序設計中應用最頻繁的一種模式。主要是因為“狀態”經常被應用在游戲設計的許多環節中,包含AI人工智能狀態、賬號登錄狀態、角色狀態……3.2.1 狀態模式(State)的定義狀態模式(State),在GoF中的解釋是:“讓一個對象的行為隨著內部狀態的改變而變化,而該對象也像是換了類一樣”。如果將GoF對狀態模式(State)的定義改以游戲的方式來解釋,就會像下面這樣:“當德魯伊(對象)由人形變化為獸形狀態(內部狀態改變)時,他所施展的技能(對象的行為)也會有所變化,玩家此時就像是在操作另一個不同的角色(像是換了類)”。“德魯伊“是一種經常出現在角色扮演游戲(RPG)中的角色名稱。變化外形是他們常使用的能力,通過外形的變化,使德魯伊具備了轉換為其他形體的能力,而變化為“獸形”是比較常見的游戲設計。當玩家決定施展外形轉換能力時,德魯伊會進入“獸形狀態”,這時候的德魯伊會以“獸形”來表現其行為,包含移動和攻擊施展的方式;當玩家決定轉換回人形時,德魯伊會復原為一般形態,繼續與游戲世界互動。所以,變化外形的能力可以看成是德魯伊的一種“內部狀態的轉換”。通過變化外形的結果,角色表現出另外一種行為模式,而這一切的轉化過程都可以由德魯伊的內部控制功能來完成,玩家不必理解這個轉化過程。但無論怎么變化,玩家操作的角色都是德魯伊,并不會因為他內部狀態的轉變而有所差異。當某個對象狀態改變時,雖然它“表現的行為”會有所變化,但是對于客戶端來說,并不會因為這樣的變化,而改變對它的“操作方法”或“信息溝通”的方式。也就是說,這個對象與外界的對應方式不會有任何改變。但是,對象的內部確實是會通過“更換狀態類對象”的方式來進行狀態的轉換。當狀態對象更換到另一個類時,對象就會通過新的狀態類,表現出它在這個狀態下該有的行為。但這一切只會發生在對象內部,對客戶端來說,不需要了解這些狀態轉換的過程及對應的方式。3.2.2 狀態模式(State)的說明狀態模式(State)的結構如圖3-5所示。 圖3-5 狀態模式的結構圖參與者的說明如下:? Context(狀態擁有者)? 是一個具有“狀態”屬性的類,可以制定相關的接口,讓外界能夠得知狀態的改變或通過操作讓狀態改變。? 有狀態屬性的類,例如:游戲角色有潛行、攻擊、施法等狀態;好友上線、脫機、忙碌等狀態;GoF使用TCP聯網為例,有已連接、等待連接、斷線等狀態。這些類中會有一個ConcreteState[X]子類的對象為其成員,用來代表當前的狀態。? State(狀態接口類):制定狀態的接口,負責規范Context(狀態擁有者)在特定狀態下要表現的行為。? ConcreteState(具體狀態類)? 繼承自State(狀態接口類)。? 實現Context(狀態擁有者)在特定狀態下該有的行為。例如,實現角色在潛行狀態時該有的行動變緩、3D模型變半透明、不能被敵方角色察覺等行為。3.2.3 狀態模式(State)的實現范例首先定義Context類:Listing 3-2 定義Context類(State.cs)public class Context{ State m_State = null;
public void Request(int Value) { m_State.Handle(Value); }
public void SetState(State theState ) { Debug.Log ("Context.SetState:" theState); m_State = theState; }}Context類中,擁有一個State屬性用來代表當前的狀態,外界可以通過Request方法,讓Context類呈現當前狀態下的行為。SetState方法可以指定Context類當前的狀態,而State狀態接口類則用來定義每一個狀態該有的行為:Listing 3-3 State類(State.cs)public abstract class State{ protected Context m_Context = null; public State(Context theContext) { m_Context = theContext; } public abstract void Handle(int Value);}在產生State類對象時,可以傳入Context類對象,并將其指定給State的類成員m_Context,讓State類在后續的操作中,可以獲取Context對象的信息或操作Context對象。然后定義Handle抽象方法,讓繼承的子類可以重新定義該方法,來呈現各自不同的狀態行為。定義3個繼承自State類的子類:Listing 3-4 定義3個狀態(State.cs)// 狀態Apublic class ConcreteStateA : State{ public ConcreteStateA(Context theContext):base(theContext) {}
public override void Handle (int Value) { Debug.Log ("ConcreteStateA.Handle"); if( Value > 10) m_Context.SetState( new ConcreteStateB(m_Context)); }}
// 狀態Bpublic class ConcreteStateB : State{ public ConcreteStateB(Context theContext):base(theContext) {}
public override void Handle (int Value) { Debug.Log ("ConcreteStateB.Handle"); if( Value > 20) m_Context.SetState( new ConcreteStateC(m_Context)); } }
// 狀態Cpublic class ConcreteStateC : State{ public ConcreteStateC(Context theContext):base(theContext) {} public override void Handle (int Value) { Debug.Log ("ConcreteStateC.Handle"); if( Value > 30) m_Context.SetState( new ConcreteStateA(m_Context)); } }上述3個子類,都要重新定義父類State的Handle抽象方法,用來表示在各自狀態下的行為。在范例中,我們先讓它們各自顯示不同的信息(代表當前的狀態行為),再按照本身狀態的行為定義來判斷是否要通知Context對象轉換到另一個狀態。Context類中提供了一個SetState方法,讓外界能夠設置Context對象當前的狀態,而所謂的“外界”,也可以是由另一個State狀態來調用。所以實現上,狀態的轉換可以有下列兩種方式:? 交由Context類本身,按條件在各狀態之間轉換;? 產生Context類對象時,馬上指定初始狀態給Context對象,而在后續執行過程中的狀態轉換則交由State對象負責,Context對象不再介入。筆者在實現時,大部分情況下會選擇第2種方式,原因在于:狀態對象本身比較清楚“在什么條件下,可以讓Context對象轉移到另一個State狀態”。所以在每個ConcreteState類的程序代碼中,可以看到“狀態轉換條件”的判斷,以及設置哪一個ConcreteState對象成為新的狀態。每個ConcreteState狀態都可以保持自己的屬性值,作為狀態轉換或展現狀態行為的依據,不會與其他的ConcreteState狀態混用,在維護時比較容易理解。因為判斷條件及狀態屬性都被轉換到ConcreteState類中,故而可縮減Context類的大小。4個類定義好之后,我們可以通過測試范例來看看客戶端程序會怎樣利用這個設計:Listing 3-5 State的測試范例(StateTest.cs) void UnitTest() { Context theContext = new Context(); theContext.SetState( new ConcreteStatA(theContext ));
theContext.Request( 5 ); theContext.Request( 15 ); theContext.Request( 25 ); theContext.Request( 35 ); }首先產生Context對象theContext,并立即設置為ConcreteStateA狀態;然后調用Context類的Request方法,并傳入作為“狀態轉換判斷”用途的參數,讓當前狀態(ConcreteStateA)判斷是否要轉移到ConcreteStateB;調用幾次Request方法,并傳入不同的參數。從輸出的信息中可以看到,Context對象的狀態由ConcreteStateA按序轉換到ConcreteStateB、 ConcreteStateC狀態,回到ConcreteStateA狀態。 State測試范例產生的信息 Context.SetState:DesignPattern_State.ConcreteStateA ConcreteStateA.Handle ConcreteStateA.Handle Context.SetState:DesignPattern_State.ConcreteStateB Context.SetState:DesignPattern_State.ConcreteStateC Context.SetState:DesignPattern_State.ConcreteStateA3.3 使用狀態模式(State)實現游戲場景的轉換在Unity3D的環境中,游戲只會在一個場景中運行,所以我們可以讓每個場景都由一個“場景類”來負責維護。此時,如果將場景類當成“狀態”來比喻的話,那么就可以利用狀態模式(State)的轉換原理,來完成場景轉換的功能。由于每個場景所負責執行的功能不同,通過狀態模式(State)的狀態轉移,除了可以實現游戲內部功能的轉換外,對于客戶端來說,也不必根據不同的游戲狀態來編寫不同的程代碼,同時也減少了外界對于不同游戲狀態的依賴性。而原本的Unity3D場景轉換判斷功能,可以在各自的場景類中完成,并且狀態模式(State)同時間也只會讓一個狀態存在(同時間只會有一個狀態在運行),因此可以滿足Unity3D執行時只能有一個場景(狀態)存在的要求。3.3.1 SceneState的實現《P級陣地》的場景分成3個:開始場景(StarScene)、主畫面場景(MainM