I'm doing a game with canvas but I have a small problem with the the method drawImage(...);
which is supposed to crop the sprite sheet to get the proper sprite. When we run and especially when we jump, we can see bits of the adjacent sprite.
(Note: if you want to run this code, make sure you run either Firefox or Chrome as the values given to image-rendering
are only supported on these browsers).
var ctx;
var heightCanvas;
var widthCanvas;
var player;
var reqAnim;
var stopped;
left = false;
right = false;
up = false;
window.onload = function() {
var canvas = document.getElementById('canvas');
heightCanvas = canvas.height;
widthCanvas = canvas.width;
ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
//Detection of arrow keys
document.onkeydown = function(e) {
if (e.keyCode == 37) left = true;
if (e.keyCode == 39) right = true;
if (e.keyCode == 38) up = true;
}
document.onkeyup = function(e) {
if (e.keyCode == 37) left = false;
if (e.keyCode == 39) right = false;
if (e.keyCode == 38) up = false;
}
//The animation begins when the sprite sheet is loaded
img = new Image();
img.onload = function() {
player = new Player(img,10,50);
reqAnim = requestAnimationFrame(updateCanvas);
stopped = false;
}
img.src = "https://i.imgur.com/6eKrMOI.png";
}
function updateCanvas() {
ctx.clearRect(0, 0, widthCanvas, heightCanvas);
player.updatePos();
player.updateStateDirection();
player.updateSprite();
player.updateDisplay()
reqAnim = requestAnimationFrame(updateCanvas);
}
function startStop() {
if (stopped) {
reqAnim = requestAnimationFrame(updateCanvas);
stopped = false;
} else {
cancelAnimationFrame(reqAnim);
stopped = true;
}
}
//----------------------------------//
//----------------------------------//
//----------Code of Player----------//
//----------------------------------//
//----------------------------------//
function Player(spritesheet, x, y) {
this.spritesheet = spritesheet;
this.x = x;
this.y = y;
//The direction of the player. false = left, true = right
this.direction = true;
//The state of the player. 0 = stand, 1 = run
this.state = 0;
//The dimensions of a sprite in the sprite sheet
this.width = 41;
this.height = 40;
//All the attributes beginning with 'ss' are related with the sprite sheet.
//The coordinates of the current sprite in the sprite sheet
this.ssX = 0;
this.ssY = 200;
//The number of times that we have repeated the current sprite
this.ssRepeat = 0;
this.speed = 2.5;
this.gravity = 0.3;
this.gravitySpeed = 0;
this.jumping = false;
//state: 0 = stand, 1 = run
//direction: false = left, true = right
this.updateStateDirection = function() {
if (left) { //If left is pressed
if (this.state != 1 || this.direction) { //If the player wasn't running
this.state = 1; //or if he was running in the opposite direction
this.ssY = 0;
}
this.direction = false;
} else if (right) { //If right is pressed
if (this.state != 1 || !this.direction) { //If the player wasn't running
this.state = 1; //or if he was running in the opposite direction
this.ssY = 80;
}
this.direction = true;
} else if (this.state != 0) { //If neither right nor left are pressed and the state isn't stand
this.state = 0;
if (this.direction) this.ssY = 200;
else this.ssY = 160;
}
}
this.updateSprite = function() {
if (this.state == 0) { //If the state is stand
if (this.ssRepeat < 15) //We display the same sprite 15 times before passing to the next one
this.ssRepeat++;
else {
this.ssRepeat = 0;
if (this.ssX < 205) this.ssX += this.width; //If we didn't reach the end of the sprite sheet
else this.ssX = 0;
}
} else if (this.state == 1) { //If the state is run
if (this.ssRepeat < 5) //We display the same sprite 5 times before passing to the next one
this.ssRepeat++;
else {
this.ssRepeat = 0;
if (this.ssX < 205) this.ssX += this.width; //If we didn't reach the end of the sprite sheet
else {
this.ssX = 0;
if (this.ssY < 40) this.ssY = 40; //If we reached the end of the first line of the SS
else if (this.ssY < 80) this.ssY = 0; //the end of the second
else if (this.ssY < 120) this.ssY = 120; //the third
else this.ssY = 80; //the fourth
}
}
}
}
//Display the proper sprite of the spritesheet
this.updateDisplay = function() {
ctx.drawImage(this.spritesheet, this.ssX, this.ssY,
this.width, this.height, this.x, this.y, this.width, this.height);
}
//Updates the position of the sprite according to the user's inputs
this.updatePos = function() {
this.jump();
this.gravitySpeed += this.gravity;
this.y += this.gravitySpeed;
this.hitBottom();
this.move();
}
this.hitBottom = function() {
var rockbottom = heightCanvas - this.height;
if (this.y > rockbottom) {
this.y = rockbottom;
this.gravitySpeed = 0;
this.jumping = false;
}
}
this.move = function() {
if (left) player.x -= this.speed;
if (right) player.x += this.speed;
}
this.jump = function() {
if (!this.jumping) {
if (up) {
this.gravitySpeed = -5.2;
this.jumping = true;
}
}
}
}
<!DOCTYPE html>
<html>
<head>
<title>Forto</title>
<meta charset="UTF-8">
<style>
canvas {
border: 1px solid black;
background-color: #9e9eaf;
image-rendering: optimizespeed; /*Firefox*/
image-rendering: pixelated; /*Chrome*/
}
</style>
<script src="forto.js"></script>
</head>
<body>
<canvas id="canvas" width="300" height="100"></canvas>
<br>
<button onclick="startStop()">Start/Stop</button>
</body>
</html>
If you can't see the edge bleeding, that's part of the problem, this is not supported similarly on each browser and I want a solution that is the same on every browser. Here is what I see:
Thank you for your help.
EDIT1: There is a similar post here, but it doesn't really help because the validated answer uses the setTransform(...);
method of the 2D context, but even if it works for Safari and IE, id doesn't (at least) for Firefox (see my output of the validated answer). This solution is too 'browser dependant', I want a solution that is strongly supported.
The second answer of this post is about adding an empty border of 1 pixel around each sprite in the sprite sheet to avoid edge bleeding. This would require to rework entirely the sprite sheet, so I would accept this answer only if there is no simpler solution.