Creating Your First JavaScript Game: A Step-by-Step Tutorial


Welcome to this JavaScript game development tutorial! In this tutorial, we’ll walk through creating a simple game using HTML canvas and JavaScript. By the end, you’ll have built a basic game where a player character jumps over obstacles to score points.

Game development, particularly in JavaScript, offers a wealth of benefits and opportunities. According to recent statistics, the global gaming market is projected to reach $286.61 billion in 2024, indicating significant growth and demand. Therefore, mastering game development will not only give you valuable skills transferable to various industries, but also make it a rewarding and versatile pursuit in today’s digital era.

Prerequisites

Before we get started, make sure you have a basic understanding of HTML, CSS, and JavaScript.

  • Basic Understanding of HTML, CSS, and JavaScript: Familiarity with the fundamental concepts of HTML for structuring web pages, CSS for styling, and JavaScript for adding interactivity is essential. This tutorial assumes a basic knowledge of these languages.
  • Web Browser: Use a modern web browser like Google Chrome, Mozilla Firefox, or Microsoft Edge .

For the purpose of this tutorial, you can download the source files for the game to help you follow along:

Note: As you test the source files, ensure that you load them with in a suitable environment such that all the files are loaded. For offline use, we recommend that you use Live Server and vscode to load the files. Simply open the files in Vscode and run “index.html” using Live Server.

Demo

Here is what we are going to create. This is a simple infinite runner game where the player jumps to dodge obstacles. Press “W” for the player to jump.

Setting Up the Project

Let’s start by setting up the basic structure of our game. Create an html called “index.html”, with a basic html web format, and add some adds for canvas element to draw the game and button to play the game.

Also add some tags for the css in the header (link tag) , and a script tag for our javascript code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="web.css" type="text/css">
    <title>First Javascript Game</title>
</head>
<body>
    <div class="main">

            <canvas width="500px" height="500px" id="canvas"></canvas>
            <span class="actions">
                <button class="button" id="button" onclick="Play();">Play</button>
            </span>


    </div>

    <script type="text/Javascript" src="app.js">
</body>
</html>

Now create a css file called “web.css”, to add some basic css style as shown below.

body{
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: center;  

}
.main{
    display: flex;
    flex-direction: column;
    
    margin-top: 50px;
}
.actions{
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: center;
    margin-top: 10px;
}
.button{
    border-radius: 4px;
    background: rgb(76, 230, 140);
    font-size: 18px;
    font-family: Arial;
    font-weight: bold;
    cursor: pointer;
    padding-inline: 20px;

}
.button:hover{
    background: rgb(159, 239, 145);
}

JavaScript Code

Now, let’s dive into the JavaScript code to create our game. We’ll break down the code into several sections:

1. Setting Up Game Variables and Constants

We start by getting a reference to the canvas element and setting up some variables. Here we also create a variable “objects” used to update the objects or sprites drawn on the game screen as well as a constant for the gravity in the game.

const canvas = document.getElementById("canvas");
const gl=canvas.getContext("2d");
const canvas_width=canvas.width;
const canvas_height=canvas.height;

var objects=[];
const gravity=6;

Lets also add some variables for the player such as the size, height to jump and animations. The playerAnimationCount stores the length of each animation for the player, and the animationFrame is used to track which sprite is shown at each of the animation.

//game variables
var player=null;
const player_width=50;
const player_height=50;
const player_tile_size=[16,16];
const playerAnimationCount={
    idle:6,
    run:8
}
const jumpHeight=150;
var animationFrame=0;

Lets also some settings for the platform where the player stands

//settings for the game platforms
const platform_height=50;
const  platform_width=100;

2. Loading Game Assets

Lets also create an object to store and load our images for the game:

//image assets
const assets={
    background:"bg.jpg",
    platform:"platform.png",
    run:"run.png",
    idle:"idle.png",
    obstacle:"obstacle.png"

}

Finally, lets add more settings to how manage the game.

//refresh rate for the game
const refresh_rate = 100;

This line defines a constant variable refresh_rate which determines the frequency (in milliseconds) at which the game loop will execute. In this case, it’s set to 100 milliseconds, meaning the game will update/render approximately 10 times per second.

//game initiation variables and checks
var all_assets_loaded = false;
var asset_count = 0;
var game_started = false;
var is_game_over = false;
var init = false;
var is_grounded = true;

These lines declare several variables related to the state of the game:

  • all_assets_loaded: Indicates whether all game assets (images) have been loaded.
  • asset_count: Keeps track of the number of assets yet to be loaded.
  • game_started: Tracks whether the game has started.
  • is_game_over: Signals if the game is currently in a “game over” state.
  • init: Represents whether the game has been initialized.
  • is_grounded: Indicates if the player character is currently on the ground.
var score = 0;
var score_timeout = 1000;

These lines initialize variables related to the game’s score:

  • score: Keeps track of the player’s score.
  • score_timeout: Specifies the interval (in milliseconds) at which the score will update. In this case, it’s set to update every second (1000 milliseconds).
//physics time
var dt = 0.25;
var time_lapse = 0;

These lines introduce variables related to physics simulation in the game:

  • dt: Represents the time step for physics calculations. It determines how much time passes in the game world between each physics update.
  • time_lapse: Tracks the elapsed time since the last physics update. It’s used to simulate continuous motion and gravity in the game.

3. Creating Game Objects

Lets create some classes represent our basic game objects. This class will be called Sprite, and will be used to draw and update the player, platforms and obstacles on the screen.

//sprite class for drawing objects
class sprite{
    constructor(image,x,y,width,height,dx=0,dy=0,dwith=null,dheight=null){
        this.image=image;
        this.x=x;
        this.y=y;
        this.width=width;
        this.height=height;
        this.dx=dx;
        this.dy=dy;
        this.dwidth=dwith;
        this.dheight=dheight;
        this.type="sprite";
        objects.push(this);
    }
    reset(image,x,y,width,height,dx=0,dy=0,dwith=null,dheight=nul){
        this.image=image;
        this.x=x;
        this.y=y;
        this.width=width;
        this.height=height;
        this.dx=dx;
        this.dy=dy;
        this.dwidth=dwith;
        this.dheight=dheight;
    }
    bounds(){
        //for the obstacle lets add some offset to the bounds
        if(this.type=="obstacle"){
            var offset=[20,10];
            return {
                x:this.x,
                y:this.y,
                width:this.width-offset[0],
                height:this.height-offset[1]
            };

        }
        return {
            x:this.x,
            y:this.y,
            width:this.width,
            height:this.height
        };
    }
    update(){
       
        if(this.dwidth==null || this.dheight==null){
            gl.drawImage(this.image,this.x,this.y,this.width,this.height);
        
        }
        else{
            gl.drawImage(this.image,this.dx,this.dy,this.dwidth,this.dheight,this.x,this.y,this.width,this.height);
        }
        
    }

}

Here is an explanation for code for the above class:

Constructor: This is the method called when a new instance of the sprite class is created. It takes parameters such as image, x, y, width, height, and optional parameters for drawing offsets (dx, dy) and size (dwith, dheight). These parameters define the initial state of the sprite.

Reset Method: This method allows for resetting the sprite’s properties to new values. It takes parameters similar to the constructor and updates the sprite’s properties accordingly.

Bounds Method: This method calculates and returns the boundaries of the sprite. If the sprite is of type “obstacle,” it applies an offset to the boundaries to account for any adjustments needed.

Update Method: This method is responsible for updating and drawing the sprite on the canvas. It checks if optional size parameters (dwith, dheight) are provided and uses them for drawing if available. Otherwise, it draws the sprite using the default width and height.

4. Implementing Game Mechanics

Lets look at the function and mechanics required to run this game.

//lets add event to capture input from the player to jump
document.addEventListener("keyup",function(event){
 
    //lets the key w 
    if(event.key=="w"){
        jump();
        
    }
})
//function to do player jump
function jump(){
    if(is_grounded==true && init==true){
        player.y-=jumpHeight;
    }
}

The above code listens for a key press event using the addEventListener method. When a key is released, it checks if the key pressed is “w”. If “w” is pressed, it calls the jump function.

The jump function is responsible for making the player character jump. It checks if the player is grounded and if the game is initialized before making the player jump by adjusting its vertical position (y) by a specified jumpHeight. This setup allows players to trigger a jump by pressing the “w” key, provided the game is running and the player is grounded.

//function to update the score
function updateScore(){
    if(is_game_over==false  && init==true && game_started==true){
       
        
        score+=1;
        setTimeout(updateScore,score_timeout);
    }
}
//function to draw the score on the screen
function drawScore(){
    var message="000000"+score;
    gl.font="32px Arial";
    var size=gl.measureText(message);
    gl.fillStyle='#fff'
    gl.fillText(message,canvas_width-size.width,32+10);
}

Here’s what the above code does:

  1. updateScore Function: This function updates the score continuously by incrementing it by 1 at regular intervals using setTimeout. It ensures that the score increases only if the game is not over (is_game_over is false), the game is initialized (init is true), and the game has started (game_started is true).
  2. drawScore Function: This function is responsible for rendering the score on the screen. It formats the score value into a string, pads it with zeros, and then uses the canvas context (gl) to draw the score text at a specific position on the canvas. The font size, color, and position of the score text are configured within this function.
//function load assets
function load(){
    var keys=Object.keys(assets);
    asset_count=keys.length;
    for(i in keys){
        var key=keys[i];
        var src=assets[key];
        var img=new Image();
        img.src=src;
       
        img.onload=function(){
            asset_count-=1;
            
            if(asset_count==0){
                all_assets_loaded=true;
               
            }
        }
        assets[key]=img;
    }
}

In order to efficiently manage our game assets/images, we add the above code.This code defines a function named load responsible for loading assets (images) used in the game. Here’s a breakdown of its functionality:

  1. Retrieve Asset Keys: It retrieves the keys (names) of all assets stored in the assets object using Object.keys(assets).
  2. Initialize Asset Count: It sets the asset_count variable to the number of assets to be loaded, which is the length of the array obtained in the previous step.
  3. Iterate Through Assets: It iterates through each asset key using a for loop.
  4. Load Image: For each asset, it creates a new Image object, sets its src property to the corresponding asset path (assets[key]), and loads the image asynchronously.
  5. Track Loading Completion: Upon each image’s successful loading (img.onload), it decrements the asset_count variable. If all assets have been loaded (when asset_count reaches 0), it sets the all_assets_loaded flag to true, indicating that all assets have been successfully loaded and are ready for use.
  6. Update Asset Object: It replaces the asset path in the assets object with the loaded Image object (img). This step ensures that the loaded image objects are available for use throughout the game.
//lets add some obstactles to the game
function addObstacles(){
    var platform_y=canvas_height-platform_height;
    var offset=50;
    var obstacle=new sprite(assets.obstacle,canvas_width+offset,platform_y-obstactleHeight,obstacleWidth,obstactleHeight);
    //lets ensure that the obstactles are not spawned too close, lets leave a min distance of 5 times the player width
    if(prev_obstacle!=null){
        var separation = obstacle.x-prev_obstacle.x;
        if(separation<5*player_width){
            obstacle.x=prev_obstacle.x+5*player_width;
        }
    }
    
    obstacle.type="obstacle";
    prev_obstacle=obstacle;
    var random_time=Math.random()*(obstactleSpawnTime[1]-obstactleSpawnTime[0])+obstactleSpawnTime[1];
    setTimeout(addObstacles,random_time);
}
//function to check collision of obstacles with player
function checkCollision(rect1,rect2){
    if (rect1.x < rect2.x + rect2.width) {
        if (rect1.x + rect1.width > rect2.x) {
            if (rect1.y < rect2.y + rect2.height) {
                if (rect1.y + rect1.height > rect2.y) {
                    return true;
                }
            }
        }
    }
    return false;
}


The above code block consists of two functions related to spawning obstacle and collision detection:

  1. addObstacles Function: This function adds obstacles to the game at regular intervals. Here’s a breakdown of its functionality:
    • It calculates the initial position (x and y) for the obstacle, typically at the bottom of the canvas (platform_y) with an offset.
    • It creates a new sprite object representing the obstacle, using the provided image (assets.obstacle) and dimensions (obstacleWidth and obstactleHeight).
    • It ensures that obstacles are not spawned too close together by checking the separation distance between the current obstacle and the previous one (prev_obstacle), and adjusting the position if necessary.
    • It sets the type of the obstacle sprite to “obstacle” and schedules the addition of the next obstacle after a random time interval using setTimeout.
  2. checkCollision Function: This function checks for collisions between obstacles and the player (often used for collision detection in simple games). Here’s a breakdown of its functionality:
    • It takes two rectangles (rect1 and rect2) as input parameters, each represented by their x, y, width, and height.
    • It checks if the two rectangles overlap in both the horizontal and vertical axes, indicating a collision.
    • If a collision is detected, it returns true; otherwise, it returns false.
//function to play the game
function Play(){
    var button=document.getElementById("button");
    //lets check for quit event
    if(button.innerHTML=='Quit'){
            objects.splice(0,objects.length);
            score=0;
            game_started=false;
            is_game_over=false;
            button.innerHTML="Play";
            //lets set the focus on the canvas
            canvas.focus();
    }
    else{
    //lets check for play events
    if(game_started==false){
            game_started=true;
            is_game_over=false;
            init=false;
            button.innerHTML="Quit";
            //lets set the focus on the canvas
            canvas.focus();
    }
    else{
        if(is_game_over==true){
             //lets set the game over button
             init=false;
             is_game_over=false;
             button.innerHTML="Quit";
             //lets set the focus on the canvas
             canvas.focus();
        }
    }
}

}

The above code defines the Play function, which handles the game’s play and quit functionality based on user interactions with a button element:

  1. Button Initialization: It retrieves the button element from the HTML document using getElementById and assigns it to the variable button.
  2. Handling Quit Event: If the button’s inner HTML is “Quit,” indicating that the game is currently active, it performs the following actions:
    • It clears the objects array, resetting the game state.
    • It resets the score to zero.
    • It sets game_started and is_game_over to false.
    • It updates the button’s inner HTML to “Play,” signaling the option to start a new game.
    • It sets focus on the canvas element to ensure keyboard events are captured.
  3. Handling Play Event: If the game is not currently active (game_started is false), it toggles the game state to active by setting game_started to true and is_game_over to false. It updates the button’s inner HTML to “Quit” to allow the player to quit the game. Additionally, it sets focus on the canvas element for input interaction.
  4. Handling Game Over Event: If the game is over (is_game_over is true), it resets the game state similar to the quit event. However, it updates the button’s inner HTML to “Quit” to allow the player to quit or start a new game.

Since game is drawn using the canvas element , we need to clear the screen on every frame so that new objects are drawn clearly. We do this by drawing a black rectangle all over the canvas to erase the previous objects, and this is done by the clear function below.

//function to clear the screen
function clear(){
    gl.beginPath();
    //setting the color of the rectangle to black.
    gl.fillStyle="#000";
    gl.fillRect(0,0,canvas_width,canvas_height);
    gl.closePath();
}

Finally, we define the main game loop function (update()), which continuously updates and renders the game state. Inside the loop, we update game objects, render them on the canvas, handle collisions, update the score, and any other UI elements. The loop runs at a specified refresh rate using the inbuilt setTimeout() function.

Below is a summary of update loop outlining the structure and key components of the JavaScript code used to create the game. Each section plays a crucial role in implementing different aspects of the game’s functionality.

// Main game loop
function update() {
    // Update game state
    // Render game objects
    // Check for collisions and handle game logic
    // Update score and other UI elements
    // Repeat the loop
    setTimeout(update, refresh_rate);
}

// Kick off the game loop
load();
update();

Here is a detailed update function used to run the game:

//function to update every frame of the game
function update(){

    
    //check if the game has started
    if(all_assets_loaded==true && game_started==true && is_game_over==false){
        clear()
        
        //lets initialize the game
        if(init==false){
            //lets clear the objects array
            objects.splice(0,objects.length);
            //adding walk platform
            var platform_count=parseInt(canvas_width/platform_width)+5;
            var index=0;
            
            while(index<platform_count){
               
                var platform=new sprite(assets.platform,index*platform_width,canvas_height-platform_height,platform_width,platform_height);
                platform.type="platform";
                index+=1;
            }
            //add player to the game
            var platform_y=canvas_height-platform_height;
            player=new sprite(assets.idle,canvas_width/2-platform_width/2,platform_y-player_height,player_width,player_height,0,0,player_tile_size[0],player_tile_size[1]);
            //lets start adding the obstacles
            var random_time=Math.random()*(obstactleSpawnTime[1]-obstactleSpawnTime[0])+obstactleSpawnTime[0];
            
            setTimeout(addObstacles,random_time);
            
            init=true;
            //lets the score update
            score=0;
            setTimeout(updateScore,score_timeout);
        }
        //draw game background
        gl.drawImage(assets.background,0,0,canvas_width,canvas_height);
        

         //adding gravity to the player
         var platform_y=canvas_height-platform_height;
         if((player.y+50)<platform_y){
            player.y+=gravity*time_lapse;
            is_grounded=false;
            player.reset(assets.idle,player.x,player.y,player_width,player_height,0,0,player_tile_size[0],player_tile_size[1]);
            time_lapse+=dt;
        }
        else{
            time_lapse=0;
            player.y=platform_y-player_height;
            is_grounded=true;
            //lets animation the player
            player.reset(assets.run,player.x,player.y,player_width,player_height,animationFrame*player_tile_size[0],0,player_tile_size[0],player_tile_size[1]);
            animationFrame+=1;
            
            if(animationFrame>=playerAnimationCount.run-1){
                animationFrame=0;
            }
        }
        
        //lets draw the score
        drawScore();
        //update objects on the screen
        for(i in objects){
            var obj=objects[i];
            if(obj==null){
                continue;
            }
            //lets make the obstacles move
            if(obj.type=="obstacle"){
                if(obj.x>0-obstacleWidth){
                    obj.x-=obstactleSpeed;
                }
                else{
                    //remove the obstacle from the update array and replace with a null value
                    alert("null created")
                    objects[i]=null;
                }
                //check if obstacle hit player
                if(checkCollision(player.bounds(),obj.bounds())==true){
                        is_game_over=true;
                        //lets show some game over text
                        var message="Game Over";
                        gl.font="32px Arial";
                        var size=gl.measureText(message)
                        gl.fillStyle="red";
                        gl.strokeStyle="yellow";
                        
                        gl.fillText(message,canvas_width/2-size.width/2,canvas_height/2-32/2);
                        gl.strokeText(message,canvas_width/2-size.width/2,canvas_height/2-32/2);
                        //lets set the game over button
                        var button=document.getElementById("button");
                        button.innerHTML="Play Again"
                  
                        
                }
            }
            //lets move the platforms
            if(obj.type=="platform"){
                obj.x-=obstactleSpeed;
                if(obj.x+platform_width<=0){
                    
                            
                    var platform_count=parseInt(canvas_width/platform_width);
                    obj.x=platform_count*platform_width-obstactleSpeed;
                    
                   
                }
             
              
            }
            obj.update();
        }
        //finally lets remove any null objects from the update
        objects = objects.filter(element => element !== null);
    }

    else{
        if(is_game_over==false){
        clear()
        //draw game background
        gl.drawImage(assets.background,0,0,canvas_width,canvas_height);
       //adding walk platform
       var platform_count=parseInt(canvas_width/platform_width)+5;
       var index=0;
       
       while(index<platform_count){
          
         
           gl.drawImage(assets.platform,index*platform_width,canvas_height-platform_height,platform_width,platform_height);
           index+=1;
       }
       //lets also draw the score
       drawScore();
       //lets idle animate the player
       var platform_y=canvas_height-platform_height;

       gl.drawImage(assets.idle,animationFrame*player_tile_size[0],0,player_tile_size[0],player_tile_size[1],canvas_width/2-platform_width/2,platform_y-player_height,player_width,player_height);
       animationFrame+=1;
       
       if(animationFrame>=playerAnimationCount.idle-1){
           animationFrame=0;
       }
   //lets add game title
   var message="First Javascript Game";
   gl.font="30px Arial";
   var size=gl.measureText(message)
   gl.fillStyle="blue";
   gl.strokeStyle="yellow";
                   
   gl.fillText(message,canvas_width/2-size.width/2,canvas_height/2-32/2);
   gl.strokeText(message,canvas_width/2-size.width/2,canvas_height/2-32/2);
        }
     
        

    }
    


    setTimeout(update,refresh_rate)
}

Here is a summary of the what the update code does:

  1. Checking Game State: It first checks if all assets are loaded (all_assets_loaded), the game has started (game_started), and the game is not over (is_game_over). If these conditions are met, it proceeds with updating the game.
  2. Game Initialization: If the game is not initialized (init is false), it initializes the game by clearing the objects array, adding walk platforms, creating the player sprite, starting to add obstacles at random intervals, and setting up the score update mechanism. After initialization, it sets init to true.
  3. Drawing Background: It clears the canvas and draws the game background.
  4. Player Movement and Animation: It updates the player’s position based on gravity and ground detection. If the player is grounded, it animates the player’s running motion; otherwise, it sets the player to an idle state.
  5. Obstacle Movement and Collision Detection: It updates the position of obstacles and checks for collisions with the player. If a collision occurs, it triggers the game over state.
  6. Platform Movement: It moves the platforms horizontally and handles platform recycling (repositioning platforms when they move off-screen).
  7. Drawing Score: It draws the current score on the screen.
  8. Removing Null Objects: It removes any null objects from the objects array.
  9. Drawing Default Screen: If the game is not yet started or if the game is over, it draws the default game screen, including platforms, player animation, and game title.
  10. Setting Timeout for Next Frame: It schedules the update function to be called again after a specific time interval (refresh_rate) to continue the game loop.

Conclusion

Hopefully, this tutorial has given you a good introduction to JavaScript game development and game development in general. Wish you the best as you learn more and create amazing things.

Leave a Reply

Your email address will not be published. Required fields are marked *