javascript - 为什么清除 Canvas 元素并将其放回原处会出现故障?

标签 javascript html css html5-canvas

这是我发布的最后一个问题的问题,但是当我清除 Canvas 时,对象就会出现故障。它消失然后出现。但为什么会出现故障呢?我将间隔设置为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, 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]---------+
`|____________________|`

但是,您的方法将屏幕清除从同步更新 block 中拉出,以在随机点触发。在再次运行原子更新/渲染代码有机会重绘屏幕之前可能会有延迟。如果动画就像一本翻页书,这就像打开了显示空白页的可能性。

无论渲染循环多么简单(包括 setInterval),解决此问题的更好方法是记录发生的按键和用户输入,但不执行任何其他操作(包括屏幕清除、定位更新等)。然后,一旦您的更新/渲染函数再次有机会运行,您就可以批量更新实体位置并批量执行所有重新渲染。

在每一帧上清除并重新绘制屏幕似乎效率很低,但这就是动画的工作原理。如果您只想在按下某个键时清除并重绘,那没问题,但是完全忽略动画循环并仅在键处理程序中重新渲染屏幕。这对于许多应用程序来说都很好,但如果您正在制作游戏,您可能会希望敌人移动并且事情实时发生,即使玩家没有按下按钮,所以它可能会赢在这种情况下没有多大帮助。关键是不要混合使用两种方法。

所有这些都会导致键盘/UI 与游戏逻辑(玩家移动等)分离。您可以使用特定的 UI 到游戏逻辑耦合模块来处理将用户输入转换为玩家位置更改和其他状态更新。这与将渲染逻辑与游戏/状态更新逻辑解耦是相同的想法。

实体是任何可渲染的东西(响应render(ctx)函数)。从 OOP Angular 思考,boxplayer 共享相同的基类——它们是可渲染的矩形,因此我们可以使用排序和折腾的构造函数来构建它们在 playerspeed 属性上。

现在,当然,对于像这样的小型应用程序来说,其中一些可能是过早的优化,但您会看到它的代码量或多或少是相同的。

将上述所有要点放在一起,我们得到如下结果:

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

关于javascript - 为什么清除 Canvas 元素并将其放回原处会出现故障?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65399409/

相关文章:

html - Chrome 和 Opera 在使用显示 :table 时创建小填充

javascript - 单击按钮无法显示div

JavaScript 隐藏和显示内容

javascript - jquery如何访问选定的数据

javascript - Datepicker 在 Safari 和 FireFox 中不起作用

javascript - 使用 react-testing-library 的故事截图

javascript - 更改 Bootstrap btn css 和悬停操作问题

html - 防止背景图片水平滚动

javascript - AngularJS 测试 - 注入(inject) $location 导致错误

html - 是否有浏览器的视口(viewport)宽度(以 css 像素为单位)可以是小数?