Over time, the FPS of the JavaScript canvas game is decreasing

I've recently delved into a JavaScript canvas project, and a few days ago I began the coding process. I have enemies that are generated with random points on the canvas and move towards them. These enemies are created by running the createEnemy() function, which generates an object with the enemy's data and stores it in the "enemies" array. Everything seems to be working fine until my FPS drops significantly after some time. I'm baffled as to why this is happening. Here's a snippet of my code:

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>

Answer №1

Let me start by acknowledging that you are making progress in the right direction. Utilizing traditional ECMAScript is beneficial for creating a solid foundation in canvas-oriented scripts.

However, there is a mistake in your usage of context.save(). Simply repeatedly calling save() without a corresponding restore() can create challenges for your program and lead to memory leaks.

It is crucial to use the canvas 2D context judiciously.

  • Avoid using the context without a clear purpose.
  • Some setups, like setting the .font or shadows, can be resource-intensive.

Here's the correct way to use save and restore:

// We have already set up a complex configuration initially
// But we need to make some changes
ctx.save();
ctx.fillStyle = "red";
ctx.fillText('use it', 1, 1, 111, 111);
ctx.restore();
// Reverting back to the original setup now

Almost forgot to mention one major error: I removed startLoop() and placed it as the final call in the drawFrame function. This ensures smooth operation.

Explanation:

Calling drawFrame() and then immediately calling startLoop(), which in turn calls drawFrame again...


My English may not be perfect, but I hope I can be of assistance...
    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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Implementing CSS styles to Iframe content

I have written the following code snippet to display an iframe: <iframe scrolling='no' frameborder='1' id='fblike' src='http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Fwww.XYZ.com%2F&amp;send=false& ...

Tips for resolving the error message "TypeError: Cannot access property 'category' of undefined" while working in a React environment

Hey there, I encountered an issue while trying to destructure in react and can't figure out why the title is appearing as an error message. Below is the code snippet: const {correct_answer, incorrect_answers} = data[0] const arr = [correct_answer, ... ...

How to send parameters to the jQuery delete button click event handler

Here is the jQuery code I am working with: $('#btnDelete').click( function() {//Do the delete here via jquery post}); In my table, each row has a delete button like this: <a id="btnDelete">Delete</a> I need to pass parameters to t ...

How to Use Attributes as Component Parameters in Vue.js

I am currently developing a test Component using Vue.js. I am trying to pass a parameter to be used in my template like this: Vue.component('test', { props: ['href'], template: '<li><a href="{{href}}"><slot> ...

Dealing with the response from an AJAX post sent from an Express server: strategies and tips

As a beginner, I might not have been clear in my previous question. The issue I'm facing is that the response I receive from my express server is showing on the page, but I am unable to handle it on the client side using jQuery. On the server-side: r ...

Creating a custom Angular filter that leverages the power of $http - a

I want to make a retrieval request using $http in AngularJS and then display the obtained result on the front-end. To achieve this, I'm using {{ URL-STRING | iframely }}. 'use strict' angular.module( 'iframely', [] ).filter( &ap ...

How to fix the Uncaught TypeError: Unable to access the 'open' property of an undefined value in the AppBar + Drawer component with ReactJS and Material-UI?

I'm currently working on implementing the navigation feature in my app using Material-UI framework. I want the default hamburger icon on the left side of the AppBar to open the Drawer component when clicked. However, I keep encountering an error that ...

Prisma auto-generating types that were not declared in my code

When working with a many-to-many relationship between Post and Upload in Prisma, I encountered an issue where Prisma was assigning the type 'never' to upload.posts. This prevented me from querying the relationship I needed. It seems unclear why P ...

How can I make sure the return statement in getServerSideProps is only executed once all fetching operations are finished?

Currently, I am able to retrieve a person's username and corresponding data object with just one fetch from firebase. Inside this data object, there is a property named "uploads," which contains an array of documentIDs representing posts uploaded by t ...

What is the best way to integrate Google Analytics into a Next.js application without the need for an _app.js or _document.js file?

I'm encountering some challenges while trying to incorporate Google Analytics into my Next.js application. One issue I'm facing is the absence of an _app.js or _document.js file in the project structure. Additionally, I notice that when I include ...

Resolving Node.js Absolute Module Paths with TypeScript

Currently, I am facing an issue where the modules need to be resolved based on the baseUrl so that the output code is compatible with node.js. Here is my file path: src/server/index.ts import express = require('express'); import {port, database ...

What is the best way to insert a value into the span element generated by the Tag-it plugin?

Currently working with the Tag-it plugin to generate tags. Since the tags are created using spans, I'm curious to learn how I can assign a value to a tag created by the plugin? Seeking assistance from someone knowledgeable in this area. Thank you! ...

Converting JavaScript code to jQuery and integrating it into a WordPress website has become a common practice

I recently developed a working javascript function as shown below: function calc(A,B,SUM) { var one = Number(A); var two = Number(document.getElementById(B).value); if (isNaN(one)) { alert('Invalid entry: '+A); one=0; } if (isNaN(tw ...

Implement jQuery to close multiple div elements

My dilemma involves a series of divs with different manufacturer IDs listed as follows: manufacturer[1], manufacturer[2], manufacturer[3], ... etc ... In attempting to create a JavaScript for loop to hide each div, I discovered that it was not achievab ...

Spotlight the flaw in the card's backbone using JS

What's the most effective method for emphasizing an error card view in backbone? Initially, I render 10 cards as UI where users input details in each card. Upon clicking submit, I validate all the details by parsing through collection->models. Curr ...

Transform Django Model Instance from Serialized Back to Object using Ajax

Currently, I'm utilizing Ajax to search for a model instance. Once found, I return that instance and pass it as a variable to a template tag within my template. To achieve this, in my view, I serialize the object before sending it to the Ajax success ...

Are extra parameters in the URL causing issues with AngularJS routing?

When I receive password reset instructions in my app, the URL I use to go to the server looks like this: /changepass?key=1231231231212312 In the controller, I have the following code: if (typeof $routeParams.key !== 'undefined') { $scope ...

Pressing the enter key in an AngularJS form does not trigger submission

Having trouble with a login form that won't submit when the user presses enter. While the form works fine when the "Login" button is clicked, hitting enter doesn't trigger submission and leads to some unexpected behavior: The ng-submit associat ...

What is the method of using "*" as the value for allowedModules in a jsreport configuration file to enable all modules?

I am having an issue when trying to generate a report using jsreport STUDIO. The error message I received is as follows: An error occurred - Error during rendering report: Unsupported module in scripts: request. To enable require on a particular module, ...

How to Extract an Object from an Array using React

I have a situation where I am fetching data from an API using axios. Specifically, on my invoice details page, I am trying to retrieve data for only one invoice using the following code: const id = props.match.params.id; const invoice = useSelecto ...