02Credits Blog

Day18 - 8Bomb Camera Fixes

SCRIPT-8 bug fixes to enable better 8Bomb physics

2019-02-24

Project Page

Todo

Another busy day today. I mostly made some bug fixes in SCRIPT-8 to clean up yesterday's camera code. If you had tried the demo yesterday, you may have noticed that the ball was a little bouncy whenever the camera moved. I tried to hide some of this by using a stationary camera zone, but the effect was still visible. This was mostly due to the fact that I had to work around the getPixel and built in camera translation features in SCRIPT-8 since they had some bugs. Today's daily will be about the bugs and how they made the 8Bomb physics better.

When attempting to use camera and getPixel together I ran into two problems: An issue with GetPixel's coordinate space and anti aliasing problems in camera.

GetPixel Bug

In the PICO-8 version I didn't bother with translating the graphics manually because I could simply use the camera function to do the work for me. This worked great because the getPixel and setPixel commands in PICO-8 were both in camera coordinates.

Unfortunately when I added getPixel and setPixel to SCRIPT-8 I forgot to pay attention to the camera translation. So setPixel worked fine, but getPixel was in screen coordinates, so setting a pixel to some coordinates and reading from those same coordinates would not give the same reading if the camera was translated.

The first step was to write a test to see what was going on.

  draw = () => {
  clear();
  camera(5, 0);
  setPixel(5, 5, 5);
  print(15, 3, getPixel(5, 5));
}

Initially when running this code, I expected the read value to be 5, as I just set the pixel. In practice though the reported value was 7 which is the background or default color.

SadGetPixel

The fix was pretty simple since the CanvasAPI already had variables representing the cameraX and cameraY values.

  getPixel(x, y) {
  return getPixel({
    x: Math.floor(x - _cameraX),
    y: Math.floor(y - _cameraY),
    ctx
  })
},

Rerunning my test yielded:

HappyGetPixel

Camera Bug

The camera bug was slightly more complicated. Initially I thought everything worked fine since simple tests gave correct results. However when I tried using camera in 8Bomb the screen would flicker and the ball would fall through the screen. Very frustrating.

To diagnose I created a small demo which moved a target graphic back and forth across the screen in a simple smooth animation.

  let x = 0;
let targetX = 0;
let t = 0;
let frequency = 150;
draw = () => {
  clear();
  camera();
  print(0, 0, "x: " + x);
  print(0, 8, "t: " + t);
  if (t % frequency == 0) {
    targetX = ((t / frequency) + 1) % 2 * 100;
  }
  x += (targetX - x) * 0.1;
  camera(-x, 1);
  sprite(0, 50, 0);
  t += 1;
}

The sprite I used looked like:

TestTile

The exact calculation isn't important, but basically the target position is changed every 150 frames to bounce back and forth between 0 and 100. Luckily this animation captures the problem. When the camera position is between integer multiples, the canvas will draw the image partially in one position in partially in another as a form of anti aliasing.

When caught between frames the tile looks somewhat transparent.

BlurredTile

This would cause getPixel to report the default value since it didn't exist in the pallet, which would cause the ball to fall through the floor in 8Bomb. The fix was to floor the values passed to camera so that they are always a whole number which would prevent any anti-aliasing from happening.

  camera(x = 0, y = 0) {
  _cameraX = Math.floor(x)
  _cameraY = Math.floor(y)
  ctx.setTransform(1, 0, 0, 1, 0, 0)
  ctx.translate(-_cameraX, -_cameraY)
},

This fixed the problem leaving everything nice and crisp.

Pull Requests

I made two pull requests with these fixes which got merged an hour or two ago. With the fixes in place, I updated 8Bomb to take out the cameraY translation I built yesterday and to set the camera position in the draw function.

  draw = state => {
  clear();
  camera(0, state.cameraY);
  drawTerrain(state);
  handleTerrainCollisions(state.player);
  drawPlayer(state);
  camera();
  drawInstructions(state);
}

With those changes in place, the physics no longer jumps on camera move. I like the camera changes made yesterday, so I left them in, but it feels much better to know that the physics is actually correct. The current version of the game can be played here.

Till tomorrow,
Keith