Today, I’m adding controller support to Centipede. (See the first controller commit.)
Controls Code Cleanup
First, the location of the controls currently doesn’t make a lot of sense, so I’m moving them to a new file, controls.js
. On load, controls.addEventListeners
will execute. There are no dependencies, so the script load order doesn’t matter. Previously these were on the gameArea
object because that’s where the keys tracker was stored, but the keys tracker can just live in the controls
object.
I ran into problems when I changed the gameArea
qualifiers on the keysDown
list to this
inside the window.addEventListeners
function calls.
|
|
This makes sense, since window
will have no reference to controls
once the event listener is set. I changed the reference back to explicit instead: controls.keysDown
.
This is now the setup for mouse/keyboard control:
|
|
Basically, key and mouse presses are tracked by updating a key-value pair of the keysDown
object on mouse/keydown (True), or mouse/keyup (False). The controls algorithms then look for specific key codes each cycle, and if they’re True, action happens.
|
|
The above controls.isFiring()
can be reduced by using Array.prototype.find
, which returns the first match (which would be truthy
), or if no matches, undefined (falsey
):
|
|
To add a firing option for the controller, we just need to identify how to capture the code with an event listener, add it to the keysDown
object when pressed, and add it to the fireKeyCodes
array.
Exploring the GamePad API
According to MDN, the GamePad API should do everything needed. Throwing on the first listener example for gamepadconnected
gets us a readout when the gamepad is turned on.
Listener
|
|
Readout when connecting the adapter in X-input mode:
|
|
Readout when connecting the adapter in D-input mode:
|
|
With the following, I can read the buttons by looking at the navigator object:
|
|
With the above, button.value
prints as 0
by default, 1
when pressed, and button.pressed
prints as false
by default, true
when pressed.
With this I can determine which buttons map to which indices in the buttons Array. Here are the mappings I discovered, and how I plan to use them.
|
|
|
|
Investigating the Mayflash Controller Adapter
I’m using a Wii U pro controller with a Mayflash wireless controller adaptor for PC USB on Xinput mode. I get four instances of the controller, which is really interesting. This means the adaptor is capable of pairing up to 4 controllers at a time (I’ll test this out later with a second controller). I had thought that D-input was the multi-input, but this makes more sense. X: cross, D: direct.
When testing, I’m only seeing activity on one of the gamepads (as expected). Unfortunately, with the Mayflash adapter, there’s no way to tell which index has a connected controller, since it fires up all 4 connectors, regardless of whether a controller is actually connected to the adapter.
|
|
I was only able to discover the correct index for the active controller by trying all of them. During gameplay, I’ll have to check all four gamepad indices and every button on those gamepads every game loop to see which one is actually accepting inputs, since Mayflash is connecting all 4. This just seems insane. Worst case scenario, I can find the correct controller once, once buttons are pressed, then stop checking, but since I can’t tie an event listener directly to each button, I’ll have to check periodically, or have a toggle in the UI for controller activation.
Working Fire Button Detection!
I added the toggle checkbox to the UI. Each gameloop, the checkbox state will be determined. If the checkbox is checked, gamepads will be read from the navigator, and the buttons of each gamepad will be checked until input is received. Once it is received, the gamepads index will be stored in a controllerIndex variable, and this check will not occur again until the checkbox state changes. It’s a bit messy, and I’ll make another pass at it later. It could be modified to allow me to register players in order. First detected button push would be player 1, then that index will get ignored on the next check cycle, second detected push would be player 2, etc.
|
|
To actually detect fire inputs, a similar approach is used. If controllerEnabled
is true (checkbox checked) and the controllerIndex
has been detected, only then will the buttons be read. It gets the buttons array from the active controller, checks them against the allowed firing buttons, and if a match is found, returns true. If no match is found, it moves on to check the keysDown array, as before.
|
|
Next time I’ll tackle movement, then once it’s working, take time to reconsider the gratuitous use of for loops and see if there’s a more elegant approach.