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.
Advertisement:
Read Later
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.
Related Links :
Previous Game
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.
- Move Mario using arrow keys only - left, right, up, and space.
- Pause or resume the game using the escape key when you need a break.
- Jump to reach the next platform and avert a collision with obstacles.
- Collect coins to increase the score by 1000 points for each coin.
- 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.
- If Mario is intercepted by any obstacle or the mushroom, the game is over.
- 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);.
- 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.
- 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.
- 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.
- 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.
- 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.
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;
}
});
}
}