javascripthtmlcanvas2d-gamesonline-game

Why are the 'pixels' on my scaled HTML5 canvas strangely aligned?


I'm currently writing a little HTML5 canvas/JS based snake clone similar to the classic Nokia Snake. (This is my first HTML5 canvas/JS game as well as my first game, I'm still a novice programmer ;) )

To keep things simple I decided to make every segment of the snake and the cherry just 1 pixel, however that would normally result in a very tiny snake and cherry! ;) So I decided to scale the canvas with .scale(8,8) so every pixel would be 8*8.

var snake = {
  length: 4,
  direction: "right",
  color: "#9A9A92",
  location: [{x: 32, y: 32}, {x: 31, y: 32}, {x: 30, y: 32}, {x: 29, y: 32}]
}
var cherry = {
  color: "#B1001D",
  x: Math.random()*65,
  y: Math.random()*65
}
var ToDrawOrNotToDraw; //limits the speed of the snake
function moveSnake(eventinfo){
  if(eventinfo.keyCode === 38){
    if(snake.direction === "left" || snake.direction === "right"){
      snake.direction = "up";
    }
  }
  else if(eventinfo.keyCode === 40){
    if(snake.direction === "left" || snake.direction === "right"){
      snake.direction = "down";
    }
  }
  else if(eventinfo.keyCode === 37){
    if(snake.direction === "up" || snake.direction === "down"){
      snake.direction = "left";
    }
  }
  else if(eventinfo.keyCode === 39){
    if(snake.direction === "up" || snake.direction === "down"){
      snake.direction = "right";
    }
  }
}
function draw(){
  requestAnimationFrame(draw);
  if(ToDrawOrNotToDraw === true){
    var snakeDrawerCount = 0;
    ctx.fillStyle = "#006C00";
    ctx.fillRect(0, 0, 64, 64);
    ctx.fillStyle = cherry.color;
    ctx.fillRect(cherry.x, cherry.y, 1, 1);
    ctx.fillStyle = snake.color;
    if(snake.direction === "up"){
      if(snake.location[0].y > 0){
        snake.location.unshift({x: snake.location[0].x, y: snake.location[0].y-1});
        snake.location.pop();
      }
      else {
        snake.location.unshift({x: snake.location[0].x, y: 64});
        snake.location.pop();
      }
    }
    else if(snake.direction === "down"){
      if(snake.location[0].y < 64){
        snake.location.unshift({x: snake.location[0].x, y: snake.location[0].y+1});
        snake.location.pop();
      }
      else {
        snake.location.unshift({x: snake.location[0].x, y: 0});
        snake.location.pop();
      }
    }
    else if(snake.direction === "left"){
      if(snake.location[0].x > 0){
        snake.location.unshift({x: snake.location[0].x-1, y: snake.location[0].y});
        snake.location.pop();
      }
      else {
        snake.location.unshift({x: 64, y: snake.location[0].y});
        snake.location.pop();
      }
    }
    else {
      if(snake.location[0].x < 64){
        snake.location.unshift({x: snake.location[0].x+1, y: snake.location[0].y});
        snake.location.pop();
      }
      else {
        snake.location.unshift({x: 0, y: snake.location[0].y});
        snake.location.pop();
      }
    }
    var snakeDrawerCount = 0;
    while(snakeDrawerCount < snake.location.length){
      ctx.fillRect(snake.location[snakeDrawerCount].x, snake.location[snakeDrawerCount].y, 1, 1);
      snakeDrawerCount++;
    }
    ToDrawOrNotToDraw = false;
  }
  else {
    ToDrawOrNotToDraw = true;
  }
}

var cnvs = document.getElementById("cnvs");
window.addEventListener("keydown", moveSnake, false);
var ctx = cnvs.getContext("2d");
ctx.scale(8,8); //actual size of the canvas is 512 * 512, this gives the 'pixels'/blocks on the canvas a size of 8 * 8, so the canvas will get a size of 64 * 64 'pixels'/blocks
draw();

But for some strange reason those 'big pixels' (the 8*8 scaled pixels) seem to overlap each other. As can be seen here:

1st image of the glitch 2nd image of the glitch 3rd image of the glitch

Unfortunately I have absolutely no idea why this is happening, because as I mentioned earlier I'm still very new to the HTML5 canvas/JS stuff, I have only programmed in Qt/QML/C++ and Python before. I tried my game in both Firefox 26 (on Linux) and Konqueror (on KDE 4.11)(WebKit), both browsers seem to have the issue, so I don't think it is Gecko-related.


Solution

  • The problem comes from anti-aliasing / sub-pixeling.

    Your cherry is placed in the board at a random value. The problem with the random value is that it's a float value so the cherry will be placed offset with some decimals and therefor sub-pixeled by the browser.

    When using scale the result of this sub-pixeling is also scaled (which is more clear when you change its color to for example white) and the result is that it doesn't match the grid.

    To solve this simply force the x and y position of the cherry to integer, for example like this:

    var cherry = {
      color: "#B1001D",
      x: Math.random()*65|0,
      y: Math.random()*65|0
    }
    

    Fiddle here

    | is the bitwise OR operator which forces the number first to an integer in order to be able to use bitwise OR. Since we OR with 0, the output is the same (beyond cutting the float decimal).