如何解决为什么清除画布元素并将其放回原处会使它出现故障?
这是我发布的最后一个问题的一个问题,但是当我清除画布时,对象就会出现故障。它消失然后出现。但是为什么会出现故障呢?我将间隔设置为 0 MS,但它只是弹出然后出现。
这里发生了小故障: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,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>
解决方法
虽然 accepted answer 解决了这个问题并提供了很好的建议来使用 requestAnimationFrame
作为其优化动画支持的一部分,它会自动批处理所有重新渲染,但手头还有更基本的设计问题。有问题的动画更像是一种症状。
尽管 requestAnimationFrame
足够聪明,可以(无意中)挽救设计,但您没有理由不能很好地使用 setInterval
(但不要使用 setInterval
除此以外的其他原因)。
动画的典型流程是:
______________________
|| :synchronous code: || :asynchronous code:
|| ||
|| [update state]<---------+
|| | || |
|| v || | (keypresses are collected,etc)
|| [clear screen] || |
|| | || |
|| v || |
|| [redraw screen]---------+
`|____________________|`
但是,您的方法将屏幕清除从同步更新块中拉出以在随机点触发。在另一次运行原子更新/渲染代码有机会重绘屏幕之前可能会有延迟。如果动画就像一本翻书,这就像打开了显示空白页的可能性。
一个更好的方法来解决这个问题,不管渲染循环有多么幼稚(包括 setInterval
),是在它发生时记录按键和用户输入,但什么都不做(包括屏幕清除、位置更新等) )。然后,一旦您的更新/渲染函数有机会再次运行,您就可以批量更新实体位置并批量执行所有重新渲染。
在每一帧上清除和重绘屏幕似乎效率低下,但这就是动画的工作原理。如果您只想在按下某个键时清除和重绘,那很好,但是然后完全省略动画循环并仅在键处理程序中重新渲染屏幕。这对于许多应用程序来说都很好,但是如果你正在制作一个游戏,你很可能希望敌人移动并实时发生事情,即使玩家没有按下按钮,所以它可能会赢在这种情况下没有多大帮助。关键是不要混合这两种方法。
所有这些都会导致键盘/UI 与游戏逻辑(玩家移动等)分离。您可以使用特定的 UI 到游戏逻辑耦合模块来处理用户输入到玩家位置变化和其他状态更新的转换。这与将渲染逻辑与游戏/状态更新逻辑解耦的想法相同。
实体是任何可渲染的东西(响应 render(ctx)
函数)。从 OOP 的角度考虑,box
和 player
共享相同的基类——它们是可渲染的矩形,因此我们可以使用 sorts 和 toss on the {{1} 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,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,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);
使用 requestAnimationFrame
代替 setInterval
。即使您为 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,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 只会冒意外错误的额外风险。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。