Friday, April 25, 2014

Dev - Controlling the Beast of Burden

Controlling the Beast

Early in development, the game was controlled almost entirely using the mouse.  As the design changed, and gamepad input started being implemented, the limitations of Unity's input system became very apparent.  After a quick look through the available replacements available, we decided that none of them quite met our requirements and started developing our own.  Here's how it works.

Controller Types

Multiple input devices can be configured and enabled/disabled independently.  Each input device definition consists of a Device Name, Device Type and an Enabled flag.  For instance, for the XBox360 controller we have an input with a name of XBOX and a type of JOYSTICK.  

Game Inputs

Next are game inputs.  These are actions that you want to perform, such as "Orbit", "Select Build Spot", etc.  Each game input takes one or more Input Devices (above) and an Input Type (Button or Axis).  "Orbit", for instance, is of type Axis and has XBOX axis 6 or Keyboard Left/Right arrow.


One of the most important features of the input system is the concept of Contexts.  Since the same button or axis can be used for multiple different actions, depending on the context in which it is pressed, we need a way to specify when an action can occur.  If you're zoomed out from the giant and press left, we want it to Orbit.  When in build mode, left should select the previous tower/plot.  We can create different contexts and assign multiple Game Inputs to each context.  An example of a context could be "Choose Plot Mode", which might have "Select Plot", "Cancel Build", and "Confirm Plot Selection" as valid inputs for that context.

Callback Driven

Instead of polling the input system every frame to see if a Game Input was pressed, we register callback functions to be called whenever the desired action takes place.

The Inspector

The input system itself was finished pretty quickly, but the inspector took a bit longer.  There are several Serializable classes that reference each other, but the serialization doesn't handle references too well.  There's the ScriptableObject base class that is supposed to be able to deal with this type of setup, but I didn't have much luck getting it to work properly.  So I ended up rolling my own.  Since everything has a unique name, I serialized everything myself, storing the name of the referenced class as well as a direct pointer, using MsgPack.  This loses the references but keeps the names, then on deserialization I rebuild the references based on the names.  I'm not entirely happy with it, and wouldn't want to make it available for general consumption, but it works well enough for the two of us to use it as we understand its quirks.

Written by Matthew Bowie