24 September 2013
Tutorial: Game controllers and axes
In the previous tutorial about HID controllers, we showed you how to read button inputs from controllers. Generally speaking, controller buttons generate a simple “down” or “up” value, so you can easily determine if the player is interacting with that button.
About analog controls
In contrast, analog controls are more complex. These measure an almost infinite number of potential values that must be converted from their analog “infinity” to discrete digital values. In the case of the Corona, these values typically range from
1.0 and a very precise set of values anywhere between. To compound the complexity, some controls have two associated values — for example, an analog stick can be moved up and down in either direction over a varying distance, as well as left and right. Other controls like “triggers” generate values between
1.0 where the value increases the more you squeeze the trigger. As a broad definition, we refer to these control values as axis. For instance, the left analog stick has both an X axis and a Y axis. The right trigger, in comparison, has just a single axis to measure.
At the hardware level, analog sticks typically depend on springs to push the stick back to the neutral center position. However, these springs don’t always push the stick back to its perfect
0 value. In fact, a stick can be “noisy” and generate events even when the control isn’t being touched. Internally, Corona tries and minimize this noise, but because controllers vary considerably (even those from the same manufacturer), you should account for a certain amount of “slop” in your analog stick handling.
Another issue to contend with is the varied axis numbers that manufacturers assign to their controllers. For instance, the right trigger on the OUYA controller is axis number 6, but on a DualShock controller, that same control is axis number 14. In general, there are two ways to handle this:
- Build a mapping screen like those in many PC games, giving the player a chance to press the various buttons and controls, then gather those values and assign an axis number to each specific control. This is probably the best way to handle devices if you expect that players may use controllers that you know little about.
- Build a mapping system of specific controllers that you want to support. Since you’ll know in advance what each controller’s axes map to, you can build a table of inputs. Of course, you’ll still need to determine what the values are when you test a new controller.
For this tutorial, we’ll explore a basic mapping system of “known” controllers. To keep this tutorial relatively simple, we’ll only include two controllers, but you could repeat this basic pattern to handle additional controllers. Let’s look at some basic code:
The first block of code creates a table based on the controllers we want to to support: the standard OUYA controller and the DualShock controller. The second table maps the physical control names to specific numbers. Later, we’ll execute code that reveals the real values that should be entered into this table.
Basic game setup
Let’s explore a simple game featuring two players that can be manipulated with a controller:
Each player is a colored square — one green and one red — and they begin in the center of the screen. We’ve added some properties to each player that represent the movement along the X and Y axes. The left stick is used to move the player and the right stick is used to rotate the player. Just for fun, we’ll use the triggers to change the color of the player. Because our axis events are not continuous, we need to use an
"enterFrame" listener to move our player while the stick is being held (this listener will manage the player rotation as well).
The next step is a function that calculates the player rotation angle based on the X and Y of where the stick is being held. Note that you get one event for X movement and one event for Y movement. As a result, you must store the last value generated for X so that when you receive a Y event, you can compare the two. Likewise, if you get an X event, you must have access to the previous saved Y event.
Now let’s examine the game loop required to monitor the controller input:
Movement with the X and Y values is straightforward. If you simply want to move around, you can apply the values from the two axis events. At this point, you could apply physics impulses or movement, or in this case, simply apply the values from the controller. Rotation is more tricky — depending on what the rotation should do, you may have to convert the X and Y into an angle. You can also consider using the distance the stick is pressed to determine how fast to rotate. Since the X and Y values for rotation comes from two separate events, you need to store the two values and perform your movement based on those values.
Let’s look at the actual code to manage the axis data:
Initialize the controllers
Finally we need to initialize everything, including mapping the controllers:
The above code loops over the list of axes returned by the
inputDevices[deviceIndex]:getAxes() function. It’s very likely that we don’t need all of the axes returned. This is where we use the two data tables at the top of this tutorial to pick an axis that we find and store the axis number into the second table.
Note that game controllers may run out of battery power and just “drop out” without warning. It’s also possible that a player may want to change controllers in the middle of the game, and thus “Joystick 2” is actually player 1. It’s your responsibility to manage these events, using the inputDeviceStatus event:
This tutorial should get you up to speed on the axis type inputs on your controller and the basic mapping system.