Demo

Planning

Adding: mushroom drops to the fly creature, and pause toggling with the gamepad.

Fly Droppings

Added mushroom generation to the falling bugs (the flies). This was straightforward.

First, I set up an interval, and added a new function to intervalCreatures: dropMushrooms.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
dropMushrooms(creature) {
  if (creature != 'flies' || !everyinterval(knobsAndLevers[creature].mushroomCreateInterval)) {
    return;
  };
  this.flies.forEach(fly => {
    if (fly.y > game.gameArea.gamePieceTopLimit) {
      // return acts like a continue in a forEach
      return;
    };
    let mushroom = mushrooms.generate(fly.x, fly.y);
    mushroom.color = 'purple';
    mushrooms.mushrooms.push(mushroom);
  });
},

This gets called in the intervalCreatures.manage function every frame. If the creature type is not 'flies' or the interval is not eligible, it just returns.

The fly won’t drop mushrooms inside the player area, since that would make some mushroom targets impossible to hit. That could be changed later for more challenge to the player, but then consideration will have to be made for scenarios where the centipede could potentially become impossible to hit.

The adding mushroom bit was taken from collisions.processKill, and the mushroom color updated.

Commit: 2637c50

Pausing with the Gamepad

The way it used to be

The ability to pause the game with the gamepad was missing, which means the user was still required to use the keyboard to start the game. That’s lame.

So I added pause toggling with the gamepad’s + button.

This was not as straightforward as I thought it would be. The original pause logic is spread all over the place and should probably be consolidated, but here’s the basic flow.

Every frame, check the paused boolean, which is a global var in main.js.

1
2
3
4
if (paused) {
  game.managePause();
  return;
};

If paused, invoke game.managedPause(), which just sets the appropriate message and stops the sounds. Then return from the game loop, which will restart the game loop. This does not increment the frame number, and will continue to loop until paused is no longer true.

1
2
3
4
5
6
7
8
managePause : function() {
  texts.pausedMessage.text = "Paused: Spacebar to Continue";
  if (this.gameArea.frameNo === 0) {
    texts.pausedMessage.text = "Press Spacebar to Start";
  }
  texts.pausedMessage.update();
  stopAllSounds();
},

paused gets toggled by the spacebar through an event listener. Really, there’s no reason to use this event listener, as the key is already captured in keysPressed, but we can refactor that later.

1
2
3
4
5
6
// TODO this is not necessary; the key is already being captured in keysDown
window.addEventListener('keydown', function (e) {
  if (e.keyCode == 32) {
    paused = !paused;
  }
})

All of this works great, and I don’t have to worry about multiple inputs, as pause only toggles on keydown, and the inherent delay means the rapid toggle was never a problem (though holding it down will trigger multiple inputs after the multi-input keyboard delay).

Enter gamepad pausing.

I already had isFiring logic that was looping over the gamepad buttons, but it was only getting called by the lasers.spawn function. I tried to shoehorn the pause in with the firing key detection, but that introduced an interesting delay in the firing, where a laser may still fire after the button was released. So instead, I duplicated the button checking of isFiring (ugh).

checkPauseButton is called every frame, and will toggle the paused boolean if the set delay has reached zero. This prevents flickering of the paused boolean with a short button press.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
checkPauseButton : function() {
  if (framesToDisallowTogglePause > 0) {
    framesToDisallowTogglePause--;
    return;
  }
  // TODO extract this into a function and pass the target indices in (pausedButtonIndices or fireButtonIndices)
  if (controllerEnabled && controllerIndex >= 0) {
    let buttons = navigator.getGamepads()[controllerIndex].buttons;
    for (let i = 0; i < buttons.length; i++) {
      if (buttons[i].pressed && this.pausedButtonIndices.includes(i)) {
        paused = !paused;
        framesToDisallowTogglePause = 50;
        break;
      };
    };
  };
},

I see an opportunity to extract the second if into a function and pass the target indices in (pausedButtonIndices or fireButtonIndices). This would reduce the duplication between isFiring and checkPauseButton, but for now, this is fine.

Commit: 04b97f7

Shipping to Prod

Merged to master and pushed changes out to my droplet and heroku

Added Joust and Robotron to matthewodle.com as well.

I also updated the catchall for matthewodle.com subdomains to give a list of game links, because I tried to access robotron before I set up the symlink for it to sites-enabled and got the catchall which said something lame like ‘this is the catchall’.

Post Session

Nothing very exciting today, or at least not nearly as exciting as writing all that fun gamepad logic in the previous sessions, but I think I can consider Centipede feature-complete-enough. I added a few issues (mostly experience improvements) and TODOs (mostly code quality) for things I’d like to do later.

For the next few sessions, I’m going to take some time away from Centipede and focus on updating the architecture of Joust and Robotron, because they are a mess.