Game Dev Session – Twinstick Firing in Robotron

Planning

More gamepad updates today. This time, specifically for Robotron. I plan to integrate the controls pausing and twinstick shooting.

Pause updates

Updated pause to handle both spacebar and the + key. I should probably find an XBox controller and see if the indices of those buttons are the same as the X-input (they should be; the indices don’t change between D- and X-input, only the dPad location does, and X-input includes a few extra buttons.)

This was fairly straightforward. The main issue it highlighted was that the game cycle intervals between Centipede and Robotron were different (Robotron: 20, Centipede: 5). This meant Centipede had four times the number of frames (1000/5 = 200) of Robotron (1000/20 = 50) in the same period of time. This made the Robotron pause delay too long. For now, I just set both intervals to 5. 5 is far too high, so I should probably have used 20, but we can optimize later.

Robotron Commit: a576d0b
– frames increased, speeds reduced
paused boolean moved
spacebar event listener removed

canvas-libs commit: c68c7a3
– renamed pause repeat frames limiter
– subfunctioned the pause button checking, and handled both spacebar and +

Twinstick Lasers!

The gamepad is useless if I can’t fire my lasers with it. Time to update lasers.js.

First, the key mappings from lasers.js were moved into robotronControls.js.

Initially, in the lasers object, each key combination was getting tested in an 8-branch if/else. The speed coordinates were set accordingly.

var speedX = 0;
var speedY = 0;
// left up
if (controls.keysDown[37] && controls.keysDown[38]) {
  speedX = -1 * this.speed;
  speedY = -1 * this.speed;
// left down
} else if (controls.keysDown[37] && controls.keysDown[40]) {
  speedX = -1 * this.speed;
  speedY = 1 * this.speed;
}
...

Obviously this is not very clean, so I applied the same approach that was applied to determine the movement direction to get the fire direction, by first mapping the keys to their appropriate direction.

robotronControls.fireDirections = {
  upRight : [38, 39],
  downRight : [39, 40],
  downLeft : [37, 40],
  upLeft : [37, 38],
  up : [38],
  right : [39],
  down : [40],
  left : [37],
};

Then, I loop through the keys on the fireDirections object, and if all of the current direction’s keys are set to true in keysDown (check by invoking isWholeArrayPresent), that direction is returned. The speed var is set to undefined at the start and left unchanged if no match so that the forEach will only update the apropriate direction if it’s not already set! This means the first direction to match will get used.

robotronControls.getLaserSpeed = function(baseSpeed) {
  let speed = undefined;
  Array.from(Object.keys(robotronControls.fireDirections)).forEach(direction => {
    speed = !speed && robotronControls.isWholeArrayPresent(robotronControls.fireDirections[direction], robotronControls.keysDown)
    ? Object.assign({}, robotronControls.coordinateDirections[direction])
    : speed;
  });
  if (speed) {
    this.applySpeed(speed, baseSpeed);
  };
  return speed;
};

Note that we’re cloning the target direction object from the coordinateDirections object, because otherwise the values will get multiplied by baseSpeed each cycle without ever resetting because the object we’re multiplying and the original object are the same! Since that base direction/speed mapping object is used in other places, we don’t want to mess with it.

robotronControls.applySpeed just multiplies the direction by the speed modifier:

robotronControls.applySpeed = function(speed, modifier) {
  speed.x *= modifier;
  speed.y *= modifier;
};

That’s all we need to handle the speed determination for lasers using the keyboard. To call it, we just invoke this from lasers.make, and if it’s not falsey (and if either x or y is not 0), we create a new laser using the speed values:

In lasers.make:

let speed = robotronControls.getLaserSpeed(this.baseSpeed);
if (!speed) {
  return;
};
if (speed.x != 0 || speed.y != 0) {
  this.lasers.push(
    new component(
      ...

Lasers is now so much easier to comprehend.

Now we can add twinstick firing

At this point, we still can’t use the right stick to fire, but we have all the pieces in place. Since we don’t care about velocity variance (if the stick x value is 0.5 or 1, we treat it the same), it’s just a matter of calling the controls library for the direction.

The problem is the controls library only handles the left stick. This means we have to make a few changes.

controls.checkDirectionOfLeftStick grabs the direction parameter, compares the x and y values to that direction’s object, and returns the direction if it finds a match. This is exactly what we want the right stick to do, so we change checkDirectionOfLeftStick to checkDirectionOfStick, and just pass in the stick name.

Before:

checkDirectionOfLeftStick : function(direction) {
  stickValues = this.axes.leftStick.values;
  compareObj = this.movementCoordinates[direction];

After:

checkDirectionOfStick : function(direction, stick) {
  stickValues = this.axes[stick].values;
  compareObj = this.coordinateDirections[direction];

We also need to change the internal call to checkDirectionOfLeftStick in the controls.checkDirection function so we don’t break anything.

Before:

return this.isWholeArrayPresent(this.movementCodes[direction], controls.keysDown) || this.checkDirectionOfLeftStick(direction);

After:

return this.isWholeArrayPresent(this.movementCodes[direction], controls.keysDown) || this.checkDirectionOfStick(direction, 'leftStick');

Now all we need is to add the call to robotronControls.getLaserSpeed:

Before:

...
Array.from(Object.keys(robotronControls.fireDirections)).forEach(direction => {
  speed = !speed && robotronControls.isWholeArrayPresent(robotronControls.fireDirections[direction], robotronControls.keysDown)
...

After:

...
Array.from(Object.keys(robotronControls.fireDirections)).forEach(direction => {
  speed = !speed && (
    robotronControls.isWholeArrayPresent(robotronControls.fireDirections[direction], robotronControls.keysDown)
      ||
    robotronControls.checkDirectionOfStick(direction, 'rightStick')
  )
...

canvas-libs Commits:
9b98059

Robotron Commits:
a534042
e375db1

Success!

At some point, this firing stick logic can get moved to controls.js in canvas-libs, so other twin-stick games can make use of it, but for now, we’ll leave it in Robotron.

Leave a Reply