HOME ABOUT CONTACT

1. 前提top

繼「角色移動」之後,緊接著我實作了 Combo 的功能。為什麼實現這個功能,它比我想像中的規模大得多了,包含有 Combo Tries、FSM 以及相關延伸的 Handler,因為這些項目,我不得不將原本的架構做一些改造調整。

這篇主要以「角色跑步」來做些實作記錄,也就是如何連按兩次同個方向鍵之後,角色就會跑起來。在這個過程中,我主要遇到了兩個問題,第一是要如何在 Combo Buffer 中區分是按著方向鍵讓角色走起來,還是連按兩次方向鍵讓角色跑起來?以及第二個問題是,如何區分 Handlers 與 FSM 之間的職責?

2. 問題描述top

在完成「角色移動」的功能之後,我們需要有一個 Combo Buffer 來實現組合按鍵的操作。然而,當 Combo Buffer 無法區分連按和按著按鍵時,就會出現問題,如下動圖: Game Engine Combo Problem

你會看到幾個動作是角色走一小段之後,就會跑起來了,因為此時系統誤判為使用者連按了兩次方向鍵。

而另一個問題是屬於系統架構設計的問題,由於實現 Combo 這個功能,規模增幅了不小,模組的職權應該如何劃分和設計,也是這次主題的一大難關。

3. 解決方法與架構設計top

3.1 Combo 實現

首先,連按和按著按鍵的區分問題,如果我們只考慮以下的 Stack 狀況,那肯定是行不通的:


D, D = 按著或連按兩次都有可能 = 人物先往右走,再跑起來
A, A = 按著或連按兩次都有可能 = 人物先往左走,再跑起來
                    

所幸 SDL 提供了兩種 Keyboard Event 的種類,分別是 Key Down (Pressed) 和 Key Up (Released),這讓我們可以將連按兩次的 Stack 變為:


D_Down, D_Up, D_Down = 連按兩次 = 人物往右跑
D_Down, D_Down       = 按著     = 人物往右走

A_Down, A_Up, A_Down = 連按兩次 = 人物往左跑
A_Down, A_Down       = 按著     = 人物往左走
                    

3.1.1 Combo Tries

雖然上面我提到了 Stack,但主要是強調「後進先出」的順序,以及它是作為判斷的參數結構。也就是使用者最近(後)一次按下的按鍵,都應該作為判斷的開頭,這樣才能達到即時判斷使用者所輸入 Combo 的正確性。

例如想像一下,如果使用者先是讓角色往左邊走,然後連按了兩次右,那麼 Stack 可能會是如下:


A_Down, A_Down, A_Up, D_Down, D_Up, D_Down
                    

如果是以 Queue 的先進先出(從左到右)順序,這樣就不符合「跑起來」的組合鍵。當然你可以用暴力解得到最後三個輸入來判斷是「跑起來」,但最簡便的方式就是用 Stack 的順序來判斷,一來是速度較快,二來是較為合理。

然而,真正的主角是 Combo Tries,如上述提到,Stack 只是 Combo Tries 的參數結構,也就是說我們需要將 Stack 丟給 Combo Tries 去做判斷。

個人認為,Combo Tries 結構與 Union Find 結構作為有向圖來看,概念有點相反,如下圖示: Combo Tries and Union Find

在系統設計上,每個 Combo Tries 節點有兩個重要資訊,一個是「需要執行的 Event 指標」,另一個是「轉換的目標狀態」,因此整個結構會是如下圖示:
Combo Tries Part1

3.2 Handlers 與 FSM 架構設計

有了從 Combo Tries Node 得到的 Event 和 State 之後,我們若直接讓 Event 包辦了所有事項,例如改變角色方向和位置,那麼 FSM 與 Keyboard Event Handler 之間會變得難以處理:
i. 當角色跑起來了,要如何讓 Combo Buffer 在下一幀保持角色跑步的狀態?
ii. 在只有 Keyboard Event Handler 與 FSM 兩者的情況下,角色若順利持續跑起來,又要如何允許在奔跑的過程中改變奔跑方向?

3.2.1 Keyboard Event Handler

在「角色移動」文章裡,我有提及這個類別,並且讓這個類別直接去執行「角色移動」的 Event。然而,在 Combo 的實現中,我將權責拆分了一些出去,讓 Keyboard Event Handler 僅改變了物件的方向,而不改變物件位置。

3.2.2 FSM

FSM(有限狀態機)主要負責幾件事情,包含讓使用者建立狀態有向圖、判斷狀態是否可以轉換並轉換物件狀態。因此當 Keyboard Event Handler 從 Combo Tries 節點拿到「轉換的目標狀態」之後,就須交由 FSM 來做物件狀態轉換的判斷,狀態轉換可能如下圖示:
FSM

FSM 我設計為 Singleton Pattern,目前我認為物件的狀態判斷和轉換應該統一由一個實體來完成。

3.2.3 Object Action Event Handler

在狀態機確認物件狀態是否轉換之後,接著由 Object Action Event Handler 來執行物件狀態對應的動作。如果物件狀態是 Walk,那就用「角色移動」來變更物件位置;如果物件狀態是 Run,就執行「角色奔跑」來變更物件位置,也就是由 Keyboard Event Handler 拆分出來的權責。

這其實和 Animation Handler 是一致的,在遊戲主迴圈中,Event Handlers 會調用 execute,讓底下所有的 Handler 全部執行起來,這包含了上述所有內容,以及目前文章還未提及的 Animation Handler。

從上述可以得知,Event Handlers (或稱 Manager) 的設計是以 Strategy Pattern 來實現的,所有的 Handler 都繼承並實現了 execute() 方法,並且在遊戲主迴圈中被調用。

3.3 UML and Summary

總結來說,處理的順序如下: Step 1: Keyboard Event Handler 用 Combo Buffer 來取得 Combo Tries Node,並且若 Node 有變更物件方向的 Event,則執行物件方向變更。
Step 2: 將 Combo Tries Node 的 State 丟給 FSM 判斷是否符合使用者設計的狀態轉換規則,若符合,則進行狀態轉換。
Step 3: Object Action Event Handler 從 Registry 取得物件狀態之後,進行對應的操作。

UML 設計如下: Combo UML

最後成果如下動圖: Game Engine Combo Problem Solved

Last updated: