In this entry, I will cover the system I initially implemented, and then one of the other coders, Sunny, greatly extended afterwards – the InputBuffer. I’ll discuss why the need for such a system arose, how it was implemented and the crucial bits of how it could be improved further.
Seeing as the player vehicle model is a component that you can attach to any rigid body in the level, we would obviously want to let this component know about inputs from the PS4 joypad. Due to the way that the framework was set up, all of the components had to be implemented in the DeveloperExtensions project, which is a pre-compiled library. However, the game can only query inputs inside the handleInputs() function of the CGCApplication class, which is the main run-time class of the game. Of course, that lives in a different project, i.e. the GCRacingGame project.
It is important to re-iterate that these projects can only have a one-way link: the GCRacingGame can use the library project DeveloperExtensions, but the DeveloperExtensions can not use any GCRacingGame classes directly, as it is also used in other internal PhyreEngine solutions such as the PhyreAssetGatherer (otherwise, the gatherer would fail to compile due to link errors in DeveloperExtensions, since the GCRacingGameProject is not part of that solution).
Therefore, we couldn’t query the main application class from the components to get to know about inputs. So instead, as a first pass, we could fetch the component that needs input in the application, and explicitly feed in inputs from the main class. That came with a host of other problems. First of all, the wide range of components that need inputs, and the context in which these components are relevant. The easiest example is the player component. While it’s reasonable to assume it exists in a race, it probably doesn’t in a menu. In the same way, menu buttons are components that need input in menus, but don’t have much of a reason to exist outside of that.
Therefore, we thought that there’s all of these different contexts that require different inputs, so it would be nice to abstract that out and handle inputs differently based on the context. This is one of the reasons Radu developed the scene system, the other being the need to switch between levels and load multiple levels at once, etc.
So with this scene system in place, I created a test scene for myself, in which it would be reasonable to expect a player controller to exist, and would feed inputs to it once that scene is running. I would store a pointer to the player component as a member in the scene, and would feed the inputs to the controller in the handleInputs() function of my scene.
This worked fine as a starting point, but we realised the problems with this approach quite quickly. First of all, making the game run correctly was a challenge in the level editor. The way our game would determine which scene to run was via a check of what level file is being loaded. The result of that was that my scene would only run on my test level as a starting point. So if the designers wanted to create their own test level and fly around, we would need to add that into the code, indicating that their test level should activate my test scene. Otherwise, inputs would not be fed into the player controller, as only my scene had that behaviour.
We found the whole approach of defining bindings between scenes and levels to be a bit messy. Furthermore, one of the goals of the player controller was re-bindable controls. The model we had set up didn’t translate well into achieving this goal. After all, any value binding can only be done in the DeveloperExtensions project, on components themselves directly. The main run-time project that processes the inputs was more or less invisible to the level editor.
The direct passing of inputs to components would cause further problems if a component that’s expected to be there was removed, and the whole system was coupled way more than it should have been.
This prompted me to come up with a better solution for processing inputs, with the main aim being to reduce coupling between the main application and the specific components that are expected to exist; to get rid of explicit pointers being stored for components needing input; to shift responsibility of input processing from the main application to the components that care about the input, and finally to make a system that’s readily extendable without introducing new dependencies to the code.
The solution – CGCObjInputBuffer
The solution I came up with was a singleton class that would be defined in the DeveloperExtensions project. So instead of having to do a growing amount of couplings with input passing like this:
Instead, we could do it like this:
Now this is a huge improvement. The main application does not need to know about which components exist and does not even care, which is how it should be. Instead, the objects fetching the inputs are the ones that actually need the inputs for processing – the components themselves.
This also means that whenever a new component is introduced, there is no need to introduce any new dependencies or checks neither in the application, nor in the input buffer itself. Instead, the programmer can just query the inputs directly from the component they are developing. No need to write a single line of code outside the component.
Furthermore, there is no need to create context specific scenes, such as a Menu scene or a Race scene in the main application – it’s all driven by the data (the components) that exist in the specific context. This is the reason why the build submitted for the end of prototype does not contain a CGCMindaugasTestScene – there was no more need for it. And the complicated question of mapping scenes to level names in the level editor was eliminated as well – the components would get their inputs regardless of the main application’s context.
So without further ado, here’s how the input buffer class works. All inputs are defined in an enum, in EInputIndex.h:
This is just the start of the enum, as the number of inputs to choose from is quite big by now. Since enums translate to a sequential list of integers quite well, it means that they can be used as a way to index an array. This is precisely the main functionality of the InputBuffer:
The InputBuffer holds an array of input values (represented as floats, where the input fluctuates from 0.0f to 1.0f on the analog sticks, and is set to 0.0f or 1.0f for buttons). The array is the size of the number of inputs available, multiplied by the number of players supported by the game. The multiple player extension to the buffer was written by Radu, so it’s not worth me covering it in depth, but the idea is that if there’s 10 inputs available, each player would query the buffer in offset positions using the formula of playerNumber*NumInputs + InputKey. So if the X button is the third input in the enum, player 0 would query the input as 0*10 + 3 to get the index 3 in the array. Player 1 would query the same button as 1*10 + 3 to get index 13 in the array, if I remember correctly.
With the array in place, I only had to write two basic functions to complete the class: one for writing into the array, and one for querying the array. The first one would be called from the main application to update all of the inputs in the array:
This function could do well to include some sanity checks, but it’s only ever called by one client by design, the main application, so problems shouldn’t really arise. Still, a check would be good.
The main application updates these inputs each frame as such (this is not my code, since it’s been streamlined and extended by Sunny):
For the L1 and L2 buttons as an example. The second function is querying the input buffer, which is pretty trivial as well (and could also do with some sanity checks):
Again, sanity checks would be nice, and the reason they were omitted was because the eInput parameter was designed to be passed as a value set within the level editor only, so that constraint would enforce a valid index being passed.
An example use of this function would be to retrieve the button for boosting:
Writing this, I realised that it would be much cleaner to have two parameters for the function, which is InputIndex and playerID separately. Then the client wouldn’t need to do the redundant and duplicative calculation for player offsets in the array manually, it could just be done in the query function. I will suggest this improvement for the start of production.
As a final thing to clarify, it is worth noting that the input buffer singleton (just like all of the other helper singletons I wrote) is explicitly initialised at the start of the application, and explicitly destroyed at the end of it. It can be retrieved anywhere with the following function call:
Which is a static function, of course.
To wrap up the input processing functionality, I’ll quickly cover how this class translates to assignable buttons in the level editor. First of all, the client component defines an enum member variable and exposes it to the level editor:
Then, the exposed variable needs to be annotated in the special class called PhyreDeveloperExtensionsGameEdit. This is the class where all annotations are defined. To clarify, annotations are what defines how a variable would appear in the level editor, and once I figured out how to do that, I strongly urged all of my code colleagues to follow suit. The programmer can define a string name of how the variable will be named in the editor, the description that appears when you hold the mouse over the variable, and the ordering in which variables appear on the component in the level editor, among other things. The process of annotating is simple enough:
This is an example of annotating the Dead Zone variable. The annotation macro takes in the type of data as its first parameter, the identifier of the annotation type as the second, the class descriptor (object that holds all meta-data about a class) of the class being annotated, the variable name and the actual value to set.
However, we didn’t want to just put in an integer corresponding to the input in the level editor. Instead, we wanted a drop down box to appear for the designer to choose input as such:
I had to figure out how to do this, and thankfully Phyre supported such annotations for enums (albeit there was no documentation at all on how to do it). Unfortunately, it meant going through all enum values and assigning them a string name each. Since there’s many variables where we’d want to use an input box, I created a macro that makes defining this drop down for inputs easy:
The macro is a nasty long list for each and every input enum value, but as a result, defining the drop down is easy enough:
And that concludes the implementation of the current Input system as far as I’m concerned. I implemented the base functionality and support for the analog sticks and buttons on the joypad, whereas Sunny later extended it to support touch controls, motion controls, the light bar and so on.
What next, or What I would do differently
While the current input processing system works all well and great, there’s still room for it to improve. The first issue that comes to mind is dependencies between the same input on different components. For example, when the player presses R2 (in a certain configuration) to accelerate, the vehicle model start applying a force in the forward direction. What’s more, the Visual Rotation component (the one that tilts the mesh in fancy ways to improve the control feel) rotates the mesh forwards to replicate what a real drone looks like when it accelerates.
The first non-ideal option is to store a member variable for the acceleration button in both components and rely on the designer to map the same button on both. This is tedious, especially if more components come into play later (such as VFX on acceleration, camera panning out, etc.).
The second non-ideal option (and the one in place right now) is for one central component (the vehicle model, a.k.a. the CGCObjFlyingPlayer component) to pass the inputs to other dependent components. However, this acts as a form of dependency that I’ve already covered in my entry about player controls.
I think what would solve the issue here is a further layer of abstraction, which would be something like an Input Map component. In here, instead of setting positive and negative inputs to rotations and movements, it would abstract the whole issue a level up, by simply defining input boxes for “Accelerate”, “Brake”, “Turn Left” and so on, much like you would see in a control options screen on any PC game. In this case, the designer would only assign a button for acceleration, and each of the components that care about acceleration behaviour would just query the acceleration button for whether it is pressed or not. So the Visual Rotation would know that if the acceleration button is pressed, it needs to tilt forwards, and the Flying Player component would know that if the acceleration button is pressed, it needs to go forward.
That would mean there’s just one place where “Acceleration” as a concept is defined and mapped to a button. This is better since in the current system, the user would assign the same button to different actions, and the assignments as a whole would define the concept of “Acceleration”. This is good when exploring vehicle control models, since it gives a lot of flexibility and the power to actually figure out what “Acceleration” means in our game.
However, once we figure out our vehicle model, and the concept of acceleration is well known, it becomes tidier to simply bind a single button to the whole concept instead of separate actions. This can only really be done once our vehicle model is more or less finalised, so I’ll need to get approval on this from the design team before implementing something like this.
An abstracted input map would also prove useful in streamlining switching between multiple control configurations, which is a task that’s set out for the production phase, since I could just serialize the input map and switch between different options in a single centralised input map component, rather than jumping between multiple components to set their input bindings in-game. In a nutshell, the input map would change the way components query input from something like:
and so on.
The exact logistics of implementation would still need to be figured out, but the advantage seems to be clear, since the components could just individually, with no inter-dependencies, query the input for an “action” performed by the player, instead of a specific button press.
None due to NDA.