HOME ABOUT CONTACT

1. Premisetop

Following the "Character Movement" feature, I then implemented a Combo feature. The scope of this feature turned out to be much larger than expected, including Combo Tries, FSM, and related extended handlers. Because of these additions, I had to refactor and adjust part of the original architecture.

This article mainly records implementation notes around "character running", specifically how the character starts running after pressing the same direction key twice in a row. During this process, I encountered two main issues. The first one is how to distinguish in the Combo Buffer between holding a direction key (walk) and double-tapping a direction key (run). The second one is how to separate responsibilities between Handlers and FSM.

2. Problem Descriptiontop

After finishing the "Character Movement" feature, we needed a Combo Buffer to support key combinations. However, if the Combo Buffer cannot distinguish double-tap and hold input, the following issue appears: Game Engine Combo Problem

You can observe that in several cases the character walks a short distance and then starts running, because the system incorrectly interpreted the input as a double-tap.

The other issue is architecture-related. Since implementing Combo significantly increased system complexity, deciding how to split module responsibilities became one of the biggest challenges in this topic.

3. Solution and Architecture Designtop

3.1 Combo Implementation

First, if we only consider the following Stack states, distinguishing double-tap from hold is impossible:


D, D = could be hold or double-tap = character walks right first, then runs
A, A = could be hold or double-tap = character walks left first, then runs
                    

Fortunately, SDL provides two types of keyboard events: Key Down (Pressed) and Key Up (Released). This allows us to represent double-tap sequences as:


D_Down, D_Up, D_Down = double-tap = character runs right
D_Down, D_Down       = hold       = character walks right

A_Down, A_Up, A_Down = double-tap = character runs left
A_Down, A_Down       = hold       = character walks left
                    

3.1.1 Combo Tries

Although I mentioned Stack above, the key point is the "last in, first out" order, and using it as the parameter structure for judgment. The most recent key input should be the starting point for validation so we can evaluate combo correctness in real time.

For example, if the player first moves left and then double-taps right, the stack may look like this:


A_Down, A_Down, A_Up, D_Down, D_Up, D_Down
                    

If we use Queue order (first in, first out, left to right), this does not directly match the "run" combo. You could still brute-force and inspect the last three inputs, but evaluating with stack order is both faster and more reasonable.

However, the real core is Combo Tries. As mentioned above, Stack is only the parameter structure for Combo Tries, meaning we pass Stack into Combo Tries for validation.

In my view, if you treat them as directed graphs, Combo Tries and Union Find are conceptually somewhat opposite: Combo Tries and Union Find

In system design, each Combo Tries node has two critical pieces of data: an event pointer to execute, and a target state to transition to. The overall structure is shown below:
Combo Tries Part1

3.2 Handlers and FSM Architecture Design

Once we obtain Event and State from a Combo Tries node, if Event directly handles everything (for example, both direction and position changes), FSM and Keyboard Event Handler become difficult to manage:
i. Once the character starts running, how can Combo Buffer preserve running behavior in the next frame?
ii. With only Keyboard Event Handler and FSM, if running continues correctly, how can we still allow direction changes while running?

3.2.1 Keyboard Event Handler

In the "Character Movement" article, I mentioned this class and let it directly execute the movement event. In the Combo implementation, I split responsibilities so Keyboard Event Handler only changes object direction, not object position.

3.2.2 FSM

FSM (Finite State Machine) is mainly responsible for several tasks: allowing users to define a directed state graph, checking whether state transitions are valid, and applying state transitions. Therefore, after Keyboard Event Handler gets the target state from a Combo Tries node, FSM must evaluate whether the transition is allowed. A possible state graph is shown below:
FSM

3.2.3 Object Action Event Handler

After FSM confirms state transitions, Object Action Event Handler executes behavior based on the current object state. If state is Walk, it executes "character movement" to update position; if state is Run, it executes "character running" to update position. This is exactly the responsibility extracted from Keyboard Event Handler.

This is also consistent with Animation Handler. In the game main loop, Event Handlers call execute so all handlers run each frame, including all parts discussed above and the Animation Handler not yet covered in this article.

3.3 UML and Summary

In summary, the processing sequence is: Step 1: Keyboard Event Handler uses Combo Buffer to get a Combo Tries node. If the node contains an event for direction change, execute direction update.
Step 2: Pass the Combo Tries node state to FSM and check whether it matches user-defined transition rules. If valid, perform state transition.
Step 3: Object Action Event Handler reads object state from Registry and performs the corresponding action.

UML design is shown below: Combo UML

Final result is shown in the animation below: Game Engine Combo Problem Solved

Last updated: