Friday, 3 November 2017

p5 Pacman.js

I've been playing with p5js on and off for a while in order to generate some resources for use with Code Club and I've made (so far) Frogger, Snake and I've helped with Pong. Now I'm working with Pacman.

The thing about Pacman though, is that his jaws open and close as he's moving along and me and maths have an interesting relationship thanks to a somewhat spotchy formal education. Anyway, p5js has an arc so I was sorted. Sorted until I clocked that by default it uses radians rather than degrees, so much head scratching followed, as well as visits to the Khan Academy. And I got it working.

But p5js knows that people have issues with radians so instead of using radians I set angleMode(DEGREES);, neat eh?

The other thing about Pacman is that he moves and moves along 4 axes so the chomping needs to go in the direction of travel. Again I was stuck with radians until I realised that degrees would work (despite them being an arbitrary measurement). So I just had to rotate the start and stop angles of the arc by 90-degree increments to get this working - except that when Pacman was going right or up he flashed when his mouth was closed, so then I had to replace the arc with a circle if the gap was zero.

This is what I came up with:

if(this.jawWidth === 0){
    ellipse(this.x, this.y, this.r * 2, this.r * 2);
}else{
    let topJaw = this.jawWidth;
    let bottomJaw = -Math.abs(this.jawWidth);
    if(this.speed.every((e, i) => e === [0,-1][i])){
        topJaw += 270;
        bottomJaw += 270;
    }
    if(this.speed.every((e, i) => e === [0,1][i])){
        topJaw += 90;
        bottomJaw += 90;
    }
    if(this.speed.every((e, i) => e === [-1,0][i])){
        topJaw += 180;
        bottomJaw += 180;
    }
    arc(
      this.x, 
      this.y, 
      this.r * 2, 
      this.r * 2, 
      topJaw, 
      bottomJaw, 
      PIE);
}

I was not overly happy with it, but it worked. But what about just rotating Pacman instead? It turns out that that is even easier but does involve pushing and popping to ensure everything works properly - and that took quite a bit of head-scratching as well (It really is no wonder I'm bald). Anyway, here it is:

push();
translate(this.x, this.y);
if(this.speed.every((e, i) => e === [0,-1][i])){
    rotate(270);
}
if(this.speed.every((e, i) => e === [0,1][i])){
    rotate(90);
}
if(this.speed.every((e, i) => e === [-1,0][i])){
    rotate(180);
}
arc(
  0, 
  0, 
  this.r * 2, 
  this.r * 2, 
  this.jawWidth, 
  -Math.abs(this.jawWidth), 
  PIE);  
pop();

It's much more elegant isn't it?