Sitemap / Advertise

Introduction

This particular game is a rudimentary platform game that only includes the basic mechanics of its genre without any complex and perplexing graphics and animations.


Tags

Share

JavaScript Game: Rudimentary Platform with Mario

Advertisement:


read_later

Read Later



read_later

Read Later

Introduction

This particular game is a rudimentary platform game that only includes the basic mechanics of its genre without any complex and perplexing graphics and animations.

Tags

Share





Advertisement

Advertisement


Banggood
Banggood



Introduction

I am a novice in developing and designing a game using JavaScript. In this project series named JavaScript Game, I share games I develop with their tutorials from basic to advanced. This particular game is a rudimentary platform game that only includes the basic mechanics of its genre without any complex and perplexing graphics and animations. In that regard, I share this tutorial for the mentioned game as a starting point in developing an indulging platform game for novices. And, of course, I chose the protagonist of my basic platform game as the most-experienced and spectacular platform veteran - Mario :)

It is also a competitive game: check out your newest score on the game over screen.

Click 'Preview' to Play.

For more information about the code files, please visit the Source Code section below.

Step 1: Features

- Move Mario using arrow keys only - left, right, up, and space.

project-image
Figure - 38.1

- Pause or resume the game using the escape key when you need a break.

project-image
Figure - 38.2

- Jump to reach the next platform and avert a collision with obstacles.

project-image
Figure - 38.3

- Collect coins to increase the score by 1000 points for each coin.

project-image
Figure - 38.4

- There are two levels in this game. In Level 1, you get only three obstacles (enemies) at the same frame. However, in Level 2, ten obstacles (enemies) attacks towards Mario if your score is beyond 20000.

project-image
Figure - 38.5

- If Mario is intercepted by any obstacle or the mushroom, the game is over.

project-image
Figure - 38.6

Step 2: Index

- Import the Game class.

- Get the canvas element.

- Create the 2d context to draw.

- Define the game width and the game height.

- Define the game object.

- Create the loop function to update and draw the game using requestAnimationFrame(loop);.

project-image
Figure - 38.7

Step 3: Game

- Import Mario, InputHandler, Obstacles, and Collision classes.

- Define game states.

- Create and define game objects. And, attach the class itself to the created game objects.

- Update each game object if the game state is not equal to paused or gameover.

- Print the game score and the current game level.

- Proceed after drawing each object unless there is not a change in the game state.

- Define pause() and gameOver() functions.

project-image
Figure - 38.8


project-image
Figure - 38.9

Step 4: Mario

- Get combined information from the game class

- Define the properties of Mario - width, height, speed, accelerate, position, jump, score, and LEVEL.

- Define the animation frames from a Mario sprite sheet using source positions. I split five different frames as movements from the sprite sheet - left, right, jump, stop, and coin.

- Define the current movement as the initial movement of Mario.

- Define movement functions and related animation frames to animate Mario.

- Use the jump boolean to avoid repetitive jumps.

- Draw the background and Mario with the current movement.

- Update the position of Mario and increase the score incrementally.

- Activate Level 2 if the score is greater than 20000.

project-image
Figure - 38.10


project-image
Figure - 38.11

Step 5: Obstacles

- Get combined information from the game class

- Define the properties of obstacles - limit, distance, and obstacle elements.

- Define coin positions and sizes.

- Define the properties of the mushroom - speed, width, x, and y.

- Define the properties of walls - width, height, top, right, left, and bottom.

- Using the line variable, define the optimum length for each wall.

- Create bricks by using the distance variable and the pre-defined wall properties. Add each brick into the bricks object using the push() function.

- Draw bricks, coins, obstacles, and the mushroom.

- Update the obstacles object by adding random obstacles using Math.random() function if the number of obstacles in the current frame is under the limit.

- Remove obstacles if they are out of the screen using splice() and indexOf() functions.

- Create the mushroom loop on the left wall.

project-image
Figure - 38.12


project-image
Figure - 38.13


project-image
Figure - 38.14

Step 6: Collision

- Get combined information from the game class

- In detect_obstacles_collision(obstacles, mario) function, detect whether any obstacle intercepts Mario or not. If so, activate the game over function and disable the jump function.

- In detect_mushroom_collision(mushroom, mario) function, detect whether Mario hits the mushroom or not. If so, activate the game over function and disable the jump function.

- In detect_wall_collision(bricks, mario) function, if Mario reaches to a higher platform, change the position of Mario depending on the brick Mario steps.

- In detect_coin_collision(coins, mario) function, if Mario collects a coin, increase the score by 1000 and remove that coin by using splice() and indexOf() functions.

- Update the mentioned functions for each obstacle, brick, coin, and mushroom.

project-image
Figure - 38.15


project-image
Figure - 38.16

Step 7: InputHandler

- Get objects and variables from the Game class.

- If the right arrow is pressed, execute the moveRight() function.

- If the left arrow is pressed, execute the moveLeft() function.

- If the up arrow key or the space key is pressed, execute the jump() function.

- To avoid the page navigation while pressing arrows, use event.preventDefault();

- If the escape key is pressed, pause or resume the game depending on the previous state.

- If the Enter key is pressed, restart the game in the GAMEOVER state.

- On keyup, stop Mario by executing the stop() function.

project-image
Figure - 38.17

Source Code

Please sign in to download source code files and the game, including index.css, index.html, and assets, in a zipped folder to play and try the game on your computer.

Zip Folder

Download


Index

Download



import Game from './game.js';

let canvas = document.getElementById("game");
let ctx = canvas.getContext("2d");

const GAME_WIDTH = 800;
const GAME_HEIGHT = 600;

let game = new Game(GAME_WIDTH, GAME_HEIGHT);

function loop(timestamp){
	// Define if needed.
	let deltaTime;

	ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
    game.update(deltaTime);
	game.draw(ctx);
	
	window.requestAnimationFrame(loop);
}

window.requestAnimationFrame(loop);


Game

Download



import Mario from './mario.js';
import InputHandler from './inputhandler.js';
import Obstacles from './obstacles.js';
import Collision from './collision.js';

export default class Game{
	constructor(gameWidth, gameHeight){
		this.gameWidth = gameWidth;
		this.gameHeight = gameHeight;
		
		this.STATE = {
			PAUSED: 0,
			RUNNING: 1,
			GAMEOVER: 2
		};
		
	    this.mario = new Mario(this);
        this.obstacles = new Obstacles(this);
		this.collision = new Collision(this);
		new InputHandler(this);
		this.objects = [this.mario, this.obstacles, this.collision];

	}
	
	update(deltaTime){
		if(this.gameState == this.STATE.PAUSED || this.gameState == this.STATE.GAMEOVER) return;
		this.objects.forEach((object) => object.update(deltaTime));
	}
	
	draw(ctx){
		this.objects.forEach((object) => object.draw(ctx));
		
		ctx.font = "bold 20px Times";
		ctx.fillStyle = "#eb2e00";
		ctx.fillText("SCORE: " + this.mario.properties.score, 10, 25);
		ctx.fillText("LEVEL: " + this.mario.properties.LEVEL, this.gameWidth - 100, 25);
		
		if(this.gameState == this.STATE.PAUSED){
			ctx.fillStyle = "rgba(78, 88, 81, 0.6)";
			ctx.fillRect(0, 0, this.gameWidth, this.gameHeight);
			ctx.font = "bold 30px Times";
			ctx.fillStyle = "white";
			ctx.textAlign = "center";
			ctx.fillText("PAUSED", this.gameWidth / 2, this.gameHeight / 3);
			ctx.textAlign = "left";
		}
		
		if(this.gameState == this.STATE.GAMEOVER){
			ctx.fillStyle = "rgba(78, 88, 81, 0.6)";
			ctx.fillRect(0, 0, this.gameWidth, this.gameHeight);
			ctx.font = "bold 30px Times";
			ctx.fillStyle = "white";
			ctx.textAlign = "center";
			ctx.fillText("GAME OVER !!!", this.gameWidth / 2, this.gameHeight / 2);
			ctx.textAlign = "left";
		}

	}
	
	pause(){
		if(this.gameState == this.STATE.PAUSED){
			this.gameState = this.STATE.RUNNING
		}else{
			this.gameState = this.STATE.PAUSED;
		}
	}
	
	gameOver(){
		this.gameState = this.STATE.GAMEOVER;
	}
}


Mario

Download



export default class Mario{
	constructor(game){
		this.game = game;
		
		this.gameWidth = game.gameWidth;
		this.gameHeight = game.gameHeight;
		
		this.properties = {
			width: 35,
			height: 60,
			speed: {x: 0, y: 5},
			accelerate: 10,
			position: {x : 0, y: this.gameHeight - 10},
			jump: true,
			score: 0,
			LEVEL: 1
		};
		
		this.walking_animation = {
			left: {sx: 0, sy: 110, Sx: 70, Sy: 65},
			right: {sx: 210, sy: 420, Sx: 70, Sy: 65},
			jump: {sx: 90, sy: 500, Sx: 70, Sy: 65},
			stop: {sx: 220, sy: 190, Sx: 70, Sy: 65},
			coin: {sx: 20, sy: 500, Sx: 70, Sy: 65}

		};
		
		this.currentMove = this.walking_animation.stop;
		
	}
	
	moveRight(){
		this.properties.speed.x = -this.properties.accelerate;
		this.currentMove = this.walking_animation.right;
	}
	
	moveLeft(){
		this.properties.speed.x = this.properties.accelerate;
		this.currentMove = this.walking_animation.left;
	}
	
	jump(){
		if(this.properties.jump) this.properties.position.y += -150;
		this.properties.jump = false;
		this.currentMove = this.walking_animation.jump;
	}
	
	stop(){
		this.properties.speed.x = 0;
		this.currentMove = this.walking_animation.stop;
	}
	
	draw(ctx){
		ctx.drawImage(document.getElementById("background"), 0, 0, this.gameWidth, this.gameHeight);

		if(this.game.gameState != this.game.STATE.GAMEOVER) ctx.drawImage(document.getElementById("mario"), this.currentMove.sx, this.currentMove.sy, this.currentMove.Sx, this.currentMove.Sy, this.properties.position.x, this.properties.position.y, this.properties.width, this.properties.height);

	}
	
	update(deltaTime){
		this.properties.position.x += this.properties.speed.x;
		this.properties.position.y += this.properties.speed.y;
		
	    if(this.properties.position.x > this.gameWidth - this.properties.width) this.properties.position.x = this.gameWidth - this.properties.width;
		if(this.properties.position.x < 0) this.properties.position.x = 0;
	    if(this.properties.position.y > this.gameHeight - this.properties.height){ this.properties.position.y = this.gameHeight - this.properties.height; this.properties.jump = true;}
	    if(this.properties.position.y < 0) this.properties.position.y = 0;

        if(this.game.gameState != this.game.STATE.PAUSED) this.properties.score++;
		if(this.properties.score > 20000){ this.game.obstacles.properties.limit = 10; this.properties.LEVEL = 2;} 

	}
}


Obstacles

Download



export default class Obstacles{
	constructor(game){
		this.game = game;
		
		this.gameWidth = game.gameWidth;
		this.gameHeight = game.gameHeight;
		
		this.properties = {
			limit: 3,
			distance: 0,
			elem: [document.getElementById("obstacle_1"), document.getElementById("obstacle_2")]
		};
		
		this.coins = [
		    {x: 40, y: 40, width: 20, height: 20},
		    {x: 60, y: 40, width: 20, height: 20},
		    {x: 80, y: 40, width: 20, height: 20},
		    {x: 100, y: 40, width: 20, height: 20},
		    {x: 120, y: 40, width: 20, height: 20},
		    {x: 140, y: 40, width: 20, height: 20},
		    {x: 160, y: 40, width: 20, height: 20},
		    {x: 180, y: 40, width: 20, height: 20},
		    {x: 200, y: 40, width: 20, height: 20},
		    {x: 220, y: 40, width: 20, height: 20},
		    {x: 240, y: 40, width: 20, height: 20},
		    {x: 260, y: 40, width: 20, height: 20},
		    {x: 280, y: 40, width: 20, height: 20},
		    {x: 300, y: 40, width: 20, height: 20},
		    {x: 400, y: 160, width: 20, height: 20},
		    {x: 420, y: 160, width: 20, height: 20},
		    {x: 440, y: 160, width: 20, height: 20},
		    {x: 460, y: 160, width: 20, height: 20},
		    {x: 480, y: 160, width: 20, height: 20},
		    {x: 500, y: 160, width: 20, height: 20}
		];
		
		this.obstacles = [];
		
		this.mushroom = {
			speed: 0,
			width: 30,
			height: 30,
			x: 320,
			y: 180 - 30
		};
		
		this.wall = {
			width: 40,
			height: 40,
			top: {line: [1, 1, 1, 1, 1, 1, 1], y: 60},
			right: {line: [1, 1, 1, 1], y: 300},
			left: {line: [1, 1, 1, 1, 1, 1, 1], y: 180},
			bottom: {line: [1, 1, 1], y: 490}
			
		};
		
		this.bricks = [];
		
		let distance = 0;
		
		this.wall.top.line.forEach((object) => {
			distance+=40;
			this.bricks.push({x: distance, y: this.wall.top.y, width: this.wall.width, height: this.wall.height});
		});
		this.wall.left.line.forEach((object) => {
			distance+=40;
			this.bricks.push({x: distance, y: this.wall.left.y, width: this.wall.width, height: this.wall.height});
		});
		this.wall.right.line.forEach((object) => {
			distance+=40;
			this.bricks.push({x: distance, y: this.wall.right.y, width: this.wall.width, height: this.wall.height});
		});
		distance = 410;
		this.wall.bottom.line.forEach((object) => {
			distance+=40;
			this.bricks.push({x: distance, y: this.wall.bottom.y, width: this.wall.width, height: this.wall.height});
		});
	
	}
	
	draw(ctx){
		this.obstacles.forEach((object) => {
			ctx.drawImage(object.image, object.x, object.y, object.width, object.height);
		});
		
		this.bricks.forEach((object) => {
			ctx.drawImage(document.getElementById("wall"), object.x, object.y, object.width, object.height);
		});
		
		this.coins.forEach((object) => {
			ctx.drawImage(document.getElementById("coin"), object.x, object.y, object.width, object.height);
		});
		
		ctx.drawImage(document.getElementById("mushroom"), this.mushroom.x, this.mushroom.y, this.mushroom.width, this.mushroom.height);
	}
	
	update(deltaTime){
		if(this.properties.distance > 800){this.properties.distance = 50;}else{this.properties.distance += 100;}
		if(this.obstacles.length < this.properties.limit) this.obstacles.push({image: this.properties.elem[Math.floor(Math.random() * 2)], x: this.gameWidth + this.properties.distance, y: this.gameHeight - 60, width: 60, height: 60});
		
		this.obstacles.forEach((object) => {
			object.x += -5;
			if(object.x < 0) this.obstacles.splice(this.obstacles.indexOf(object), 1);
		});
		
	    if(this.mushroom.x == ((this.wall.top.line.length + this.wall.left.line.length) * 40) - 30 + 40) this.mushroom.speed = -5;
	    if(this.mushroom.x == this.wall.top.line.length * 40 + 40) this.mushroom.speed = 5;
		this.mushroom.x += this.mushroom.speed;

	}

}


Collision

Download



export default class Collision{
	constructor(game){
		// GET ALL INFO FROM THE GAME CLASS
		this.game = game;
	}
	
	detect_obstacles_collision(obstacles, mario){
		let bottomOfObstacles = obstacles.y + obstacles.height;
        let leftOfObstacles = obstacles.x;
        let rightOfObstacles = obstacles.x + obstacles.width;
        let topOfObstacles = obstacles.y;
        
        let bottomOfMario = mario.position.y + mario.height;
        let leftOfMario = mario.position.x;		
        let rightOfMario = mario.position.x + mario.width;	
        let topOfMario = mario.position.y;
		
		if(leftOfMario < rightOfObstacles && rightOfMario > leftOfObstacles && topOfMario < bottomOfObstacles && bottomOfMario > topOfObstacles){
			this.game.gameOver();
			mario.jump = false;
		}
	}
	
	detect_mushroom_collision(mushroom, mario){
		let bottomOfMushroom = mushroom.y + mushroom.height;
        let leftOfMushroom = mushroom.x;
        let rightOfMushroom = mushroom.x + mushroom.width;
        let topOfMushroom = mushroom.y;
        
        let bottomOfMario = mario.position.y + mario.height;
        let leftOfMario = mario.position.x;		
        let rightOfMario = mario.position.x + mario.width;	
        let topOfMario = mario.position.y;
		
		if(leftOfMario < rightOfMushroom && rightOfMario > leftOfMushroom && topOfMario < bottomOfMushroom && bottomOfMario > topOfMushroom){
			this.game.gameOver();
			mario.jump = false;
		}
	}
	
	detect_wall_collision(bricks, mario){
		let bottomOfBrick = bricks.y + bricks.height;
        let leftOfBrick = bricks.x;
        let rightOfBrick = bricks.x + bricks.width;
        let topOfBrick = bricks.y;
        
        let bottomOfMario = mario.position.y + mario.height;
        let leftOfMario = mario.position.x;		
        let rightOfMario = mario.position.x + mario.width;	
        let topOfMario = mario.position.y;
		
		if(leftOfMario < rightOfBrick && rightOfMario > leftOfBrick && topOfMario < bottomOfBrick && bottomOfMario > topOfBrick){
			mario.position.y = topOfBrick - mario.height;
			mario.jump = true;
		}
	}
	
	detect_coin_collision(coins, mario){
		let bottomOfCoin = coins.y + coins.height;
        let leftOfCoin = coins.x;
        let rightOfCoin = coins.x + coins.width;
        let topOfCoin = coins.y;
        
        let bottomOfMario = mario.position.y + mario.height;
        let leftOfMario = mario.position.x;		
        let rightOfMario = mario.position.x + mario.width;	
        let topOfMario = mario.position.y;
		
		if(leftOfMario < rightOfCoin && rightOfMario > leftOfCoin && topOfMario < bottomOfCoin && bottomOfMario > topOfCoin){
			this.game.mario.currentMove = this.game.mario.walking_animation.coin;
            mario.score += 1000;
			this.game.obstacles.coins.splice(this.game.obstacles.coins.indexOf(coins), 1);
		}
	}
	
	draw(ctx){
		
	}
	
	update(deltaTime){
		this.game.obstacles.obstacles.forEach((object) => {
			this.detect_obstacles_collision(object, this.game.mario.properties);
		});
		
		this.game.obstacles.bricks.forEach((object) => {
			this.detect_wall_collision(object, this.game.mario.properties);
		});
		
		this.game.obstacles.coins.forEach((object) => {
			this.detect_coin_collision(object, this.game.mario.properties);
		});
		
		this.detect_mushroom_collision(this.game.obstacles.mushroom, this.game.mario.properties);
	
	}

}


InputHandler

Download



export default class InputHandler{
	constructor(game){
	    // GET ALL INFO FROM THE GAME CLASS
		this.game = game;
		
		document.addEventListener("keydown", (event) => {
			let key = event.keyCode || event.which;
			switch(key){
				case 37:
				this.game.mario.moveRight();
				break;
				case 38:
				event.preventDefault(); if(this.game.gameState != this.game.STATE.PAUSED) this.game.mario.jump();
				break;
				case 39:
				this.game.mario.moveLeft();
				break;
				case 32:
				event.preventDefault(); if(this.game.gameState != this.game.STATE.PAUSED) this.game.mario.jump();
				break;
				case 27:
				if(this.game.gameState != this.game.STATE.GAMEOVER) this.game.pause();
				break;
				case 13:
				if(this.game.gameState == this.game.STATE.GAMEOVER) location.reload();
				break;
				
			}
		});
		
		document.addEventListener("keyup", (event) => {
			let key = event.keyCode || event.which;
			switch(key){
				case 37:
				this.game.mario.stop();
				break;
				case 38:
				event.preventDefault(); this.game.mario.stop();
				break;
				case 39:
				this.game.mario.stop();
				break;
				case 32:
				event.preventDefault(); this.game.mario.stop();
				break;
			}
		});
	}
}