1. 前提
2. 問題描述
3. 解決方法與架構設計
3.1 Combo 實現
3.1.1 Combo Tries
3.2 Handlers 與 FSM 架構設計
3.2.1 Keyboard Event Handler
3.2.2 FSM
3.2.3 Object Action Event Handler
3.3 UML and Summary
繼「角色移動」之後,緊接著我實作了 Combo 的功能。為什麼實現這個功能,它比我想像中的規模大得多了,包含有 Combo Tries、FSM 以及相關延伸的 Handler,因為這些項目,我不得不將原本的架構做一些改造調整。
這篇主要以「角色跑步」來做些實作記錄,也就是如何連按兩次同個方向鍵之後,角色就會跑起來。在這個過程中,我主要遇到了兩個問題,第一是要如何在 Combo Buffer 中區分是按著方向鍵讓角色走起來,還是連按兩次方向鍵讓角色跑起來?以及第二個問題是,如何區分 Handlers 與 FSM 之間的職責?
在完成「角色移動」的功能之後,我們需要有一個 Combo Buffer 來實現組合按鍵的操作。然而,當 Combo Buffer 無法區分連按和按著按鍵時,就會出現問題,如下動圖:
你會看到幾個動作是角色走一小段之後,就會跑起來了,因為此時系統誤判為使用者連按了兩次方向鍵。
而另一個問題是屬於系統架構設計的問題,由於實現 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 = 按著 = 人物往左走
雖然上面我提到了 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 節點有兩個重要資訊,一個是「需要執行的 Event 指標」,另一個是「轉換的目標狀態」,因此整個結構會是如下圖示:
有了從 Combo Tries Node 得到的 Event 和 State 之後,我們若直接讓 Event 包辦了所有事項,例如改變角色方向和位置,那麼 FSM 與 Keyboard Event Handler 之間會變得難以處理:
i. 當角色跑起來了,要如何讓 Combo Buffer 在下一幀保持角色跑步的狀態?
ii. 在只有 Keyboard Event Handler 與 FSM 兩者的情況下,角色若順利持續跑起來,又要如何允許在奔跑的過程中改變奔跑方向?
在「角色移動」文章裡,我有提及這個類別,並且讓這個類別直接去執行「角色移動」的 Event。然而,在 Combo 的實現中,我將權責拆分了一些出去,讓 Keyboard Event Handler 僅改變了物件的方向,而不改變物件位置。
FSM(有限狀態機)主要負責幾件事情,包含讓使用者建立狀態有向圖、判斷狀態是否可以轉換並轉換物件狀態。因此當 Keyboard Event Handler 從 Combo Tries 節點拿到「轉換的目標狀態」之後,就須交由 FSM 來做物件狀態轉換的判斷,狀態轉換可能如下圖示:
在狀態機確認物件狀態是否轉換之後,接著由 Object Action Event Handler 來執行物件狀態對應的動作。如果物件狀態是 Walk,那就用「角色移動」來變更物件位置;如果物件狀態是 Run,就執行「角色奔跑」來變更物件位置,也就是由 Keyboard Event Handler 拆分出來的權責。
這其實和 Animation Handler 是一致的,在遊戲主迴圈中,Event Handlers 會調用 execute,讓底下所有的 Handler 全部執行起來,這包含了上述所有內容,以及目前文章還未提及的 Animation Handler。
總結來說,處理的順序如下:
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 設計如下:
最後成果如下動圖:
Last updated: