为什么清除 canvas 元素并将其放回原处会使它出现故障?
Why does clearing a canvas element and putting it back in make it glitch?
这是我发布的上一个问题的问题,但是当我清除 canvas 时,对象就会出现故障。它消失然后出现。但是为什么会出错呢?我把间隔设置为0毫秒,但它只是弹出然后出现。
这里出现了小故障:https://cmt-1.hoogidyboogidy.repl.co/
可能只是这个设备。我使用笔记本电脑和电脑。一台 windows 7 电脑很旧,还有一台 chromebook,嗯,很糟糕。
const c = document.getElementById('c')
const ctx = c.getContext('2d')
c.height = window.innerHeight
c.width = window.innerWidth
let blockInfo = {
h: 15, // Defining height and width so they can be changed. (NOTE: The height and width doesn't have to be the same value)
w: 15
}
let renderedObjects = false // Keep in case you're gonna make colission
let player = {
speed: 0.125,
x: 2,
y: 2,
height: 1,
width: 1
}
function clearObjects() {
// Clear all
ctx.clearRect(0, 0, c.width, c.height)
}
function renderObjects() {
// Render block
ctx.fillStyle = '#00b02f'
ctx.fillRect(5 * blockInfo.w, 5 * blockInfo.h, blockInfo.w, blockInfo.h)
// Render player
ctx.fillStyle = '#000000'
ctx.fillRect(player.x * blockInfo.w, player.y * blockInfo.h, player.width * blockInfo.w, player.height * blockInfo.h)
}
function press(e) {
let w = e.which
if (renderedObjects == true) {
clearObjects()
if (w == 39) {
player.x += player.speed
} else if (w == 37) {
player.x -= player.speed
} else if (w == 38) {
player.y -= player.speed
} else if (w == 40) {
player.y += player.speed
}
}
}
setInterval(function() {
renderedObjects = false
renderObjects()
renderedObjects = true
}, 0)
body {
margin: 0;
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>repl.it</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body onkeydown="press(event)">
<canvas id="c"></canvas>
<script src="script.js"></script>
</body>
</html>
而不是 setInterval
,使用 requestAnimationFrame
。即使您为 setInterval
指定了 0,它实际上仍然会在再次调用回调之前等待一些最小延迟。这与您的设备无关。 requestAnimationFrame
特别适用于...动画。
const c = document.getElementById('c');
const ctx = c.getContext('2d');
c.height = window.innerHeight;
c.width = window.innerWidth;
let blockInfo = {
h: 15, // Defining height and width so they can be changed. (NOTE: The height and width doesn't have to be the same value)
w: 15
};
let renderedObjects = false; // Keep in case you're gonna make colission
let player = {
speed: 0.125,
x: 2,
y: 2,
height: 1,
width: 1
};
function clearObjects() {
// Clear all
ctx.clearRect(0, 0, c.width, c.height);
}
function renderObjects() {
// Render block
ctx.fillStyle = '#00b02f';
ctx.fillRect(5 * blockInfo.w, 5 * blockInfo.h, blockInfo.w, blockInfo.h);
// Render player
ctx.fillStyle = '#000000';
ctx.fillRect(player.x * blockInfo.w, player.y * blockInfo.h, player.width * blockInfo.w, player.height * blockInfo.h);
}
function press(e) {
let w = e.which;
if (renderedObjects == true) {
clearObjects();
if (w == 39) {
player.x += player.speed;
} else if (w == 37) {
player.x -= player.speed;
} else if (w == 38) {
player.y -= player.speed;
} else if (w == 40) {
player.y += player.speed;
}
}
}
(function loop() {
renderedObjects = false;
renderObjects();
renderedObjects = true;
requestAnimationFrame(loop);
})();
body {
margin: 0;
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>repl.it</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body onkeydown="press(event)">
<canvas id="c"></canvas>
<script src="script.js"></script>
</body>
</html>
无关:请用明确的分号结束你的陈述。依赖 automatic semi colon insertion 只是冒着意外错误的额外风险。
虽然 解决了问题并提供了使用 requestAnimationFrame
的很好建议,它自动批处理所有重新渲染作为其优化动画支持的一部分,但手头还有更多基本的设计问题。故障动画比其他任何东西更能说明这一点。
尽管 requestAnimationFrame
足够聪明(无意中)将设计排除在外,但您没有理由不能很好地使用 setInterval
(但不要使用 setInterval
除此之外的其他原因)。
典型的动画流程是:
______________________
|| :synchronous code: || :asynchronous code:
|| ||
|| [update state]<---------+
|| | || |
|| v || | (keypresses are collected, etc)
|| [clear screen] || |
|| | || |
|| v || |
|| [redraw screen]---------+
`|____________________|`
但是,您的方法将屏幕清除从同步更新块中拉出以在随机点触发。在另一个 运行 原子 update/render 代码有机会重绘屏幕之前可能会有延迟。如果动画就像一本翻书,这就像打开了显示空白页的可能性。
无论渲染循环多么天真(包括 setInterval
),解决此问题的更好方法是在发生时记录按键和用户输入,但不执行任何其他操作(包括屏幕清除、位置更新, ETC)。然后,一旦您的 update/render 函数再次获得 运行 的机会,您就可以批量更新实体位置并批量执行所有重新渲染。
在每一帧上清除和重绘屏幕似乎效率低下,但这就是动画的工作原理。如果您只想在按下某个键时清除和重绘,那很好,但随后完全省略动画循环并仅在键处理程序中重新渲染屏幕。这对许多应用程序来说都很好,但如果你正在制作游戏,你可能希望敌人移动并且事情实时发生,即使玩家没有按下按钮,所以它可能会赢在这种情况下帮助很大。关键是不要混用这两种方法。
所有这些导致 keyboard/UI 与游戏逻辑(玩家移动等)脱钩。您可以使用特定的 UI-to-game 逻辑耦合模块来处理将用户输入转换为玩家位置变化和其他状态更新。这与从 game/state 更新逻辑中解耦渲染逻辑的想法相同。
实体是任何可渲染的东西(响应 render(ctx)
函数)。从 OOP 的角度考虑,box
和 player
共享相同的基础 class——它们是可渲染的矩形,因此我们可以使用某种构造函数来构建它们,然后在speed
属性 对于 player
。
现在,当然,对于像这样的小应用程序,其中一些可能是过早的优化,但您会发现它的代码量大致相同。
将以上所有要点放在一起,我们得到如下内容:
const canvas = document.createElement("canvas");
document.body.append(canvas);
canvas.width = innerWidth;
canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
const makeRectangleEntity = props => ({
...props,
render: function (ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
});
const block = makeRectangleEntity({
x: 75, y: 75, w: 15, h: 15, color: "#00b02f"
});
const player = makeRectangleEntity({
x: 20, y: 20, w: 15, h: 15,
color: "#000", speed: 0.125
});
const entities = [block, player];
const keyCodeActions = {
37: () => (player.x -= player.speed),
38: () => (player.y -= player.speed),
39: () => (player.x += player.speed),
40: () => (player.y += player.speed),
};
const keysPressed = new Set();
document.addEventListener("keydown", e => {
keysPressed.add(e.keyCode);
});
document.addEventListener("keyup", e => {
keysPressed.delete(e.keyCode);
});
// Show that setInterval wasn't the problem, but do
// replace this with requestAnimationFrame anyway.
setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// update entity state
for (const keyCode of keysPressed) {
if (keyCode in keyCodeActions) {
keyCodeActions[keyCode]();
}
}
// redraw screen
entities.forEach(e => e.render(ctx));
}, 0);
body {
margin: 0;
overflow: hidden;
}
这是我发布的上一个问题的问题,但是当我清除 canvas 时,对象就会出现故障。它消失然后出现。但是为什么会出错呢?我把间隔设置为0毫秒,但它只是弹出然后出现。
这里出现了小故障:https://cmt-1.hoogidyboogidy.repl.co/
可能只是这个设备。我使用笔记本电脑和电脑。一台 windows 7 电脑很旧,还有一台 chromebook,嗯,很糟糕。
const c = document.getElementById('c')
const ctx = c.getContext('2d')
c.height = window.innerHeight
c.width = window.innerWidth
let blockInfo = {
h: 15, // Defining height and width so they can be changed. (NOTE: The height and width doesn't have to be the same value)
w: 15
}
let renderedObjects = false // Keep in case you're gonna make colission
let player = {
speed: 0.125,
x: 2,
y: 2,
height: 1,
width: 1
}
function clearObjects() {
// Clear all
ctx.clearRect(0, 0, c.width, c.height)
}
function renderObjects() {
// Render block
ctx.fillStyle = '#00b02f'
ctx.fillRect(5 * blockInfo.w, 5 * blockInfo.h, blockInfo.w, blockInfo.h)
// Render player
ctx.fillStyle = '#000000'
ctx.fillRect(player.x * blockInfo.w, player.y * blockInfo.h, player.width * blockInfo.w, player.height * blockInfo.h)
}
function press(e) {
let w = e.which
if (renderedObjects == true) {
clearObjects()
if (w == 39) {
player.x += player.speed
} else if (w == 37) {
player.x -= player.speed
} else if (w == 38) {
player.y -= player.speed
} else if (w == 40) {
player.y += player.speed
}
}
}
setInterval(function() {
renderedObjects = false
renderObjects()
renderedObjects = true
}, 0)
body {
margin: 0;
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>repl.it</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body onkeydown="press(event)">
<canvas id="c"></canvas>
<script src="script.js"></script>
</body>
</html>
而不是 setInterval
,使用 requestAnimationFrame
。即使您为 setInterval
指定了 0,它实际上仍然会在再次调用回调之前等待一些最小延迟。这与您的设备无关。 requestAnimationFrame
特别适用于...动画。
const c = document.getElementById('c');
const ctx = c.getContext('2d');
c.height = window.innerHeight;
c.width = window.innerWidth;
let blockInfo = {
h: 15, // Defining height and width so they can be changed. (NOTE: The height and width doesn't have to be the same value)
w: 15
};
let renderedObjects = false; // Keep in case you're gonna make colission
let player = {
speed: 0.125,
x: 2,
y: 2,
height: 1,
width: 1
};
function clearObjects() {
// Clear all
ctx.clearRect(0, 0, c.width, c.height);
}
function renderObjects() {
// Render block
ctx.fillStyle = '#00b02f';
ctx.fillRect(5 * blockInfo.w, 5 * blockInfo.h, blockInfo.w, blockInfo.h);
// Render player
ctx.fillStyle = '#000000';
ctx.fillRect(player.x * blockInfo.w, player.y * blockInfo.h, player.width * blockInfo.w, player.height * blockInfo.h);
}
function press(e) {
let w = e.which;
if (renderedObjects == true) {
clearObjects();
if (w == 39) {
player.x += player.speed;
} else if (w == 37) {
player.x -= player.speed;
} else if (w == 38) {
player.y -= player.speed;
} else if (w == 40) {
player.y += player.speed;
}
}
}
(function loop() {
renderedObjects = false;
renderObjects();
renderedObjects = true;
requestAnimationFrame(loop);
})();
body {
margin: 0;
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>repl.it</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body onkeydown="press(event)">
<canvas id="c"></canvas>
<script src="script.js"></script>
</body>
</html>
无关:请用明确的分号结束你的陈述。依赖 automatic semi colon insertion 只是冒着意外错误的额外风险。
虽然 requestAnimationFrame
的很好建议,它自动批处理所有重新渲染作为其优化动画支持的一部分,但手头还有更多基本的设计问题。故障动画比其他任何东西更能说明这一点。
尽管 requestAnimationFrame
足够聪明(无意中)将设计排除在外,但您没有理由不能很好地使用 setInterval
(但不要使用 setInterval
除此之外的其他原因)。
典型的动画流程是:
______________________
|| :synchronous code: || :asynchronous code:
|| ||
|| [update state]<---------+
|| | || |
|| v || | (keypresses are collected, etc)
|| [clear screen] || |
|| | || |
|| v || |
|| [redraw screen]---------+
`|____________________|`
但是,您的方法将屏幕清除从同步更新块中拉出以在随机点触发。在另一个 运行 原子 update/render 代码有机会重绘屏幕之前可能会有延迟。如果动画就像一本翻书,这就像打开了显示空白页的可能性。
无论渲染循环多么天真(包括 setInterval
),解决此问题的更好方法是在发生时记录按键和用户输入,但不执行任何其他操作(包括屏幕清除、位置更新, ETC)。然后,一旦您的 update/render 函数再次获得 运行 的机会,您就可以批量更新实体位置并批量执行所有重新渲染。
在每一帧上清除和重绘屏幕似乎效率低下,但这就是动画的工作原理。如果您只想在按下某个键时清除和重绘,那很好,但随后完全省略动画循环并仅在键处理程序中重新渲染屏幕。这对许多应用程序来说都很好,但如果你正在制作游戏,你可能希望敌人移动并且事情实时发生,即使玩家没有按下按钮,所以它可能会赢在这种情况下帮助很大。关键是不要混用这两种方法。
所有这些导致 keyboard/UI 与游戏逻辑(玩家移动等)脱钩。您可以使用特定的 UI-to-game 逻辑耦合模块来处理将用户输入转换为玩家位置变化和其他状态更新。这与从 game/state 更新逻辑中解耦渲染逻辑的想法相同。
实体是任何可渲染的东西(响应 render(ctx)
函数)。从 OOP 的角度考虑,box
和 player
共享相同的基础 class——它们是可渲染的矩形,因此我们可以使用某种构造函数来构建它们,然后在speed
属性 对于 player
。
现在,当然,对于像这样的小应用程序,其中一些可能是过早的优化,但您会发现它的代码量大致相同。
将以上所有要点放在一起,我们得到如下内容:
const canvas = document.createElement("canvas");
document.body.append(canvas);
canvas.width = innerWidth;
canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
const makeRectangleEntity = props => ({
...props,
render: function (ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
});
const block = makeRectangleEntity({
x: 75, y: 75, w: 15, h: 15, color: "#00b02f"
});
const player = makeRectangleEntity({
x: 20, y: 20, w: 15, h: 15,
color: "#000", speed: 0.125
});
const entities = [block, player];
const keyCodeActions = {
37: () => (player.x -= player.speed),
38: () => (player.y -= player.speed),
39: () => (player.x += player.speed),
40: () => (player.y += player.speed),
};
const keysPressed = new Set();
document.addEventListener("keydown", e => {
keysPressed.add(e.keyCode);
});
document.addEventListener("keyup", e => {
keysPressed.delete(e.keyCode);
});
// Show that setInterval wasn't the problem, but do
// replace this with requestAnimationFrame anyway.
setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// update entity state
for (const keyCode of keysPressed) {
if (keyCode in keyCodeActions) {
keyCodeActions[keyCode]();
}
}
// redraw screen
entities.forEach(e => e.render(ctx));
}, 0);
body {
margin: 0;
overflow: hidden;
}