【发布时间】:2018-09-09 01:40:25
【问题描述】:
我正在用画布做游戏,但我对 drawImage(...); 方法有一个小问题,该方法应该裁剪精灵表以获得正确的精灵。当我们奔跑时,尤其是跳跃时,我们可以看到相邻精灵的一些片段。
(注意:如果您想运行此代码,请确保您运行 Firefox 或 Chrome,因为提供给 image-rendering 的值仅在这些浏览器上受支持)。
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>
如果您看不到边缘出血,这就是问题的一部分,这在每个浏览器上都没有类似的支持,我想要一个在每个浏览器上都相同的解决方案。这是我看到的:
感谢您的帮助。
EDIT1: 有一个类似的帖子here,但它并没有真正帮助,因为经过验证的答案使用了 2D 上下文的 setTransform(...); 方法,但即使它适用于 Safari和 IE,id (至少)不适用于 Firefox (see my output of the validated answer)。这个解决方案太“依赖于浏览器”,我想要一个得到强烈支持的解决方案。
这篇文章的第二个答案是关于在精灵表中的每个精灵周围添加一个 1 像素的空边框以避免边缘出血。这将需要完全修改精灵表,所以只有在没有更简单的解决方案时我才会接受这个答案。
【问题讨论】:
-
似乎在我的非视网膜显示器上为我工作。 0.5 偏移量在哪里?我没看到。为什么你有 0.5 的偏移量?
-
这是 HTML5 画布的一个特性,它不是从像素之间的线测量,而是从像素的中心测量,
ctx.translate(0.5, 0.5);应该允许禁用此特殊性。见this post。 -
您能否附上您遇到的问题的屏幕截图?您是否尝试过用单行透明像素填充您的精灵作为一种解决方法?
-
@Charlie pic
-
你确定你的数学是对的吗? Firefox 说 sprite sheet 是 246px 宽,代码说 sprite 是 41px 宽,我看到 6 对面,即 252px。你确定它是相邻的精灵而不是同一个被一些奇怪的纹理问题包裹的精灵吗?我建议给精灵涂上不同的颜色,这样你就可以在测试时分辨出哪个在流血。
标签: javascript html html5-canvas sprite-sheet