Master Modules Part Two : The Input Matrix
by Simon Love · 04/26/2016 (3:06 am) · 3 comments
The purpose of this Module is to accept input from a variety of devices and translate this information into an idealized, imaginary controller. Think of it as a virtual joystick which can be controlled via keyboard or physical joysticks.
You can grab the InputMatrix Module here!
In most 2D Games, mouse input is usually dedicated to UI-based manipulations, which is why mouse input is not part of the InputMatrix. It will be covered in more detail in our Master Modules Part Four : UI Base.
The Starter project.
Instead of having the module's create function call upon another method to initialize everything, I chose a more direct approach. While script files usually contain functions and methods which are called upon at a later time, one script file, /scripts/Init.cs contains code which exists outside of a function or method.
This means that when this file is executed via exec(...); the code that it contains is immediately run as if it were part of the create function itself. Let us look at the contents of this Init.cs file.
The process starts whenever the player presses a button or a thumbstick.
As you can see, Action and Modifier presses are handled by the same methods (InputMatrix.ActionOne()) regardless of input device.
A joystick's axis will return a value between -1.0 and +1.0. If we take the X axis as an example, extreme left would represent a value of -1.0 while the extreme right would represent a value of +1.0.
On a keyboard, an axis is mapped to at least two keys. Pressing A would thus represent the extreme left of the X axis while pressing D would represent the extreme right of the X axis.
Your game code shouldn't be worrying about what key or thumbstick is currently being pressed; it should only be aware of the current value of the X axis, regardless of the player's input device.
The InputMatrix's job is thus to translate these inputs into universal values on our idealized controller.
Here's how it works : When a key corresponding to an axis is pressed, the script verifies if the opposite key is already pressed. If that is the case, a boolean flag (XAXIS_DBL for the X axis) is raised and the key that was last pressed takes precedence. Upon the release of either key, if the boolean flag is raised, the key that is still being held down will take over.
Does your game code need to know that the player is holding both directions at the same time? No. Let the preparation phase filter out these input-related issues so that your game code stays focused on doing its job.
Whether for quantizing joystick axis input or handling special cases, the Preparation phase is crucial to ensure that the InputMatrix provides a coherent version of the current Input State.
Let's assume that %value in the above example is equal to 1 (button/key was pressed). This would have the same effect as typing the following :
As you can see from InputMatrix.Process() (in the /scripts/Process.cs script file), once we have updated the InputMatrix Object, we then post an Event using the EventManager which was created at the start.
This results in calling the method ::onProcess_EVT on all objects subscribed to the Event Manager.
This is where our next Module : The Control Object comes into play.
In that method, you can handle input by querying the InputMatrix.
For example, this would make your object move in the direction of input.
As an example, how would you handle remapping? Much easier to handle now than with your input tied into your game code directly!
How about pausing input for a cutscene? Would you simply pop the ActionMap or add a boolean flag so that the input is still being processed but not sent to the target objects? These challenges I leave up to you as my guess is that the solution will differ greatly from game to game.
Next Issue : Master Modules Part Three - The Control Object
Creating a new object by giving it a name allows you to refer to this particular object from anywhere in your project by using that name.
Step one, create the event manager :
You can grab the InputMatrix Module here!
The Idealized Controller
InputMatrix is nothing but a data structure which we can consult to determine the current state of input. I have voluntarily kept this idealized controller as simple as possible, featuring 2 Axes (X and Y), 2 Action buttons and 2 Modifier keys. You may modify the InputMatrix as needed to accommodate your game design.In most 2D Games, mouse input is usually dedicated to UI-based manipulations, which is why mouse input is not part of the InputMatrix. It will be covered in more detail in our Master Modules Part Four : UI Base.
Code Overview
This module starts by loading the scripts contained in /scripts, as explained inThe Starter project.
Instead of having the module's create function call upon another method to initialize everything, I chose a more direct approach. While script files usually contain functions and methods which are called upon at a later time, one script file, /scripts/Init.cs contains code which exists outside of a function or method.
This means that when this file is executed via exec(...); the code that it contains is immediately run as if it were part of the create function itself. Let us look at the contents of this Init.cs file.
new ScriptObject(InputMatrix){
YAXIS = 0;
...
};The InputMatrix ScriptObject is created, its variables set to their default state (zeroed out).InputMatrix.EVTMGR = new EventManager(){
queue = "InputManagerQueue";
};An Event Manager is also created, its ObjectID stored in a field of the InputMatrix object.InputMatrix.EVTMGR.registerEvent("Process_EVT");An event is then registered to our event manager. This will be touched upon briefly in this article but will be thoroughly explained and demonstrated in my next Article : The Control Object.%AM_KB = new ActionMap(IMKB_ActionMap);ActionMaps are then created for each input device and the ones we wish to use will be pushed to the stack. This push can also occur at a later time, depending on how you wish to handle input in your game.
The process starts whenever the player presses a button or a thumbstick.
Phase One : Preparation
At first glance, the preparation phase might seem superfluous. Its purpose is to give you a chance to modify input values before they are sent to the InputMatrix.Axis vs Buttons
//Keyboard input %AM_KB.bindObj(keyboard, "space", ActionOne, InputMatrix); //Joystick input %AM_JOY.bindObj(joystick0, "button0", ActionOne,InputMatrix);
As you can see, Action and Modifier presses are handled by the same methods (InputMatrix.ActionOne()) regardless of input device.
//Keyboard input %AM_KB.bindObj(keyboard, "a", XAxisLeft, InputMatrix); //Joystick input %AM_JOY.bindObj(joystick0, "xaxis", Xaxis_analog,InputMatrix);On the other hand. the axes are handled by different methods for joystick and keyboard input.
A joystick's axis will return a value between -1.0 and +1.0. If we take the X axis as an example, extreme left would represent a value of -1.0 while the extreme right would represent a value of +1.0.
On a keyboard, an axis is mapped to at least two keys. Pressing A would thus represent the extreme left of the X axis while pressing D would represent the extreme right of the X axis.
Your game code shouldn't be worrying about what key or thumbstick is currently being pressed; it should only be aware of the current value of the X axis, regardless of the player's input device.
The InputMatrix's job is thus to translate these inputs into universal values on our idealized controller.
The Keyboard Paradox
The Keyboard is the only device where a user could press opposite directions at the same time (a & d, for example). In this case the preparation phase handles the specifics for you before sending the results to the InputMatrix.Here's how it works : When a key corresponding to an axis is pressed, the script verifies if the opposite key is already pressed. If that is the case, a boolean flag (XAXIS_DBL for the X axis) is raised and the key that was last pressed takes precedence. Upon the release of either key, if the boolean flag is raised, the key that is still being held down will take over.
Does your game code need to know that the player is holding both directions at the same time? No. Let the preparation phase filter out these input-related issues so that your game code stays focused on doing its job.
Whether for quantizing joystick axis input or handling special cases, the Preparation phase is crucial to ensure that the InputMatrix provides a coherent version of the current Input State.
Phase Two : InputMatrix Update
Once we have made sure that our input values have been prepared, we call the InputMatrix's Process function. Let's take a look at the ActionOne Preparation Phase as an example.function InputMatrix::ActionOne(%this, %value)
{
%this.Process("ActionOne", %value);
}%value will equal 1 if the button or key was pressed. %value will equal 0 if the button or key was released.Let's assume that %value in the above example is equal to 1 (button/key was pressed). This would have the same effect as typing the following :
InputMatrix.ActionOne = 1;Proceeding in this way ensures that every input is treated sequentially and that no two inputs call conflicting functions simultaneously. Less bugs, smoother controls, happier dev.
Phase Three : InputMatrix Post Event
Now that our InputMatrix was updated with the most recent input, it is time to update our game logic accordingly.As you can see from InputMatrix.Process() (in the /scripts/Process.cs script file), once we have updated the InputMatrix Object, we then post an Event using the EventManager which was created at the start.
This results in calling the method ::onProcess_EVT on all objects subscribed to the Event Manager.
This is where our next Module : The Control Object comes into play.
Example of use
If you wish to use the InputMatrix concept by itself (without the Control Object Module), you must manually subscribe your player object (the object which should react to player input) to the Event Manager, as such:InputMatrix.EVTMGR.subscribe(MyObject, "Process_EVT");Make sure to define a method named onProcess_EVT on yout target object.
In that method, you can handle input by querying the InputMatrix.
For example, this would make your object move in the direction of input.
function MyObject::onProcess_EVT(%this, %data)
{
switch(InputMatrix.XAXIS)
{
case -1 : %this.setLinearVelocity(-5. 0);
case 0 : %this.setLinearVelocity(0,0);
case 1 : %this.setLinearVelocity(5.0);
}
}Even complex game code is made as simple and as elegant as this snippet thanks to the InputMatrix Module.Conclusion
The InputMatrix is very hard to explain in words but should be straightforward enough when you see it in action or read the code. I've kept it as general-purpose as possible so that you can play around with it and make it your own! Do not be afraid to modify it to suit your projects.As an example, how would you handle remapping? Much easier to handle now than with your input tied into your game code directly!
How about pausing input for a cutscene? Would you simply pop the ActionMap or add a boolean flag so that the input is still being processed but not sent to the target objects? These challenges I leave up to you as my guess is that the solution will differ greatly from game to game.
Next Issue : Master Modules Part Three - The Control Object
Glossary
- eval
The eval function is one of TorqueScript's most flexible tools. It takes a string and evaluates it as code. This means that you can mix and match parts of a string with variables to assemble commands which would be impossible to form otherwise. One example is when you type a command into the Console, pressing Enter calls eval on the text you've typed in.- ScriptObject
Contrary to a SceneObject (and derived classes such as Sprites, Particle Systems, etc.), a ScriptObject cannot be added to a Scene. It does not have physical properties or anything graphical related to it. Think of it as an object which holds data. Since it is not part of a Scene, the object will persist until it is manually destroyed or when the application ends. It can still be saved to Taml and could be used to hold Player Data for instance.- Named Objects
In TorqueScript, when any object derived from the SimObject class is created, it is assigned a unique ID. How you keep track of this ID is up to you.Creating a new object by giving it a name allows you to refer to this particular object from anywhere in your project by using that name.
new ScriptObject(InputMatrix){...};In the above example, we have named our object InputMatrix. This means that, anywhere in our script files, we can access this object's methods and fields as follows :InputMatrix.somemethod(); InputMatrix.someVariable = 5;
- Event Manager
An Event Manager allows you to dispatch messages to several objects at once.Step one, create the event manager :
%myEventManager = new EventManager(){
queue = "QueueName";
};Step two, register events :%myEventManager.registerEvent("Test_Event");Step three, register listeners: objects which will be notified once a specific event is posted.%myEventManager.subscribe(myObject, "Test_Event");Step four, when the time is right, post an event to the event queue.
%myEventManager.postEvent("Test_Event");This will call function onEventName on all subscribers to this event.function myObject::onTest_Event(%this, %data)
{
...
}About the author
I am here to help. I've worked at every imaginable position in game development, having entered the field originally as an audio guy.
#3
However, even for keyboard-based games, having an input structure can lead to really awesome functionality. For example, one could save a copy of the InputMatrix object every time it is updated and later, replay the data. Game mechanics like those of Braid or Super Meat Boy's death replay become extremely easy to tap into.
@Dwarf King: It works, too! See, no doctors for miles and miles.
04/26/2016 (10:01 am)
@Peter : It is not that useful for mouse-based games, like Pirate Code.However, even for keyboard-based games, having an input structure can lead to really awesome functionality. For example, one could save a copy of the InputMatrix object every time it is updated and later, replay the data. Game mechanics like those of Braid or Super Meat Boy's death replay become extremely easy to tap into.
@Dwarf King: It works, too! See, no doctors for miles and miles.

Associate Peter Robinson