Skip to main content

Simple Game Framework

Let's put everything we’ve learned together to create a classic game on an HTML Canvas. In games, there are usually characters, right? Instead of drawing or uploading lots of images, we can use a grid to make our game art. Each character and part of the game can be made up of tiny squares in a Grid, like pixels in a digital picture. When a picture has lots of tiny pixels close together, it’s clearer or has a higher resolution—that’s what makes our images look sharp!

We already have a tool Open Pixel Art Creator we mentioned before to create the Pixel Art Work. Next we will explain how to use it in the following chapters.

How Canvas Game Works

The first question is: how do we make things move in a game? In well-designed games, there’s something like a heartbeat, just like how our hearts beat to keep us alive. Every beat is a loop: we clear the Canvas (like wiping a chalkboard clean) and then redraw everything in place, including the background and characters.

We can create this heartbeat with setInterval(), which repeats the loop every set amount of time. More advanced games often use window.requestAnimationFrame() for smoother animations, but since it depends on each computer's speed, it might look different on older or slower computers.

main.js
// We keep the intervalId for later if we want to terminate the interval by `clearInterval(intervalId)`
const intervalId = setInterval(() => {
// Clear Canvas completely
ctx.clearRect(0, 0, canvas.width, canvas.height);
// We have a function to draw the grids on Canvas, good for reference
drawGrid(ctx, gameWidth, gameHeight, blockSize);
// Something is drawing
something.update().draw(ctx);
}, 90);

Game Object for Main Logic and Communication

The Game object is already made, so we don’t need to build it from scratch. This lets us jump right into the fun part—creating the game characters! We can use the Game object to handle the basics, so we can focus on making our characters come to life on screen.

main.js
import Game from "game.js";
import Tia from "tia.js";

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

const blockSize = 5;
const gameWidth = canvas.width / blockSize;
const gameHeight = canvas.height / blockSize;

const game = new Game(ctx);
// We put constant values back to Game
game.blockSize = blockSize;
game.width = gameWidth;
game.height = gameHeight;

// The secret is we pass the Game instance to a character
// The character can take Game as a media to talk to other character via Game.
const tia = new Tia(game, { x: 10, y: 20 });
// The Character becomes part of the Game too.
Game.tia = tia;

game.frames = 0;

const intervalId = setInterval(() => {
// ...
}, 90);
// We store the intervalId in Game
game.intervalId = intervalId;
game.js
class Game {
constructor(ctx) {
// Canvas object and game score belong to Game.
this.ctx = ctx;
this.score = 0;
}

over() {
// If game over, we stops the interval loop
clearInterval(this.intervalId);
this.ctx.save();
this.ctx.font = "60px Courier";
this.ctx.fillStyle = "Black";
this.ctx.textAlign = "center";
this.ctx.textBaseline = "middle";
this.ctx.fillText(
"Game Over",
(this.width / 2) * this.blockSize,
(this.height / 2) * this.blockSize
);
this.ctx.restore();
}
}

export default Game;

Images in Grid

Imagine that each image of a character in an animation is made up of tiny squares, like a grid. When you see an animation, these images are actually stored in something called an Image Sprite. An Image Sprite is like a big sheet where each row has images for one type of action, like “walking” or “jumping.” As the character moves, the game quickly switches between images in one row, making it look like it’s moving. If the character changes actions, like turning left, it jumps to another row to show different images.

image.js
class Image {
constructor(images) {
this.images = images;
this.xIndex = 0;
this.yIndex = 0;
}

getWidth() {
return this.images[this.yIndex][0][0].length;
}

getHeight() {
return this.images[this.yIndex][0].length;
}

getImage() {
return this.images[this.yIndex][this.xIndex];
}

prev() {
this.xIndex =
this.xIndex === 0 ? this.images[this.yIndex].length - 1 : this.xIndex - 1;
}

next() {
this.xIndex =
this.xIndex === this.images[this.yIndex].length - 1 ? 0 : this.xIndex + 1;
}

setXIndex(index) {
this.xIndex = index;
}

getXIndex() {
return this.xIndex;
}

setYIndex(index) {
this.yIndex = index;
}

getYIndex() {
return this.yIndex;
}
}

export default Image;

How to use:

// ...
export default [
[up, upOpen],
[right, rightOpen],
[down, downOpen],
[left, leftOpen],
];
import Image from "./image.js";
import imageArr from "./imageArr.js";

const image = new Image(imageArr);
this.width = this.image.getWidth();
this.height = this.image.getHeight();

// We have a helper function to draw the image
drawShape(ctx, blockSize, mage.getImage(), x * blockSize, y * blockSize);

const UP = 0; // Row number represents the state
const RIGHT = 1;
const DOWN = 2;
const LEFT = 3;
//Change to going up state
image.setYIndex(UP);
image.setYIndex(RIGHT);

// Move to the next image on the same row
// It is in circular, the last image will go back to the first image
image.next();

Character - Game Item

We collect all of the common methods into a Shape Object, which we can extend it from to make a character.

shape.js
import Image from "image.js";
import { drawShape } from "utils.js";

class Shape {
constructor(game, imageArr) {
this.game = game;
this.image = new Image(imageArr);
this.width = this.image.getWidth();
this.height = this.image.getHeight();
this.deleted = false;
}

move(dir) {
return this;
}

update() {
return this;
}

draw(ctx) {
drawShape(
ctx,
this.game.blockSize,
this.image.getImage(),
this.x * this.game.blockSize,
this.y * this.game.blockSize
);
}
}

export default Shape;

How to use:

import Shape from "./shape.js";

class Tia extends Shape {
// ...
}

const tia = new Tia(game, {...});

We’ve learned enough about how the Simpple Game Framework works, so now let’s dive into building a simple game for fun! This first game will let us put all our skills together and get some hands-on practice. Let's go step-by-step and see how all the game pieces come together to make something exciting. Ready? Let’s start creating! 🎮