Creating a game using HTML5 Canvas, TypeScript and Webpack 4 (Part 1)
I find that one of the most fun ways to learn or develop your skills with a language is by creating your own game in that language. This is what I did to learn TypeScript and you can find my first TypeScript game here.
There are a number of HTML5 Canvas game frameworks out there such as:
These are all very popular frameworks and have large communities around them. However, you might just want to create a game without relying on any frameworks by directly using HTML5 Canvas APIs and TypeScript. If this is true, please keep reading!
I also find that one of the trickiest parts of getting started with your new project is finding the right project skeleton. So, here is my attempt at walking through creating your first game using HTML5 Canvas, TypeScript and Webpack 4.
You can either download my starter-kit at this repository: typescript-webpack-game-starter, or you can follow this article and create all the files/config yourself.
Setting up
We're going to be using the following directory structure for our project:
.
├── /src
| └── /css
| ├── /js
| ├── /assets
| ├── /images
| ├── /audio
| index.html
└── webpack.config.js
└── tsconfig.json
└── package.json
Go ahead and create all the directories, run npm init
to get package.json and initialise your git project: git init
.
HTML & CSS
We're just being to be using a very basic HTML file. All we really need to do is include the canvas
tag within our HTML. Place the following into src/index.html
:
<!DOCTYPE html>
<html lang="en">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="TypeScript Game">
<meta name=”description” content="A simple TypeScript game.">
<body>
<canvas id="canvas"></canvas>
</body>
</html>
We're also going to create a simple css file (don't worry about referencing it in the index.html
file yet as Webpack will handle this. Put the following into src/css/main.css
:
html, body {
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
margin: 0px;
padding: 0px;
}
canvas {
-webkit-tap-highlight-color: transparent;
}
This makes the game full-screen and disables an overlay that appears when the game is played on mobile (if you're targeting mobile as well).
TypeScript
To use TypeScript, we need to first install it, run the following: npm install --save typescript
.
We will also need to setup our tsconfig.json
file. Add the following to the file:
{
"compilerOptions": {
"outDir": "./dist",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"allowJs": true
},
"exclude": [
"node_modules"
],
"files": [
"./src/js/app.ts"
]
}
The above is just a very basic tsconfig.json
setup I grabbed from the TypeScript website. Feel free to make any changes to this file.
Now, let's create the TypeScript file that will bootstrap our game by loading the CSS and continually re-render/re-paint our game. In the src/js
directory, create a file called app.ts
and in that file, add the following:
declare var require: any;
require('../css/main.css');
import Game from './game';
class App {
private _game: Game;
constructor(game: Game) {
this._game = game;
}
public setup(): void {
// Any setup that is required that only runs once before game loads goes here
this.gameLoop();
}
private gameLoop(): void {
// need to bind the current this reference to the callback
requestAnimationFrame(this.gameLoop.bind(this));
this._game.render();
}
}
window.onload = () => {
let app = new App(new Game());
app.setup();
}
We are utilising the window.onload to tell us when the browser has finished loading the DOM, scripts and styles of our app. Once loading is complete, it'll create a new instance of our App
who's job it is to run a setup()
method once, followed by an infinite loop of the gameLoop()
method. You probably noticed we're importing the Game
class from a file that does not yet exist. We will be creating the Game
class shortly.
The requestAnimationFrame()
method which is run in gameLoop()
method is how we animate our HTML5 Canvas. From the Mozilla docs:
The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.
As we've passed the gameLoop()
method as the callback, our app will continually keep re-painting itself with the latest animations.
Ok great, now let's create the game.ts
file which will act as the game instance and load our Canvas!
In game.ts
, add the following:
export default class Game {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private height: number = window.innerHeight;
private width: number = window.innerWidth;
constructor() {
this.canvas = <HTMLCanvasElement>document.getElementById('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
this.ctx = this.canvas.getContext("2d");
}
public render(): void {
console.log('rendering');
}
}
Here we are initialising the Canvas object and binding it to the <canvas>
tag in our html
file. We are also adjusting the game to be fullscreen and obtaining a reference to the context of the newly created Canvas (so we can actually draw things on it).
If you remember in App.ts
, the render()
method is being called every frame. This is where we will have all the drawing of our objects (will be covered in the next article).
Webpack
First we need to install Webpack: npm install --save-dev webpack webpack-cli
.
We also need a few Webpack loaders/plugins: npm install --save-dev ts-loader style-loader css-loader file-loader html-webpack-plugin
.
If you want a live development server with auto-reloading (highly recommended), install webpack-dev-server: npm install --save-dev webpack-dev-server
.
In our webpack.config.js
file, add the following:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/app.ts',
output: {
path: "dist",
filename: "bundle.js",
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
devServer: {
contentBase: './dist'
},
module: {
rules: [
{
test: /\.ts?$/,
exclude: /node_modules/,
use: [
{ loader: "ts-loader" }
]
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
"style-loader",
"css-loader"
]
},
{
test: /\.mp3$/,
exclude: /node_modules/,
use: [
{ loader: 'file-loader' }
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
}
To run our game, we will need to add some new scripts to our package.json
. Under the "scripts"
section, add: "dev": "webpack-dev-server --open" and "prod": "webpack --production"
.
We need to do this because we didn't install the npm packages globally (feel free to, though), to make sure we use the correct modules within our node_modules
of our project, we have to add it to the npm scripts.
Now run npm run dev
and it should open up a new web page with our blank app! Check the console output to see there's no errors and hopefully a whole bunch of "rendering"
console outputs.
Conclusion
We have setup the very barebones structure of our project - nothing will actually display on the screen yet. Stay tuned for the next article in which we add click and keyboard inputs which will allow users to interact with rendered objects on the canvas - creating a very basic game.