JavaScript canvas 一段时间后游戏 FPS 下降

JavaScript canvas game is dropping FPS after some time

最近我一直在尝试制作 JS canvas 项目,几天前我开始编写代码。我创建了在 canvas 上生成随机点并移动到它的敌人。我通过 运行 createEnemy() 函数创建敌人。它用敌人的数据创建一个对象并将其存储在“敌人”数组中。一切正常,除了我的 FPS :( 一段时间后,它们下降得非常厉害。发生了什么,为什么?这是我的代码:

var c = document.getElementById("canv");
var context = c.getContext("2d");
var mouseX, mouseY;
const fpsTime = [];
var fps;
var enemies = []
var speed = 2
c.width = window.innerWidth;
c.height = window.innerHeight;
document.addEventListener("mousemove", e => { mouseX = e.pageX; mouseY = e.pageY;});

function getEnemies() {
    return enemies;
}

function drawCharacter(x, y) {
    context.clearRect(0, 0, c.width, c.height);
    context.fillStyle = 'red';
    context.fillRect(x, y,50,60);
    context.save();
    context.font = "30px Arial";
}


function getCurrentMouse() {
    return {"x": mouseX, "y": mouseY}
}

function drawPoint(x, y) {
    context.fillStyle = 'red';
    context.fillRect(x, y,10,10);
    context.save();
}

function createEnemy(name) {
    var enemy = {
        name: name,
        didCompletePoint: true,
        targetX: 0,
        targetY: 0,
        currentX: Math.floor(Math.random() * (+window.innerWidth + 1 - +0)) + +0,
        currentY: Math.floor(Math.random() * (+window.innerHeight + 1 - +0)) + +0,
        generatePoint: function() {
            this.targetX = Math.floor(Math.random() * (+ window.innerWidth + 1 - +0)) + +0
            this.targetY = Math.floor(Math.random() * (+ window.innerHeight + 1 - +0)) + +0
            return [this.targetX, this.targetY];
        },
        draw: function() {
            context.fillStyle = 'black';
            context.fillRect(this.currentX, this.currentY,60,60);
            context.save();
            drawPoint(this.targetX, this.targetY)
            context.font = "30px Arial";
        }
    };
    enemies.push(enemy)
    return enemy
}

var enemy = createEnemy("tak")
var enemy1 = createEnemy("tak")
var enemy2 = createEnemy("tak")
var enemy3 = createEnemy("tak")
var enemy5 = createEnemy("tak")


function drawFrame() {
    document.getElementById("fps").innerHTML = "FPS: " + fps;
    drawCharacter(getCurrentMouse().x, getCurrentMouse().y)
    getEnemies().forEach((en, index) => {
        if(en.didCompletePoint) {
            en.didCompletePoint = false;
            en.generatePoint()
        }else {
            if((en.targetX === en.currentX) && (en.targetY === en.currentY)) {
                en.didCompletePoint = true;
            }
            else {
                //vertical movement
                if (en.targetY > en.currentY){
                    en.currentY++
                }
                else if (en.targetY < en.currentY) {
                    en.currentY--
                }
                //side movement

                // going right
                if (en.targetX > en.currentX) {
                    en.currentX++
                }
                // going left
                else if (en.targetX < en.currentX) {
                    en.currentX--
                }
            }
        }
        en.draw()
    })
}

function startLoop() {
    window.requestAnimationFrame(() => {
        const p = performance.now();
        while (fpsTime.length > 0 && fpsTime[0] <= p - 1000) {
            fpsTime.shift();
        }
        fpsTime.push(p);
        fps = fpsTime.length;
        drawFrame()
        startLoop();
    });
}

startLoop();
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            margin: 0 !important;
            padding: 0 !important;
        }
    </style>
</head>
<body>
<p id="fps" style="font-size: 30px; font-family: 'Calibri Light', serif; position: absolute; right: 2%; top: 0%;"></p>
<canvas id="canv" style="margin: 0;"></canvas>
</body>
<script src="script.js"></script>
</html>

首先我要说你进展顺利。使用传统的 ecma 可以很好地填充 good/comform 在 canvas 面向脚本中。

您在 context.save() 调用时出错。 在不调用 restore() 的情况下不断调用 save() 会给程序带来很大的痛苦 并导致内存泄漏。

canvas 2d context 必须用smart way.

  • 切勿无目的使用上下文。
  • 有些设置更昂贵,例如 .font 或 shadows。

保存恢复的用法

 // we have some complex setup initial already
 // but we need to change something
 ctx.save()
 ctx.fillStyle = "red";
 ctx.fillText ('use it' , 1, 1, 111, 111)
 ctx.restore()
 // Now back me to the old setup

差点忘记一个更大的错误: 我删除了 startLoop() 我把它放在 drawFrame 函数中的最后一次调用中。 这样我们的工作就流畅了。

说明: 您调用 drawFrame() 并同时调用 startLoop func 如何再次调用 drawFrame....


My english is bad but i am glad if i help...
    drawFrame()
    startLoop();

var c = document.getElementById("canv");
var context = c.getContext("2d");
var mouseX, mouseY;
const fpsTime = [];
var fps;
var enemies = []
var speed = 2
c.width = window.innerWidth;
c.height = window.innerHeight;
document.addEventListener("mousemove", e => { mouseX = e.pageX; mouseY = e.pageY;});

function getEnemies() {
    return enemies;
}

function drawCharacter(x, y) {
    context.clearRect(0, 0, c.width, c.height);
    context.fillStyle = 'red';
    context.fillRect(x, y,50,60);
    // context.font = "30px Arial";
}


function getCurrentMouse() {
    return {"x": mouseX, "y": mouseY}
}

function drawPoint(x, y) {
    context.fillStyle = 'red';
    context.fillRect(x, y,10,10);
}

function createEnemy(name) {
    var enemy = {
        name: name,
        didCompletePoint: true,
        targetX: 0,
        targetY: 0,
        currentX: Math.floor(Math.random() * (+window.innerWidth + 1 - +0)) + +0,
        currentY: Math.floor(Math.random() * (+window.innerHeight + 1 - +0)) + +0,
        generatePoint: function() {
            this.targetX = Math.floor(Math.random() * (+ window.innerWidth + 1 - +0)) + +0
            this.targetY = Math.floor(Math.random() * (+ window.innerHeight + 1 - +0)) + +0
            return [this.targetX, this.targetY];
        },
        draw: function() {
            context.fillStyle = 'black';
            context.fillRect(this.currentX, this.currentY,60,60);
            drawPoint(this.targetX, this.targetY)
            context.font = "30px Arial";
        }
    };
    enemies.push(enemy)
    return enemy
}

var enemy = createEnemy("tak")
var enemy1 = createEnemy("tak")
var enemy2 = createEnemy("tak")
var enemy3 = createEnemy("tak")
var enemy5 = createEnemy("tak")

function drawFrame() {
    document.getElementById("fps").innerHTML = "FPS: " + fps;
    drawCharacter(getCurrentMouse().x, getCurrentMouse().y)
    getEnemies().forEach((en, index) => {
        if(en.didCompletePoint) {
            en.didCompletePoint = false;
            en.generatePoint()
        }else {
            if((en.targetX === en.currentX) && (en.targetY === en.currentY)) {
                en.didCompletePoint = true;
            }
            else {
                //vertical movement
                if (en.targetY > en.currentY){
                    en.currentY++;
                }
                else if (en.targetY < en.currentY) {
                    en.currentY--;
                }
                //side movement

                // going right
                if (en.targetX > en.currentX) {
                    en.currentX++;
                }
                // going left
                else if (en.targetX < en.currentX) {
                    en.currentX--;
                }
            }
        }
        en.draw();
    })
    startLoop();
}

function startLoop() {
    window.requestAnimationFrame(() => {
        const p = performance.now();
        while (fpsTime.length > 0 && fpsTime[0] <= p - 1000) {
            fpsTime.shift();
        }
        fpsTime.push(p);
        fps = fpsTime.length;
        drawFrame();
    });
}

startLoop();
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            margin: 0 !important;
            padding: 0 !important;
        }
    </style>
</head>
<body>
<p id="fps" style="font-size: 30px; font-family: 'Calibri Light', serif; position: absolute; right: 2%; top: 0%;"></p>
<canvas id="canv" style="margin: 0;"></canvas>
</body>
<script src="script.js"></script>
</html>