This document is about: QUANTUM 2
SWITCH TO

Input


Available in the Gaming Circle and Industries Circle
Circle

Overview

Input in fighting games requires quick transmission between players, precision and the ability to track the state input stream over a certain period of time rather than simply consuming it.

This page will go over which of player input is captured by Unity and how these values are subsequently evaluated and computed in Quantum.

Input Struct

It is generally recommended to keep the input struct as small as possible to keep the bandwidth low. This is pushed to the extreme in the Fighting Sample by holding a single enum InputFlag as part of the input.

chsarp

input {
  InputFlag inputFlag;
}

The InputFlag enum definition explicitly assigns power of two values thus allowing the InputFlag to be manipulated using bitwise operations. This enables an InputFlag to hold multiple input values simultaneously.

C#

// Input flags used to represent the directional and button inputs
enum InputFlag {
  NONE = 0,
  Left = 1,
  Right = 2,
  Down = 4,
  Up = 8,
  LP = 16,  // light punch
  LK = 32,  // light kick
  HP = 64,  // heavy punch
  HK = 128, // heavy kick
  Directions = 15, // Used by the systems to extract the input directions from the input struct
  Buttons = 240, // Used by the systems to extract the button input from the input struct
}

Unity Side

The Fighting template implements two different input interfaces, mobile (UI Buttons) and pc (keyboard and gamepad). Depending on the platform the build is running on, one or the other input will be taken into account.

Although the input interfaces vary, the input logic remains the same across devices.

Input Logic

The input logic is set up in the QuantumDemoInput.cs script and subscribes to the PollInput callback.

Keyboard Input

Each button set up in the KeycodeSet struct in QuantumDemoInput.cs is represented by a boolean value. When the boolean evaluates to true, the value held by the InputFlag parameter in the Input struct is updated accordingly.

C#

if (UInput.GetKey(Left)){
   input.inputFlag |= InputFlag.Left;
}

Mobile Input

The mobile input is split into two parts; a UI script called MobileJoystick which handles the button press’ value and the logic in QuantumDemoInput.cs which holds references to all active MobileJoystick scripts and consumes their value when the input is polled.

MobileJoystick presents two flag parameters to allow for movement in all 8 directions; diagonal up right is represented by the Quantum.InputFlag.Up and Quantum.InputFlag.Right flag. If the flag1 and flag2 are set to the same value, then it is considered a unique input. Bitwise operations allow for the value assignment to remain identical regardless of each UI button’s settings.

C#

public class MobileJoystick : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler{
   public Quantum.InputFlag flag1;
   public Quantum.InputFlag flag2;
   public Quantum.InputFlag FlagValue { get; private set; }

   public void OnPointerEnter(PointerEventData eventData){
       FlagValue = flag1 | flag2;
   }

   public void OnPointerExit(PointerEventData eventData){
       FlagValue = Quantum.InputFlag.NONE;
   }
}

QuantumDemoInput simply iterates over the array of known MobileJoystick scripts and uses a bitwise or to add together the values held by each button.

C#

for (int i = 0; i < mobileButtons.Length; i++){
   input.inputFlag |= mobileButtons[i].FlagValue;
}

Quantum Side

On the simulation side (Quantum), the input needs to be tracked across multiple frames to allow for both combos triggered by sequential inputs over several frames and charge attacks requiring a continuous button press.

The input sequences are defined at edit time using the InputCommandData Asset and tracked at runtime using the InputBuffer component.

InputCommandData

The InputCommandDataBase is an abstract asset class. It is used to define concrete assets that will be implementing TestCmdPair() specifically to match their needs.

C#

public abstract unsafe partial class InputCommandDataBase{
   /// The parameter set for the forward and backward version of the input.  Should be a fixed point parameter.
   public CAParameters forDirectionParameter;
   public CAParameters backDirectionParameter;

   /// The forward and backward version of the command
   public InputFlag[] forDirectionInputSequence;
   public InputFlag[] backDirectionInputSequence;

   public void TestCmdPair(Frame f, InputBuffer* i, int side, CustomAnimator* a, ref QList<InputValue> dSeq);
   protected abstract bool TestCmd(Frame f, InputBuffer* iB, int side, InputFlag[] flagArray, ref QList<InputValue> directionalSequence);
}

The concrete implementation included in the Fighting Sample are:

  • InputCommandData; and,
  • ChargeInputCommandData.

InputCommandData is used to activate special moves that players trigger by performing a sequence of inputs. ChargeInputCommandData are used to define input sequences which require charging up over several frame by holding the same input.

InputBuffer

Input needs to be tracked for each player separately as part of the frame. The most convenient and flexible way to go about this is by having a component on each player controlled entity dedicated to this purpose; that is the approach used by the Fighting Sample.

The InputBuffer component is used to keep track of 3 different things:

  • the input direction;
  • the button presses; and,
  • the amount of time for which an input has been pressed or released.

The InputBuffer is updated by the InputBufferSystem.

C#

component InputBuffer{
  list<InputValue> directionalSequence;
  list<InputValue> buttonSequence;
  array<InputTimer>[8] inputTimers;
}

The InputValue type is a simple struct defined in the DSL. It contains a copy of the InputFlag and the frame number it is associated with. Since directional input and action button sequences are manipulated separately, they are also tracked separately in the InputBuffer component; this is achieved by keeping two InputValue lists.

C#

struct InputValue{
  InputFlag inputFlag;
  Int32 frame;
}

The amount of time a certain input has been pressed or released is tracked using the InputTimer struct.

C#

struct InputTimer{
  FP timeDown;
  FP timeUp;
}

InputBufferSystem

The InputBufferSystem has two purposes:

  • Check the current InputFlag in the input struct; and,
  • Update the InputBuffer components.

Before evaluating the current input’s InputFlag, the directional and button inputs are separated.

C#

InputFlag dirFlag = input->inputFlag & ~(InputFlag.Buttons);
InputFlag btnFlag = input->inputFlag & ~(InputFlag.Directions);

This split facilitates tracking the actions taken by the player to trigger move sets. First UpdateInput() updates the InputTimer in the InputBuffer component by checking whether an input is being held down:

  • Pressed: the InputTimer.TimeDown for that input is increased.
  • Not Pressed: if the input is not held down during that frame and the reset tolerance for it has been exceeded, the InputTimer.TimeDown for that input is reset. This is done in order to enable charge moves (e.g.: charge back for a few frames, then press forward and a button).

After all the InputTimers have been updated, the InputBufferSystem updates the CustomAnimators parameters with those new values.

The last step, the InputBufferSystem extracts the input sequence list held by the InputBufferComponent. The directional values of the current input are checked against the previous InputValues held in the component and updated if the two values differ. The list is then passed as a parameter to the TestCmdPair() method in all InputCommandData assets associated with each players’ fighter.

This implementation combines the aforementioned aspects to allow players to enter a sequence of inputs with a tolerance between the changes (approx. 10 frames by default).

For example, if the player’s fighter holds for instance the InputCmd_QuarterCircle asset, this would trigger the QCF_F animation parameters and execute the associated behaviours if the input sequence was made of these values:

C#

{frame = 80, value = 4}  // Down
{frame = 90, value = 6}  // Down + Right
{frame = 100, value = 2} // Right

This would then register as a positive input sequence since the command buffer is 32 inputs.

Triggering Special Moves

Special moves are set up at edit time in the Unity Animator and baked into the CustomAnimatorGraph Asset. At runtime, the InputBufferSystem simply updates the parameters of the CustomAnimator components. If the transition parameters are all fulfilled when the CustomAnimatorUpdater calls Update() on the CustomAnimator components (see CustomAnimatorSystem), then the moves are triggered. Upon leaving the current state and entering the new state, the SignalOnAnimatorStateExit and ISignalOnAnimatorStateEnter are fired for the respective states.

N.B.: The parameters updated in the InputBufferSystem are all of type FixedPoint. This is done so players who do not perform new inputs for several frames, do not accidentally perform an attack based on input cached from prior frames.

fighting sample special move trigger parameters
Transition Triggering Paramaters for the Uppercut_LP Special Move.

The Uppercut_LP state for example can be triggered from AnyState as long as the following conditions are fulfilled simultaneously:

  • The motion on the z-axis (ZMotionF) is greater than 0 and less than 0.15;
  • The light punch input time is greater than 0 and less than 0.15;
  • The character is currently grounded (i.e. InAir is false); and,
  • The character is currently in a state that allows them to perform a special attack (i.e. CanSpecialAtk is true).

While the FixedPoint parameters are continuously updated in the InputBufferSystem, either directly in the system or as a result of the input sequence passed into the InputCommandData; the boolean parameters on the other hand are part of the FighterAnimatorState and are set when the character first enters the current state.

Back to top