Canevas drawImage est inexplicablement compensé par 1 pixel

Je travaille sur un jeu de canvas qui utilise une feuille de sprite pour le personnage.
Les dimensions du personnage sont 64px large et 128px haut avec 10 images par animation.
Ainsi, la largeur totale d’une seule animation est de 640 pixels de large et de 128 pixels de haut.
Cependant, lorsque j’utilise le code suivant, l’animation est décalée de 1 px, parfois clignotante lorsque je maintiens une touche de déplacement enfoncée

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 ); 

Voici une image de ce qui se passe:
Zut
Changer la largeur à 63 semble résoudre le problème, mais cela n’explique pas pourquoi il le fait en premier lieu.

Le code complet est disponible sur Codepen
http://codepen.io/Codewoofy/pen/QNGLNj
Sprite Sheet:
Oh

Code complet:

 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 + "
"); } // 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--; } } // 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 = 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; } 
  Use Arrow keys to move. 

Le problème semble être que votre player.x est un float. De cette façon, il est difficile de rendre compte des peintures au pixel parfait.

player.x sur drawImage() ou lorsque vous mettez à jour la valeur avec vélocité.

En utilisant un opérateur au niveau des bits, vous pouvez simplement faire:

player.x += player.velX | 0;