Game Dev Session – Canvas Gamepad Movement

Session Planning

I see a potential problem in the controls if more than one controller button is needed simultaneously. The isFiring returns a boolean on the FIRST button push it detects, and would ignore any additional entries in that list. This means if I press A, it will do the action for A, but then if I keep pressing A, and also press B, the action for A+B would not get executed; only the action for A would.

I might be able to address this with using the keysDown array, and adding a key like ‘controller’ + controllerIndex = true.

Maybe I’m wrong, though, and the response time is fast enough that this isn’t a problem. I’ll just keep it on the back burner for when I need combination triggers.

This is also potentially already a problem for movement, as that requires two keys at once (to move up+left, etc), and key overrides. Hitting right while already moving left would ideally override the left movement, and it does, but hitting left while already moving right does not, because left gets added to the map later, and so the right instance is found first. This is true regardless of which key gets hit first, as the map is constantly updated, and the sequence is upRight, upLeft, downRight, downLeft, up, down, right, left. The keys are then filtered for true cases and added to an Array of activeDirections:

setActiveDirections : function() {
  directionResults = {
    upRight : (bool logic here),
    upLeft : (bool logic here),
    etc...   };
  this.activeDirections = Array.from(Object.keys(directionResults)).filter(direction => directionResults[direction]);
},

Then the first entry in activeDirections is processed (it occurs to me that with this approach, activeDirections could be changed to activeDirection, and the above filter could be changed from filter to find to improve performance, if we only want the first entry, which is how it currently processes):

if (this.activeDirections.length > 0) {
  this.moveTheThing(this.activeDirections[0]);
  return;
}

This is what causes the override cascading. Those combinations toward the top of the list have priority. I’m not sure I want to do anything about this, as this is actually how the original Centipede behaves when emulated on the web and controlled with keyboard arrows.

If instead all entries in the list are executed as follows, you get interesting behavior around the diagonal movement.

this.activeDirections.forEach(direction => this.moveTheThing(direction));

It moves really fast! This is because with up/left, the up/left is applying a speedY and a speedX, but left is also applying speedX, and it’s additive, so left gets counted doubly. This could be mitigated somewhat by giving the movements half weight when perpendicularly applied, or by setting an upper limit on speedX/speedY. Also, the left/right and up/down combinations cancel each other out, which is desirable. Also, weird things happen when holding up/down and then hitting left/right. I think it’s best to avoid this approach without some serious reconsideration of how speeds are processed.

Some Refacoring: Low Hanging Fruit

Refactored to simplify the move/getActiveDirection logic:

if (this.activeDirections.length > 0) {
  this.moveTheThing(this.activeDirections[0]);
  return;
}

becomes:

this.moveTheThing(this.getActiveDirection());

And getActiveDirection replaces setActiveDirections:

getActiveDirection : function() {
  directionResults = {
    // something
  };
  return Array.from(Object.keys(directionResults)).find(direction => directionResults[direction]);
}

Some rework to clean this up a bit: e3d9a08

Controller Axes Analysis

console.log(navigator.getGamepads()[controllerIndex].axes)

The axes output array is: [leftStick.x, leftStick.y, rightStick.x, rightStick.y]

How do I get the D-pad values? A worry for later.

Up with the left stick looks like this: [-0.15259255468845367, -1, -0.11713004112243652, -0.0138248847797513]

Index 1 is a value of -1.

Resting axes look like this (that’s a lot of deviation from 0 in the horizontals): [-0.11615344882011414, -0.01678518019616604, -0.12402722239494324, -0.003997924737632275]

Negative values are left or up, as expected. This should make it really simple to control movement, as the speeds can just be set to the target value. gamePiece.speedX = leftStick.x, gamePiece.speedY = leftStick.y, if gamePad is active, while ignoring values less than, say, 0.5, because it looks like there’s a lot of inherent drift at rest. This can take priority over WASD, because CONTROLLERS, MAN!

Post Session

So far I’ve got it to a point where the controller works for movement, but I have not accounted for the edges of the play area, so the gamePiece can move outside, which is obviously bad.

I’m going to have to make a decision on whether to use the axes inputs to determine the direction (this makes it easier to reuse the existing keyboard logic that handles edge collisions), or apply the axes inputs directly (which gives a more dynamic speed feel, as the speed changes based on stick pressure). In the latter case, I will need to update the gamePiece.getPositionModifiers function to allow variance in the x and y values (currently they’re -1, 0, or 1 only.)

https://gitlab.com/taciturn-pachyderm/centipede/commit/f16d1a81c020bf421c3e5af0ecd0c8e161ef1dde

Leave a Reply