3

I'm working on a canvas game that's using a sprite sheet for the character.
The dimensions of character is 64px wide and 128px high with 10 frames per animation.
So the total width of a single animation is 640px wide and 128px high.
However when I use the following code, The animation is offset by 1px, Sometimes flashing when I hold down a movement key.

player.width = 64;  
player.height = 128;  
player.x = canvas.width / 2;
player.y = canvas.height / 2;


ctx.drawImage(
    LoadedImages.player_sprite,
    64, // This is offset by 1px when moving. 63px fixes it.
    128,
    player.width,
    player.height,
    player.x,
    player.y,
    player.width,
    player.height
);  

Here's a picture of what happens:
Damn
Changing the width to 63 seems to fix the problem, But it doesn't explain why it's doing it in the first place.

The full code is available on Codepen
http://codepen.io/Codewoofy/pen/QNGLNj
Sprite Sheet:
oh

Full Code:

var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;

// jQuery Objects
var $canvas = $("#canvas");
var $debug = $("#debug");

var KEY = {
   W: 1,
   UP: 38,
   LEFT: 39,
   RIGHT: 37,
   SPACE: 32,
   SHIFT: 16
};

var COLOR = {
   DARKRED: "#9C1E33",
   WHITE: "#FFFFFF"
}

var MESSAGE = {
   UNDEFINED: "",
   PRELOAD_ATTEMPT: "Attempting to preload: ",
   ERROR_IMAGE_PRELOAD: "Unable to preload images.",
   SUCCESS_IMAGE_PRELOAD: "Images successfully preloaded."
}

// Images
var Images = {
   player_sprite: "http://h.dropcanvas.com/30npe/Talia-Sheet-Fix.png",
   main_background: "http://h.dropcanvas.com/9kgs1/background_main.png"
}
var LoadedImages = {};

// Dictionaries.
var game = {};
game.enviroment = {};
game.canvas = $canvas[0];
game.canvas.height = 500;
game.canvas.width = 700;
var ctx = game.canvas.getContext('2d');
var player = {};

// Debug
game.debug = function(msg) {
   if (msg == "") msg = MESSAGE.UNDEFINED;
   $debug.prepend(msg + "<br />");
}

// Preloader.
game.loadImages = function() {
   LoadedImages = {};
   Object.keys(Images).forEach(function(path) {
      game.debug(MESSAGE.PRELOAD_ATTEMPT + path);
      var img = new Image;
      img.onload = function() {
         LoadedImages[path] = img;
         if (Object.keys(LoadedImages).length == Object.keys(Images).length) {
            game.onImagesLoaded();
         }
      }
      img.onerror = function() {
         game.onFailedPreload();
      }
      img.src = Images[path];
   });
}

game.onFailedPreload = function() {
   game.debug(MESSAGE.ERROR_IMAGE_PRELOAD);
}

game.onImagesLoaded = function() {
   game.debug(MESSAGE.SUCCESS_IMAGE_PRELOAD);
   game.game_update();
}

game.onLoad = function() {
   // Game settings
   game.keys = [];
   game.running = false;
   game.lastUpdate = 0;
   // Enviroment
   game.enviroment.gravity = 0.5;
   game.enviroment.friction = 0.9;
   // Player settings
   player.name = "Talia";
   player.color = COLOR.DARKRED;
   player.direction = 'L';
   player.width = 64;
   player.height = 128;
   player.speed = 4;
   player.walkspeed = 4;
   player.sprintspeed = 10;
   player.jumping = false;
   player.animation_frame = 0;
   player.velX = 0;
   player.velY = 0;
   player.x = 0;
   player.y = 0;
   // Player Stats
   player.health = 100;
   player.mana = 100;
   player.maxhealth = 100;
   player.maxmana = 100;

   game.loadImages();
}

/* Update the game every frame */
game.game_update = function() {
   // Sprint
   if (game.keys[KEY.SHIFT]) {
      console.log(LoadedImages);
      player.speed = player.sprintspeed;
   } else {
      player.speed = player.walkspeed;
   }
   // Jump
   if (game.keys[KEY.UP] || game.keys[KEY.SPACE]) {
      if (!player.jumping) {
         player.jumping = true;
         player.velY = -player.walkspeed * 2;
      }
   }
   // Left
   if (game.keys[KEY.LEFT]) {
      player.direction = "L";
      if (player.velX < player.speed) {
         player.velX++;
      }
   }
   // Right
   if (game.keys[KEY.RIGHT]) {
      player.direction = "R";
      if (player.velX > -player.speed) {
         player.velX--;
      }
   }
   // Gravity and Friction
   player.velX *= game.enviroment.friction;
   player.velY += game.enviroment.gravity;
   player.x += player.velX;
   player.y += player.velY;

   // Collisions
   // LEFT RIGHT
   if (player.x >= game.canvas.width - player.width) { // Check Right Collision
      player.x = game.canvas.width - player.width;
   } else if (player.x <= 0) { // Check Left Collision
      player.x = 0;
   }
   // UP DOWN
   if (player.y >= game.canvas.height - player.height) {
      player.y = game.canvas.height - player.height;
      player.jumping = false;
   }

   // Draw Objects
   game.draw_background();
   game.draw_player();

   // Request next animation frame
   requestAnimationFrame(game.game_update);
}

game.draw_player = function() {
   ctx.beginPath();
   ctx.drawImage(LoadedImages.player_sprite, 64, 128, player.width, player.height, player.x, player.y, player.width, player.height);
   /*
   if (player.direction == "R") {
      ctx.drawImage(LoadedImages.player_sprite, 65, 128, player.width, player.height, player.x, player.y, player.width, player.height);
   } else if (player.direction == "L") {
      ctx.drawImage(LoadedImages.player_sprite, 63, 0, player.width, player.height, player.x, player.y, player.width, player.height);
   }
   */

   ctx.closePath();
}

game.draw_background = function() {
   ctx.beginPath();
   ctx.clearRect(0, 0, game.canvas.width, game.canvas.height);
   ctx.closePath();
}

game.draw_UI = function() {
   ctx.beginPath();

   ctx.closePath();
}

/* Listeners */
document.body.addEventListener("keydown", function(e) {
   game.keys[e.keyCode] = true;
});

document.body.addEventListener("keyup", function(e) {
   game.keys[e.keyCode] = false;
});

/* Load Game */
window.addEventListener("load", function() {
   game.onLoad();
});
body,
html {
   position: relative;
}

canvas {
   border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="700" height="500"></canvas>Use Arrow keys to move.
<p id="debug"><p>
User K
  • 380
  • 3
  • 16

1 Answers1

4

The issue seems to be that your player.x is a float. That way is hard to account for pixel perfect paints.

Round player.x on the drawImage() or when you update the value with velocity.

Using a bitwise operator, you could simply do:

player.x += player.velX | 0;

1cgonza
  • 1,589
  • 10
  • 20
  • 1
    Sure! BTW, great effort on putting together the question. One extra bit of advise. When you use `drawImage()` and `clearRect()` you do not need to begin and close paths. Those extra operations are not doing anything and on the long run you gain performance if you take them out. – 1cgonza Mar 15 '16 at 16:02
  • Thanks for the advice @1cgonza – User K Mar 15 '16 at 17:56
  • I also had this problem with image sprites, but only on Safari, even when rounding, but it seems that other browsers also have this problem when shears are applied. – D. Pardal Dec 27 '19 at 15:03