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.
Game Input
//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.
Game Score
//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:
updateScore
Function: This function updates the score continuously by incrementing it by 1 at regular intervals usingsetTimeout
. It ensures that the score increases only if the game is not over (is_game_over
isfalse
), the game is initialized (init
istrue
), and the game has started (game_started
istrue
).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.
Asset Management
//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:
- Retrieve Asset Keys: It retrieves the keys (names) of all assets stored in the
assets
object usingObject.keys(assets)
. - 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. - Iterate Through Assets: It iterates through each asset key using a
for
loop. - Load Image: For each asset, it creates a new
Image
object, sets itssrc
property to the corresponding asset path (assets[key]
), and loads the image asynchronously. - Track Loading Completion: Upon each image’s successful loading (
img.onload
), it decrements theasset_count
variable. If all assets have been loaded (whenasset_count
reaches 0), it sets theall_assets_loaded
flag totrue
, indicating that all assets have been successfully loaded and are ready for use. - Update Asset Object: It replaces the asset path in the
assets
object with the loadedImage
object (img
). This step ensures that the loaded image objects are available for use throughout the game.
Game Obstacles and Collision Detection
//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:
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
andy
) 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
andobstactleHeight
). - 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
.
- It calculates the initial position (
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
andrect2
) as input parameters, each represented by theirx
,y
,width
, andheight
. - 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 returnsfalse
.
- It takes two rectangles (
Play Button
//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:
- Button Initialization: It retrieves the button element from the HTML document using
getElementById
and assigns it to the variablebutton
. - 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
andis_game_over
tofalse
. - 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.
- It clears the
- Handling Play Event: If the game is not currently active (
game_started
isfalse
), it toggles the game state to active by settinggame_started
totrue
andis_game_over
tofalse
. 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. - Handling Game Over Event: If the game is over (
is_game_over
istrue
), 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.
Game Loop
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:
- 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. - Game Initialization: If the game is not initialized (
init
isfalse
), it initializes the game by clearing theobjects
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 setsinit
totrue
. - Drawing Background: It clears the canvas and draws the game background.
- 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.
- 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.
- Platform Movement: It moves the platforms horizontally and handles platform recycling (repositioning platforms when they move off-screen).
- Drawing Score: It draws the current score on the screen.
- Removing Null Objects: It removes any null objects from the
objects
array. - 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.
- 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.