4
\$\begingroup\$

I got bored and started making a game using HTML5, CSS3, and JavaScript/jQuery. I've been working on it for about 2 weeks and it was running pretty well.

Today I opened it and was moving the player's ship around the screen and noticed that it became very slow as the game went on, starting at 10 seconds after the page loaded. This happens whether the ship has been moving around the entire time or sat still and was then moved after the 10 seconds.

I have never made a game before and did this more as a learning exercise than anything else.

HTML:

<!DOCTYPE html>
<html lang='en'>
    <head>
        <title>Flying Ship</title>
        <link type='text/css' rel='stylesheet' href='stylesheet.css'/>
        <script type='text/javascript' src='jquery-1.11.3.js'></script>
        <script type='text/javascript' src='jquery-color.js'></script>
        <script type='text/javascript' src='jquery-shadow.js'></script>
        <script type='text/javascript' src='flying_ship_scripts.js'></script>
    </head>
    <body>
        <canvas id='solar_system_canvas'>Your browser does not appear to support HTML5!</canvas>
        <canvas id='game_abilities_bar_canvas'></canvas>

        <div id='abilities_boost_text'>Speed Boost:</div>

        <div id='canvas_shadow_container_left'   class='canvas_shadow_container'></div>
        <div id='canvas_shadow_container_top'    class='canvas_shadow_container'></div>
        <div id='canvas_shadow_container_right'  class='canvas_shadow_container'></div>
        <div id='canvas_shadow_container_bottom' class='canvas_shadow_container'></div>
    </body>
</html>

JavaScript/jQuery:

$(document).ready(function() {

    /* ----------------------------------------------------------------------------------------------------- */
    /* Variables and Set-up                                                                                   */
    /* ----------------------------------------------------------------------------------------------------- */

    // Get the width and height of the browser view port.
    var windowW = $(window).width();
    var windowH = $(window).height();

    // Number of images that will need to be loaded.
    var imagesToLoad = 1;
    // Initial number of images that we have loaded.
    // The ReportAllImagesLoaded() function will increment imageLoaded each time
    // an image is loaded until it equals imageToLoad.
    var imagesLoaded = 0;

    // Indicators of whether that specified border is being "touched" by 
    // the player's ship. 
    var borderLeft   = false;
    var borderTop    = false;
    var borderRight  = false;
    var borderBottom = false;

    // The percentage of the player's speed boost. 
    var boost_bar_percent = 100;

    // The player's ship. PNG image. The image is 150x300 pixels.
    // The ship is 110px wide and 200px tall. (20px to 130px, 50px to 250px).
    var player_ship_image;

    // Ships object variables.
    var player_ship;

    // The state of each key in dictionary form.
    // 37: Left arrow.
    // 38: Up arrow.
    // 39: Right arrow.
    // 40: Down arrow.
    var keyStates = {
        37: false,        // Left arrow.
        38: false,        // Up arrow.
        39: false,        // Right arrow.
        40: false,        // Down arrow.
        65: false,        // 'a' key ('left-arrow').
        87: false,        // 'w' key ('up-arrow').
        68: false,        // 'd' key ('right-arrow').
        83: false,        // 's' key ('down-arrow').
        16: false         // Shift key.
    };


    /* ----------------------------------------------------------------------------------------------------- */

    /* ----------------------------------------------------------------------------------------------------- */
    /* Program Code                                                                                          */
    /* ----------------------------------------------------------------------------------------------------- */


    // Get the context of the HTML5 canvas and set it to 2D.
    var canvas = $('#solar_system_canvas');
    var ctx = canvas[0].getContext('2d');

    // Get the context of the canvas used for the abilities bar.
    // A second canvas is used on the assumption that it will yield a performance increase (however small).
    var abilities_canvas = $('#game_abilities_bar_canvas');
    var abilities_ctx = abilities_canvas[0].getContext('2d');

    // Initialize the canvas size the first time.
    SetupCanvas();

    // Load all of the images, initialize the ship objects, and start the game.
    // LoadImages() calls ReportAllImagesLoaded(), which calls InitializeShips(), which
    // calls the main GameLoop().
    LoadImages();


    /* ----------------------------------------------------------------------------------------------------- */

    /* ----------------------------------------------------------------------------------------------------- */
    /* Window Events                                                                                         */
    /* ----------------------------------------------------------------------------------------------------- */


    // When the browser window is resized...
    $(window).on("resize", function()
    {
        // Get the new size and height of the window.
        windowW = $(window).width();
        windowH = $(window).height();

        // Resize the canvas by calling SetupCanvas().
        SetupCanvas();

        // Redraw all the ships.
        DrawAllShips();
    });

    // Listen for key-down and key-up events. Set the corresponding key to 
    // true or false depending on which key is pressed.
    $(window).keydown(function(e) {
        keyStates[e.keyCode || e.which] = true;
    }).keyup(function(e) {
        keyStates[e.keyCode || e.which] = false;
    });


    /* ----------------------------------------------------------------------------------------------------- */

    /* ----------------------------------------------------------------------------------------------------- */
    /* Functions                                                                                             */
    /* ----------------------------------------------------------------------------------------------------- */


    // The main game loop.
    function GameLoop()
    {
        // Check which keys are pressed and modify the player's position.
        CheckKeyPresses();

        // Check to make sure the ship is still within the browser window.
        EnsureNoOutOfBounds();

        // Check if the player is at the window border and activate the border
        // highlight if so.
        EnableBorderMarker();

        // Draw all the ships on the canvas.
        DrawAllShips();

        DrawAbilitiesBar();

        // Repeat on next screen refresh.
        window.requestAnimationFrame(GameLoop);
    }

    // Sets up the canvas size and pixel resolution.
    function SetupCanvas()
    {
        // Set the canvas width, height, and resolution to match the window size.
        canvas.width(windowW);
        canvas.height(windowH);
        canvas[0].width  = windowW;
        canvas[0].height = windowH;

        abilities_canvas[0].width = 450;
        abilities_canvas[0].height = 40;

        // Set the width and height of the window border container
        $('#canvas_inset_shadow_container').width(windowW);
        $('#canvas_inset_shadow_container').height(windowH);
    }

    // Loads all the images.
    function LoadImages()
    {
        // Load the player ship image.
        player_ship_image = new Image();
        player_ship_image.src = 'images/ships/player_ship.png';

        // Once loaded, notify in the console and check if all images loaded.
        player_ship_image.onload = function()
        {
            console.log("Player ship image loaded.");
            ReportAllImagesLoaded();
        }
    }

    // Checks if all images have loaded and reports to user.
    function ReportAllImagesLoaded()
    {
        // Increment the number of images loaded.
        imagesLoaded++;

        // If the images loaded equals the number of images we are supposed to load...
        if (imagesLoaded == imagesToLoad)
        {
            // Notify in the console.
            console.log("All images loaded.");

            // Initialize all the ships.
            InitializeShips();
        }
    }

    // Creates new ships objects for each ship 
    function InitializeShips()
    {
        // Create the player ship, starting it at the center of the screen.
        player_ship = new Ship(player_ship_image, windowW / 2, windowH / 2, 16, 32, 0);

        // Start the "game".
        GameLoop();
    }

    // Calls the DrawImage() method for each ship object to draw them.
    function DrawAllShips()
    {
        // Clear the canvas.
        ctx.clearRect(0, 0, windowW, windowH);

        // Draw the player's ship.
        player_ship.DrawImage();
    }

    // Ship class containing image, position, and rotation.
    // Rotation starts at 90 degrees (direct north) and goes clockwise.
    function Ship(image_src, pos_x, pos_y, width, height, rotation)
    {
        // The actual image variable.
        this.image = image_src;

        // Set the x and y position of the image.
        this.pos_x = pos_x;
        this.pos_y = pos_y;

        // Set the width and height of the image.
        this.image_width  = width;
        this.image_height = height;

        // Set the rotation of the image.
        this.rotation = rotation;

        // Loads the image from the image source.
        this.SetImage = function(new_image)
        {
            this.image = new_image;
        }

        // Sets the x-position of the image.
        this.SetPos_X = function(new_pos_x)
        {
            this.pos_x = new_pos_x;
        }

        // Sets the y-position of the image.
        this.SetPos_Y = function(new_pos_y)
        {
            this.pos_y = new_pos_y;
        }

        // Sets the rotation of the image.
        this.SetRotation = function(new_rotation)
        {
            this.rotation = new_rotation;
        }

        // Returns the x-position of the image.
        this.GetPos_X = function()
        {
            return this.pos_x;
        }

        // Returns the y-position of the image.
        this.GetPos_Y = function()
        {
            return this.pos_y;
        }

        // Returns the rotation of the image.
        this.GetRotation = function()
        {
            return this.rotation;
        }

        // Returns the width of the image.
        this.GetImageWidth = function()
        {
            return this.image_width;
        }

        // Returns the height of the image.
        this.GetImageHeight = function()
        {
            return this.image_height;
        }

        // Draws the image on the canvas.
        this.DrawImage = function()
        {
            // Save the canvas.
            ctx.save();

            // Move the canvas to the image's position.
            ctx.translate(this.pos_x, this.pos_y);

            // Rotate the canvas (the image along with it) by the image's rotation.
            ctx.rotate(this.rotation * Math.PI / 180);

            // Center the image on its position.
            ctx.translate(-(this.GetImageWidth() / 2), -(this.GetImageHeight() / 2));

            // Draw the image.
            ctx.drawImage(this.image, 0, 0, this.image_width, this.image_height);

            // Restore the canvas to before it was translated and rotated.
            ctx.restore();
        }
    }

    // Checks key presses and updates player ship information based on it.
    function CheckKeyPresses()
    {
        // Check and update the rotation of the player's ship.
        // Rotate counter-clockwise by 5 degrees if left arrow or 'a' is pressed.
        if ((keyStates[37] == true) || (keyStates[65] == true))
        {
            // If turning counter-clockwise results in a rotation 
            // less than -180 degrees, set to +175 degrees.
            if ((player_ship.GetRotation() - 5) < -180)
            {
                player_ship.SetRotation(175);
            }
            // Otherwise, get the rotation and subtract 5 degrees.
            else
            {
                player_ship.SetRotation(player_ship.GetRotation() - 5);
            }
        }
        // Rotate clockwise by 5 degrees if right arrow or 'd' is pressed.
        if ((keyStates[39] == true) || (keyStates[68] == true))
        {
            // If turning clockwise results in a rotation 
            // greater than +180 degrees, set to -175 degrees.
            if ((player_ship.GetRotation() - 5) > 180)
            {
                player_ship.SetRotation(-175);
            }
            // Otherwise, get the rotation and add 5 degrees.
            else
            {
                player_ship.SetRotation(player_ship.GetRotation() + 5);
            }
        }

        // If any of the move forward or move backward keys are pressed...
        if ((keyStates[38] == true) || // Up arrow.
            (keyStates[40] == true) || // Down arrow.
            (keyStates[87] == true) || // 'w' key.
            (keyStates[83] == true))   // 's' key.
        {   
            // If the shift key is pressed, change ship "speed" to 8px.
            if (keyStates[16] == true) {
                moveDistance = 8;
            }
            // Otherwise, set it to 5px.
            else
            {
                moveDistance = 5;
            }

            // If the rotation is 0 degrees (ship is pointing "up")...
            if (player_ship.GetRotation() == 0)
            {
                // If the ship is supposed to move forward...
                if ((keyStates[38] == true) || (keyStates[87] == true))
                {
                    // Get the current ship position.
                    var y_pos = player_ship.GetPos_Y();
                    // Set the position to 5 pixels ahead of where it is.
                    player_ship.SetPos_Y(y_pos - moveDistance);
                }

                // If the ships is supposed to move backwards...
                if ((keyStates[40] == true) || (keyStates[83] == true))
                {
                    // Get the current ship position.
                    var y_pos = player_ship.GetPos_Y();
                    // Set the position to 5 pixels behind where it is.
                    player_ship.SetPos_Y(y_pos + moveDistance);
                }
            }
            // Else if the rotation is +180 or -180 degrees (player ship is pointing "down")...
            else if ((player_ship.GetRotation() == 180) || (player_ship.GetRotation() == -180))
            {
                // If the ship is supposed to move forward...
                if ((keyStates[38] == true) || (keyStates[87] == true))
                {
                    // Get the current ship position.
                    var y_pos = player_ship.GetPos_Y();
                    // Set the position to 5 pixels ahead of where it is.
                    player_ship.SetPos_Y(y_pos + moveDistance);
                }

                // If the ships is supposed to move backwards...
                if ((keyStates[40] == true) || (keyStates[83] == true))
                {
                    // Get the current ship position.
                    var y_pos = player_ship.GetPos_Y();
                    // Set the position to 5 pixels behind where it is.
                    player_ship.SetPos_Y(y_pos - moveDistance);
                }
            }
            // Else if the player ship is between 0 and -180 degrees...
            else if (player_ship.GetRotation() < 0)
            {
                // Get the current ship position.
                var x_pos = player_ship.GetPos_X();
                var y_pos = player_ship.GetPos_Y();

                // The change in x and y directions.
                var deltaX = 0;
                var deltaY = 0;

                // Get the ship's rotation.
                var ship_angle = player_ship.GetRotation();

                // If the angle is in the second quadrant (top-left)...
                if (ship_angle >= -90)
                {
                    // Convert rotation to radians and calculate the change in 
                    // the x-direction using Soh-Cah-Toa (sin(theta) = opposite/hypotenuse).
                    deltaX = moveDistance * Math.sin(ToRadians(ship_angle));
                    // Convert rotation to radians and calculate the change in 
                    // the y-direction using Soh-Cah-Toa (cos(theta) = adjacent/hypotenuse).
                    deltaY = moveDistance * Math.cos(ToRadians(ship_angle));

                    // Negate the change in y so it "points" in the right direction.
                    deltaY = deltaY * (-1);
                }
                // Else if the angle is in the third quadrant (bottom-left)...
                else if (ship_angle < -90)
                {
                    // Convert rotation to radians and calculate the change in 
                    // the x-direction using Soh-Cah-Toa (cos(theta) = adjacent/hypotenuse).
                    deltaX = moveDistance * Math.cos(ToRadians(ship_angle + 90));
                    // Convert rotation to radians and calculate the change in 
                    // the y-direction using Soh-Cah-Toa (sin(theta) = opposite/hypotenuse).
                    deltaY = moveDistance * Math.sin(ToRadians(ship_angle + 90));

                    // Negate the change in y and x so it "points" in the right direction.
                    deltaX = deltaX * (-1);
                    deltaY = deltaY * (-1);
                }

                // If the ship is supposed to move forward...
                if ((keyStates[38] == true) || (keyStates[87] == true))
                {
                    // Move the ship 5 pixels ahead of where it is.
                    player_ship.SetPos_X(x_pos + deltaX);
                    player_ship.SetPos_Y(y_pos + deltaY);
                }

                // If the ships is supposed to move backwards...
                if ((keyStates[40] == true) || (keyStates[83] == true))
                {
                    // Move the ship 5 pixels behind of where it is.
                    player_ship.SetPos_X(x_pos - deltaX);
                    player_ship.SetPos_Y(y_pos - deltaY);
                }
            }
            // Else if the player ship is between 0 and +180 degrees...
            else if (player_ship.GetRotation() > 0)
            {
                // Get the current ship position.
                var x_pos = player_ship.GetPos_X();
                var y_pos = player_ship.GetPos_Y();

                // The change in x and y directions.
                var deltaX = 0;
                var deltaY = 0;

                // Get the ship's rotation.
                var ship_angle = player_ship.GetRotation();

                // If the angle is in the second quadrant (top-left)...
                if (ship_angle <= 90)
                {
                    // Convert rotation to radians and calculate the change in 
                    // the x-direction using Soh-Cah-Toa (sin(theta) = opposite/hypotenuse).
                    deltaX = moveDistance * Math.sin(ToRadians(ship_angle));
                    // Convert rotation to radians and calculate the change in 
                    // the y-direction using Soh-Cah-Toa (cos(theta) = adjacent/hypotenuse).
                    deltaY = moveDistance * Math.cos(ToRadians(ship_angle));

                    // Negate the change in y so it "points" in the right direction.
                    deltaY = deltaY * (-1);
                }
                // Else if the angle is in the third quadrant (bottom-left)...
                else if (ship_angle > 90)
                {
                    // Convert rotation to radians and calculate the change in 
                    // the x-direction using Soh-Cah-Toa (cos(theta) = adjacent/hypotenuse).
                    deltaX = moveDistance * Math.cos(ToRadians(ship_angle - 90));
                    // Convert rotation to radians and calculate the change in 
                    // the y-direction using Soh-Cah-Toa (sin(theta) = opposite/hypotenuse).
                    deltaY = moveDistance * Math.sin(ToRadians(ship_angle - 90));

                    // Negate the change in y so it "points" in the right direction.
                    //deltaY = deltaY * (-1);
                }

                // If the ship is supposed to move forward...
                if ((keyStates[38] == true) || (keyStates[87] == true))
                {
                    // Move the ship 5 pixels ahead of where it is.
                    player_ship.SetPos_X(x_pos + deltaX);
                    player_ship.SetPos_Y(y_pos + deltaY);
                }
                // If the ships is supposed to move backwards...
                if ((keyStates[40] == true) || (keyStates[83] == true))
                {
                    // Move the ship 5 pixels behind of where it is.
                    player_ship.SetPos_X(x_pos - deltaX);
                    player_ship.SetPos_Y(y_pos - deltaY);
                }
            }
        }
    }

    // Converts the angle to radians.
    function ToRadians(angle)
    {
        return angle * (Math.PI / 180);
    }
    // Converts the angle to degrees.
    function ToDegrees(angle)
    {
        return angle * (180 / Math.PI);
    }

    // Checks to make sure that the player's ship does not leave the window.
    function EnsureNoOutOfBounds()
    {
        // If the player's ship is within 15 pixels of the left side of the screen,
        // set it's x-position to no less than 15 pixels.
        if (player_ship.GetPos_X() < 15)
        {
            player_ship.SetPos_X(15);
        }
        // If the player's ship is within 15 pixels of the right side of the screen,
        // set it's x-position to no more than 15 pixels from the right.
        if (player_ship.GetPos_X() > windowW - 15)
        {
            player_ship.SetPos_X(windowW - 15);
        }
        // If the player's ship is within 15 pixels of the top side of the screen,
        // set it's y-position to no less than 15 pixels.
        if (player_ship.GetPos_Y() < 15)
        {
            player_ship.SetPos_Y(15);
        }
        // If the player's ship is within 15 pixels of the bottom side of the screen,
        // set it's y-position to no more than 15 pixels from the bottom.
        if (player_ship.GetPos_Y() > windowH - 15)
        {
            player_ship.SetPos_Y(windowH - 15);
        }
    }

    // Changes the box shadow of the left, top, right, and bottom divs to red when the ship touches
    // that border, and deactivates the border highlight when the ship moves away from it.
    function EnableBorderMarker()
    {
        // Left border.
        if (player_ship.GetPos_X() === 15 && borderLeft === false)
        {
            borderLeft = true;
            $('#canvas_shadow_container_left').css('box-shadow', '0 0 10px 5px red');
        }
        if (player_ship.GetPos_X() !== 15 && borderLeft === true)
        {
            borderLeft = false;
            $('#canvas_shadow_container_left').css('box-shadow', 'none');
        }

        // Right border.
        if (player_ship.GetPos_X() === (windowW - 15) && borderRight === false)
        {
            borderRight = true;
            $('#canvas_shadow_container_right').css('box-shadow', '0 0 10px 5px red');
        }
        if (player_ship.GetPos_X() !== (windowW - 15) && borderRight === true)
        {
            borderRight = false;
            $('#canvas_shadow_container_right').css('box-shadow', 'none');
        }

        // Top border.
        if (player_ship.GetPos_Y() === 15 && borderTop === false)
        {
            borderTop = true;
            $('#canvas_shadow_container_top').css('box-shadow', '0 0 10px 6px red');
        }
        if (player_ship.GetPos_Y() !== 15 && borderTop === true)
        {
            borderTop = false;
            $('#canvas_shadow_container_top').css('box-shadow', 'none');
        }

        // Bottom border.
        if (player_ship.GetPos_Y() === (windowH - 15) && borderBottom === false)
        {
            borderBottom = true;
            $('#canvas_shadow_container_bottom').css('box-shadow', '0 0 10px 6px red');
        }
        if (player_ship.GetPos_Y() !== (windowH - 15) && borderBottom === true)
        {
            borderBottom = false;
            $('#canvas_shadow_container_bottom').css('box-shadow', 'none');
        }
    }

    // Draw the speed boost bar in the abilities tab. Indicates how much boost is left.
    function DrawAbilitiesBar()
    {
        // Save the abilities bar context.
        abilities_ctx.save();

        // Clear the abilities bar canvas.
        abilities_ctx.clearRect(0, 0, 450, 40);

        // Set the width of the speed bar border to 2px;
        abilities_ctx.lineWidth = "2";
        // Set the border color to white with 50% opacity.
        abilities_ctx.strokeStyle = "rgba(255, 255, 255, 0.5)";
        // Create the speed boost bar border to evenly fill the rest of the container.
        // Does not appear until stroke() or fill() is called.
        abilities_ctx.rect(160 , 13, 270, 16);
        // Stroke the border with the stroke color.
        abilities_ctx.stroke();

        // Calculate the width of the actual speed boost bar using the boost_bar_percent
        // and multiplying it by the space inside the border.
        var boost_bar_width = (boost_bar_percent / 100) * 264;

        // The bar will be full white.
        abilities_ctx.fillStyle = "white";
        // Fill the speed boost bar to the calculated width.
        abilities_ctx.fillRect(163, 16, boost_bar_width, 10);

        // Restore the abilities bar context.
        abilities_ctx.restore();
    }
});

I know I've done some things inefficiently and the game isn't completed (the speed boost bar does not have any functionality at the moment). I would appreciate if anyone could point out or help me figure out why it has started to lag.

Here is a link to the github repository.

\$\endgroup\$
4
\$\begingroup\$

I discovered why it would lag after 10 seconds. After some debugging I figured out it was coming from my DrawAbilitiesBar() function. Apparently it was because I was using

ctx.rect(windowW - 300 , windowH - 37, 270, 16);
ctx.stroke();

instead of

ctx.strokeRect(windowW - 300 , windowH - 37, 270, 16);

Using the second fixed my issue.

\$\endgroup\$
2
\$\begingroup\$

Your container for boost should only be redrawn on resize, if at all:

// Sets up the canvas size and pixel resolution.
function SetupCanvas()
{
    // Set the canvas width, height, and resolution to match the window size.
    canvas.width(windowW);
    canvas.height(windowH);
    canvas[0].width  = windowW;
    canvas[0].height = windowH;

    abilities_canvas[0].width = 450;
    abilities_canvas[0].height = 40;

    // Set the width and height of the window border container
    $('#canvas_inset_shadow_container').width(windowW);
    $('#canvas_inset_shadow_container').height(windowH);
}

And with no transforms taking place save/restore can be safely omitted, and we only have to update the actual boot meter

function DrawAbilitiesBar()
{
    // Clear the abilities bar canvas.
    abilities_ctx.clearRect(163, 16, 264, 10);

    // Calculate the width of the actual speed boost bar using the boost_bar_percent
    // and multiplying it by the space inside the border.
    boost_bar_percent--;
    if(boost_bar_percent == 0){
        boost_bar_percent = 100;
    }
    var boost_bar_width = (boost_bar_percent / 100) * 264;

    // The bar will be full white.
    abilities_ctx.fillStyle = "white";
    // Fill the speed boost bar to the calculated width.
    abilities_ctx.fillRect(163, 16, boost_bar_width, 10);
}

I've submitted a pull request with these changes, if they are agreeable with you.

Good luck with your game

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.