All About HTML5 Game Development, from Publishing to Monetization

Create a HTML5 game with ExcaliburJS: installation and set up - 1/11

Create a HTML5 game with ExcaliburJS: installation and set up - 1/11

This is an open-source project on game development with a focus on the HTML5 Excalibur game engine. Check out this feature implementation on this commit in our Github repository!

You can read here the list of features we are going to develop for our Excalibur game, such as data persistence between scenes and between game sessions If you're interested in game development, follow us on Twitter and Instagram to be caught up on our latest news!

Table of Contents

In this tutorial

At the end of this tutorial, we will have an npm project with:

  • Excalibur as a game engine, with a customized resources loader and an empty scene
  • Webpack as module bundler
  • Typescript as a programming language

 

Add Excalibur

After the Typescript and Webpack configuration done in the previous tutorial, we are finally ready to set up Excalibur!

First, add it to the project by running


npm i excalibur
        

You should see the new dependency in package.json file.

A glance on code architecture

Our project structure looks like


sword-adventure
    /src
        /index.ts
    /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    /webpack.development.js
    /webpack.production.js
        

where index.ts is the compilation entry point.

While developing our game, we will stick as much as we can to good coding practices. One of our favorites is Single Responsibility Principle.

Keeping in mind the Single Responsibility Principle, since index.ts is the entry point for all that follows, its main and only responsibility will be to set up what we need to start and play the game. We are not going to write Game class related code directly inside it. We will instead create a wrapper class that will expose the Engine methods. Why a wrapper class? Because in future development steps we are going to add transitions to our game, and the wrapper class will give us some benefits for doing so.

Code in action!

The Game class

To begin with, let’s create a Game class which extends the Excalibur Engine class:


export class Game extends Engine {

}
    

Create this class next to the index.ts file so that our project structure now looks as


sword-adventure
    /src
        /index.ts
        /Game.ts
    /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    /webpack.development.js
    /webpack.production.js
        

We want the Game class to keep a reference to the Excalibur game instance. In Excalibur, there can be just one game at a time, so it can seem that the structure that we are going to build now is too much. But at Facondia Games we already know (as we have already written a couple of games with Excalibur) that we are going to need some extra infrastructure to obtain certain effects, for example, transitions between scenes. Let’s create then this wrapper class for future benefit.


export class Game extends Engine {

    private static game: Game;

    public static getInstance(): Game {
        if (this.game === undefined || this.game === null) {
            const engineOptionFactory: EngineOptionsFactory = Container.get(EngineOptionsFactory);
            const options: EngineOptions = engineOptionFactory.buildOptions();
            this.game = new Game(options);
        }
        return this.game;
    }

    private constructor(options: EngineOptions) {
        super(options);
    }
}
    
In this code snippet, as you can see:
  • The Game class keeps a reference to a static private game object
  • The private constructor ensure that no other class can instantiate a Game class except for the Game class itself
  • If the game is not defined, it is created the first time it’s asked for through the method getInstance()

You may have noticed that there is a service called EngineOptionsFactory. This is the implementation of another of our favorite patterns of which we'll make extensive use while developing the game: Dependency injection. Here we are using TypeDI library, which you can install, together with required reflect-metadata package, by running


npm install typedi reflect-metadata
        

The reflect-metadata package is needed to use decorations in Typescript (e.g. @Service is an annotation).

In our experience, dependency injection helps keep code in order and is a great help when writing tests.

Back to the code, let's have a look at the EngineOptionsFactory class.


@Service()
export class EngineOptionsFactory {

    /** I need DisplayMode.FitContainer to deploy inside Joomla. When deploying without this constraint, use FillScreen instead */
    public buildOptions(): EngineOptions {
        const {width, height} = generalGameConfig.gameSize;
        return {
            displayMode: DisplayMode.FitContainer,
            width,
            height,
            canvasElementId: uiConfig.gameCanvaId,
            pointerScope: Input.PointerScope.Canvas,
        };
    }
}
       

As you can see from the imports, there are another couple of new classes (DOMService and uiConfig). We'll talk about them in a minute.

The EngineOptionsFactory class is placed under /src/services, so our project structure now looks as


sword-adventure
    /src
        /index.ts
        /Game.ts
        /services
            /EngineOptionsFactory.ts
    /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    /webpack.development.js
    /webpack.production.js
        

Why have an entire class just to return some configuration that can be put in the Game.ts class? We’ve taken this decision following the Single Responsibility Principle: every class has its own responsibility. The Game class will expose methods regarding the Excalibur Engine base class. The purpose of our service is instead to build the options to create the game. This service as far as today does not have much intelligence in it. But think about what could be in the future:


@Service()
export class EngineOptionsFactory {

    private deviceService = Container.get(DeviceService);

    public buildOptions(): EngineOptions {
        const {width, height} = this.getGameSize()
        return {
            displayMode: DisplayMode.FitContainer,
            width,
            height,
            canvasElementId: uiConfig.gameCanvaId,
            pointerScope: Input.PointerScope.Canvas,
        };
    }

    private getGameSize() {
        if (this.deviceService.onAndroid()) {
            return generalGameConfig.gameSize.android;
        }
        return generalGameConfig.gameSize.notebook;
    }

}
            

In this example, we use a hypothetical DeviceService which knows if the game is running on an Android device. Depending on the answer, different game sizes are given. This is a simple example, but in real use cases the logic can quickly become complex, and the Game the class would end up containing a lot of code both about the game and about devices. This is just one example of many that can be done, to show how it’s better to start right from the beginning to divide the code as much as possible into classes that have a single responsibility. This practice also gives some advantages when writing unitary tests.

Let’s go ahead now with our Game class. We need to make the game start! Following Excalibur docs and their examples, we know that we need to do a couple of things:

  • Load the resources
  • Registering the scenes

Let’s start with the resources.

Resources loading

Every game needs different kinds of assets to work: pictures and sounds are the two main examples. It's then necessary to load them before they're used. In this section, we deal with resource loading in Excalibur.

We already know that in the future we’ll want to customize the Excalibur default loader. We would like to do two things:

  • Use a custom play button
  • Display a percentage of the loading
We will display a percentage but you can also use an infinite loader or a simple text, as you prefer.
In Excalibur, it's possible to suppress the Play Button. But the game is played in a browser or a WebView and not having it gives some problems with audio management. Therefore we are going to keep it, customizing it as we want.

Go back to the Game class and add a wrapper method to Excalibur start(), which we'll call startCustomLoader() method:



public startCustomLoader() {
    const loader: Loader = new Loader();
    this.logLoadingProgress(loader);
    return Game.getInstance().start(loader);
}

private logLoadingProgress(loader: Loader, timeout = 2000, interval = 100) {
    const progressLoggerElement: HTMLElement = this.domService.getElement('loader-progress');
    const poller = setInterval(() => {
        const progressPercent: number = Math.trunc(loader.progress * 100);
        progressLoggerElement.textContent = progressPercent + '%';
        if (loader.isLoaded()) {
            clearInterval(poller);
        }
    }, interval);
    setTimeout(() => clearInterval(poller), timeout);
}
            

The Loader, for now, is empty, since at the moment we don’t have any resources to load in our game. Concerning the default Excalibur loader, we are logging here the loading percentage. Let’s see how it is accomplished.

When we set up index.html template file in the first tutorial, we left some space for a div element with id loader-progress. That's the element we are using right now to display the loading percentage. The DomService which first appeared in the EngineOptionsFactory class is now retrieving the div to display the loading percentage. Why are we using a Service instead of calling directly the document.getElementById() method?

From our experience, using directly DOM APIs result in slow code (really slow code). We need a way to speed things up, and that's exactly the purpose of the DOMService. If we were developing a Web App we would use a framework such as Angular or React (spoiler alert: in the near future, we'll add React to our project). In our case, we can't completely rely on a framework because we need also to manage the integration with the Excalibur library. In particular, if using a framework, we will need to take care of positioning and overlapping between the UI and the Excalibur canvas, as we will see in the specific tutorial. For the moment, in any case, we don't have and we don't need any framework. Nevertheless, we need to do some manual operations on the DOM. For this reasons we create now a DOMService whose responsibility will be to execute DOM-related operations.

DOMService

The DOMService, being a service, will find its place under the /src/services path:


sword-adventure
    /src
        /index.ts
        /Game.ts
        /services
            /EngineOptionsFactory.ts
            /DomService.ts
    /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    /webpack.development.js
    /webpack.production.js
        

The DOMService is done as:


@Service()
export class DOMService {

    private map: Map<string, HTMLElement> = new Map();

    public elementExist(elementId: string): boolean {
        return Util.isDefined(document.getElementById(elementId));
    }

    public getElement(elementId: DomElementIds): HTMLElement {
        if (this.map.has(elementId)) {
            return this.map.get(elementId);
        }
        if (this.elementExist(elementId)) {
            const element: HTMLElement = document.getElementById(elementId);
            this.map.set(elementId, element);
            return element;
        }
        return undefined;
    }

}

export const uiConfig: UiDivConfig = {
    rootDivId: 'root',
    gameCanvaId: 'game',
};

export type DomElementIds = 'root' | 'game' | 'loader-progress';
export type DomElementKeys = 'rootDivId' | 'gameCanvaId';

export type UiDivConfig = { [key in DomElementKeys]: DomElementIds }

    
As you can see, this service is for now just a wrapper to the document.getElementById method. It also keeps a map containing DOM elements. We’ve chosen this solution because while developing other games, we’ve noticed that running queries on the DOM (among other things) was slowing everything down. In this way performances are a little better. In this class, there is also a type containing a series of strings (UiDivConfig). This type serves as a reminder: every time that we change an id in the index.html template, we should remember to check if that id was used somewhere else in the code. This solution helped us in preventing errors due to forgetfulness.

Back to resources loading

Everything is set up now to call the startCustomLoader method. Go back to index.ts and add some content to it:


import './index.css';
import 'reflect-metadata';
import { Game } from './Game';
import { Container } from 'typedi';

const game = Game.getInstance();
game.startCustomLoader();
    

When executing these lines, the game will be created and all resources loaded.

We need to include the index.css and reflect-metadata imports in index.ts file, otherwise they will not be included in the compilation.

Registering the scenes

The last step of this tutorial is to add a scene to our game.

From Excalibur documentation, we know that to add scenes in the game we need to call the addScene() method of the Engine class. Taking one step at a time, we first create an empty scene.

Create a scene directory under the src folder, so that our project the structure now looks as


sword-adventure
    /src
        /index.ts
        /Game.ts
        /services
            /EngineOptionsFactory.ts
            /DomService.ts
        /scenes
    /index.html
    /package.json
    /tsconfig.json
    /webpack.common.js
    /webpack.development.js
    /webpack.production.js
        

In the lastly created scenes folder, add a PlayScene.ts class which extends the Excalibur Scene class:


export class PlayScene extends Scene {

    public onActivate(): void {
        alert('activating play scene!');
    }
}
            

We've added the onActivate() method so that we'll know the scene is actually working.

Now that we have a scene for our game, let's add it to our game by registering it! Go back to the Game class and add a SceneInfo object


export class Game extends Engine {
    ...
    private static scenesInfo: SceneInfo = {
        playLevel: {key: 'playLevel', ctor: PlayScene},
    }
    ...
}
    

where the type SceneInfo is defined as


type SceneInfo = { [key in SceneKeys]: { key: key, ctor: any } };
export type SceneKeys = 'playLevel';
            

at the moment we just have one scene, the PlayLevel one.

Every time we add a new scene to our game, we will declare it in the scenesInfo field of the Game class.

To finish scenes setting up, we just need to call the addScene() method of the Engine class. Add these lines to Game.getInstance() method and that's it!


public static getInstance(): Game {
    if (this.game === undefined || this.game === null) {
        const engineOptionFactory: EngineOptionsFactory = Container.get(EngineOptionsFactory);
        const options: EngineOptions = engineOptionFactory.buildOptions();
        this.game = new Game(options);
        /** here we are adding the playLevel scene to the game! */
        Object.values(this.scenesInfo).forEach(({key, ctor}) => {
            this.game.addScene(key, new ctor())
        });
    }
    return this.game;
}
        

Customising the start button: a first step

The very last step we are taking in this tutorial is about customizing the Excalibur Play Button. To do so, let's go back to the startCustomLoader() in the Game class and add a few lines to it:


public startCustomLoader() {
    const loader: Loader = new Loader();
    this.logLoadingProgress(loader);
    loader.startButtonFactory = () => {
        const progressLoggerElement: HTMLElement = document.getElementById('loader-progress');
        /** we'll replace this button with ionic-react buttons. Stay tuned :) */
        progressLoggerElement.textContent = '100%';
        const simpleButton = document.createElement('button');
        simpleButton.textContent = 'Play Sword Adventure!';
        return simpleButton as any as HTMLButtonElement;
    };
    return Game.getInstance().start(loader);
}
        

The button that we've just replaced will for now looks uglier than the Excalibur one, but in the near future, we are going to replace it with an Ionic React button. Stay tuned!

You can read now the next tutorials, which are about adding an actor to the scene, and attaching a motion controller to it.

Did you like this article and wish there were more? Donate on Paypal so that I can write more!

Related Articles