Project Devlog : Hourglass - Input Buffering
Intro:
Hi! This is Tango, programmer of the action game project [Hourglass].
Itch page:https://yixing-sheridan.itch.io/hourglass (Game page is run by our Artist)
I would like to introduce how to reach [Input buffering] mechanic during the development of our game project in this devlog post.
Why?
The reason that why we need Input Buffering in our game project is that:
-- Hourglass is an action game, there is a combo attack system designed for our controllable character. Instead of keeping focus on the combo attack timing during the combat, input buffering will make it easy for players to combo attack.
-- Player will not only combo attack during the combat, but also other actions such as [Execution] [Dodging] [Transform Attack] [Other actions such as Using Health Potion]. Input buffering will set up a range before the transition of current action to read and prioritize whichever inputs from the player so that once upon the time when deciding which next action will be played, it will smoothly transit into the specific action from pre-inputs list.
Design:
In this IB(Input buffering) design, based on the Unity Engine development background, I will implement this function based on animation event.
During Section A of the animation, the system will read whichever inputs from the player then order the input list in to the preset priority. Animation will start to transit into the first condition of the input list from section A once entering Section B.
If there are not any inputs from A, at this moment, which ever input from the player will allow the animation to transition into the new animation from input during Section B.
However if the player does not make any input during both Section A and B, the animation will transit into its next after reaching its finish frame.
Implementation:
public class InputBuffering
{
public bool InputBufferingStart;
public bool ActionCancelStart;
List<int> InputList = new();
public void AddInput(int value)
=> InputList.Add(value);
public void ResetInputList()
=> InputList.Clear();
public int GetInputCode()
{...}
}
We need a InputBuffering class to process input data. The ActionCancelStart bool is used to check whether there is a existing pre-input or player is allowed to make any inputs.
Recommended by LinkedIn
Based on the current version of the game, I set up the priority of actions needed to interact with this IB mechanic.
Input Code: 1.Execution 2.TransformAtk 3.Dodge 4.RegAtk 2&3 has same priority
public class InputReader
{
bool InputCheck(InputAction.CallbackContext context, bool condition)
=> context.performed && condition;
void InputBufferingCheck(InputAction.CallbackContext context, int inputCode)
{
if (InputCheck(context, _playerFSM.InputBuffering.InputBufferingStart))
_playerFSM.InputBuffering.AddInput(inputCode);
}
public void OnRegularAttack(InputAction.CallbackContext context)
=>InputBufferingCheck(context, 4);
public void OnTransformAttack(InputAction.CallbackContext context)
=>InputBufferingCheck(context, 2);
public void OnDodge(InputAction.CallbackContext context)
=>InputBufferingCheck(context, 3);
public void OnExecution(InputAction.CallbackContext context)
=>InputBufferingCheck(context, 1);
}
public int GetInputCode()
{
int returnCode = 0;
if (InputList.Count < 1) //* No Input
return returnCode;
if (InputList.Contains(1)) //* Check if list contains Execution
{
returnCode = 1;
}
else if (!InputList.Contains(4)) //* Check if list contains no RegAtk
{
returnCode = InputList.Last();
}
else if (InputList.Distinct().Count() == 3) //* Check if list contains all 2,3,4
{
var filteredList = InputList.Where(x => x < 4).ToList();
returnCode = filteredList.Last();
}
else //* List contains 2,4 or 3,4 or 4,4 or 2/3/4
{
var orderedInputList = InputList.OrderBy(x => x).ToList();
returnCode = orderedInputList.First();
}
ResetInputList();
return returnCode;
}
In the input script, I identify each input action in specific index and update the GetInputCode logic in the InputBuffering script.
public class PlayerCustomAnimEvt : SingletonManager<PlayerCustomAnimEvt>
{
public void InputBufferingStart()
=> _playerFSM.InputBuffering.InputBufferingStart = true;
public void ActionCancelStart()
=> _playerFSM.InputBuffering.ActionCancelStart = true;
public void PlayerAnimExit()
=>_playerFSM.CanTransitAnim = true;
}
Then the next step is to add Animation Event by check InputBufferingStart, ActionCancelStart and AnimationExit conditions.
protected void ActionCancellationConditionCheck()
{
bool actionCancelStart = _playerFSM.InputBuffering.ActionCancelStart;
if (!actionCancelStart)
return;
_playerFSM.InputBuffering.InputBufferingStart = false;
//* Pre-Input Action Check
int inputCode = _playerFSM.InputBuffering.GetInputCode();
switch (inputCode)
{
case 0:
break;
case 1:
SwitchPlayerState(PlayerStates.Execution);
return;
case 2: //* Trans Atk
SwitchPlayerState(PlayerStates.TransformAttack);
return;
case 3: //* Dodge
OnDodgeAction();
return;
case 4: //* Reg Atk
TryComboAttack(_playerFSM.LastAttack);
return;
}
//* Instant Action Cancellation
if (InputReader.Instance.IsExecuting)
{
SwitchPlayerState(PlayerStates.Execution);
return;
}
if (InputReader.Instance.IsRegularAttacking)
{
TryComboAttack(_playerFSM.LastAttack);
return;
}
if (InputReader.Instance.IsTransformAttacking)
{
SwitchPlayerState(PlayerStates.TransformAttack);
return;
}
if (InputReader.Instance.IsDodging)
{
OnDodgeAction();
return;
}
//* Exit Anim
if (CanTransitAnim())
TransitAnimation();
}
The Final step is to do player's state check in script.
Conclusion:
Implementing an input buffering mechanic in Hourglass has significantly enhanced the fluidity and responsiveness of the combat system. By allowing the system to read and prioritize player inputs during specific animation phases, we’ve ensured that combo attacks, dodges, and other critical actions feel intuitive and seamless. This design reduces the cognitive load on players, enabling them to focus more on strategy and execution rather than precise timing.
Through this devlog, I’ve walked you through the reasoning, design, and implementation of input buffering in our game. From setting up priority-based input handling to leveraging Unity’s animation events, every step was taken to ensure this feature integrates smoothly into the gameplay experience.
If you’re working on an action game, I hope this devlog provides insight into how input buffering can be approached and the benefits it can bring to your game. Let me know your thoughts or if you have any questions about the implementation!