Note - I will be using frames to describe time in this example because it’s easier than raw time. For reference, 60 of these make up one second - literally 60 fps.
In this example, each frame allows only two input states; Attack pressed or Attack not pressed.
So to do this, you use a state machine, and track the time of things happening.
For your example, let’s give four states - Idle, Attack, Attack Combo, Delayed Attack, Rapid Attack
We set rules on how we can transition:
- Idle can only transition into Attack and only if the user has pressed.
- Attack Combo can only be access through attack - player must be in Attack already to transition into it.
- Delayed Attack can only be accessed through Attack.
- Rapid Attack can only be accessed through Attack Combo.
- If a player is already in an attack of any kind, they are locked in that attack animation for a set duration.
So in this example, all you need to do is watch the timing of the state to figure out what they do next.
e.g. player is in Attack, and at frame 10 they activate the input. Our rules say if it’s before frame 5, we do nothing because they’re still in the Attack animation. At frame 6, they can transition into Attack Combo. At frame 15, they can no longer access Attack Combo, but they can access Delayed Attack. At frame 30, if the player did nothing, transition them back to Idle.
What about Rapid Attack? How can you distinguish accessing another attack combo from a rapid mash? The trick is an input buffer.
You use an input buffer to match patterns, like a fighting game.
For instance - let’s represent your attack button pressed as O and unpressed as -.
Each frame, we take our input buffer (I will use a string to represent it here), remove the last entry and add on the new entry. It’s a queue!
Then, we only need to match it to find out what state to put our player in.
I’m going to use an input buffer of 6 frames, which is why I will use a 6 length string to represent it.
XXXXXX - player is doing nothing
XOXXXX - player hit attack on frame 2
XOXXXO - player hit attack on frame 2 and 6. This means the player pressed attack, waited for 3 frames, then pressed again.
XOXOXO - player is mashing attack like a madman
So using this, you can find out if they pressed once, or if they’re actively smashing that attack button.
Check this input buffer while they’re locked into their animations to see what they’ll do next, and queue it up.
It also has the neat benefits that you can do things like dragon punch inputs for some very complex input patterns.
To integrate it with MVC, you’d have the animation display for view. Inputs and animation timing must be handled through your controller. Your model contains the information on how cancels happen, and the current state. Roblox doesn’t like it but it’s really important you decouple animation tracks from logic. Don’t rely on marker events!!
Hope this explains it a bit