All About HTML5 Game Development, from Publishing to Monetization

Infinite game level refinement in Excaliburjs HTML5 game engine - 5/11

Infinite game level refinement in Excaliburjs HTML5 game engine - 5/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 internationalization with i18next. 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

In this tutorial, we add some assets for our horizontal scene. We'll set up three different parallax layers to give a sense of depth to our game.

In the last tutorial we've added a HorizontalParallaxService which builds the layers of our scene, using temporary colorful rectangles. It's now time to use real images for them. We thank Kenney for their assets, which come with a CC0 licence!

 

Kenney's assets

First thing first we add a couple of Kenney's assets pack to our repository: we've chosen the voxel pack for the solid base on which our player moves, and the background elements redux pack for the background elements such as castles, bushes, and trees.

Register parallax graphics

Now that we have the asset images, we can load them and use them in our game as we did for the player images in this tutorial. Since it's the same procedure we've done for the player images, we suggest reading the dedicated tutorial and to browse the public repo of this project, looking for these classes:

  • ParallaxGraphics.ts for the import and declaration of the new assets
  • AllGraphics.ts for the association between the Graphic name and the Graphic itself
  • GraphicService.ts for the registerParallaxGraphics method, which register the named graphics to the actor
  • AllResources.ts to load the new resources before starting the Game

Use the parallax graphics

Now that it's all set, we can deal with the main topic of this tutorial: replace the colorful rectangles we've used in the previous tutorial with Kenney's assets. Our starting point is this:

We'd like to do as follow:

  • Use a castle background for the far away Layer 1
  • Replace Layer 2 colored rectangles with two different kind of trees
  • Add a Layer dedicated to bushes
  • Use a grass tile for the solid Layer on which the player moves.

We start with the HorizontalParallaxConfig, whose functions buildColorfulRectangles and buildTile are the ones responsible for the colored rectangles.

We start by working on the buildTile function. First thing first we rename it to buildGraphicTile. Then, we won't use anymore any colored rectangle, we will instead specify how many tiles we need. We change then the function signature as in


function buildGraphicTile(howMany:number, type: ParallaxType)
                    

This done, we would like to exploit the same mechanism we've used for the Player class to register the actor graphics, that is we are going to call the method registerParallaxGraphics of the GraphicService inside an Excalibur onInitialize lifecycle hook. Let's create, then, a ParallaxTile class which will look as


export class ParallaxTile extends Actor {

    private graphicService: GraphicService = Container.get(GraphicService);

    constructor(private parallaxType: ParallaxType) {
        super();
    }

    public onInitialize(_engine: Engine) {
        super.onInitialize(_engine);
        this.graphicService.registerParallaxGraphics(this.parallaxType, this);
        const {graphic, name} = this.getRandomGraphic();
        this.graphics.use(name);
    }

    private getRandomGraphic(): ParallaxGraphicConfig {
        const items = AllGraphics.parallaxGraphic[this.parallaxType];
        return items[Math.floor(Math.random() * items.length)];
    }

}

                    

As you can see, this class looks like the Player class. To remember how we've designed the GraphicService, you can read the dedicated tutorial.

We have all the pieces to keep refactoring the buildGraphicTile function. We'll instantiate as many ParallaxTile objects as the howMany variable demands:


function buildGraphicTile(howMany:number, type: ParallaxType, tileSize: TileSize){
    const tiles: ParallaxTile[] = [];
    for(let i=0; i< howMany; i++){
        const layerTile = new ParallaxTile(type, tileSize);
        layerTile.anchor = vec(0, 0);
        layerTile.addTag(Tags.LAYERS.horizontal[type]);
        tiles.push(layerTile);
    }
    return tiles;
}
                    

And this is enough to obtain what we wanted to achieve:

Infinite level with parallax effect in Excalibur -2

It seems that all the work we've done to set up the GraphicService, the AllGraphics and the allResources was worth it, since it's saving us a lot of time now!

Adapt to different viewports

The very last thing we miss is to scale the graphics to work on every screen. At this stage, you can see that by changing the screen size of the browser you're working on, the tiles are rendered with images that do not fill well. Luckily we've set up our code in such a way that the scaling can be easily achieved!

Let's start by having a look at the ParallaxTile class. In the onInitialize method we get a random graphic and use it. The graphic is an Excalibur GraphicsComponent, whose width and height can be set. Let's then change the class constructor by requiring also the width and the height of the graphic:


export class ParallaxTile extends Actor {

    private graphicService: GraphicService = Container.get(GraphicService);

    constructor(private parallaxType: ParallaxType, private tileSize: TileSize) {
        super();
    }

    public getTileSize(): TileSize {
        return this.tileSize;
    }

    public onInitialize(_engine: Engine) {
        ...
        const {graphic, name} = this.getRandomGraphic();
        graphic.height = this.tileSize.height;
        graphic.width = this.tileSize.width;
        ...
    }

    ...

}

export type TileSize = { width: number, height: number };

    

We instantiate the ParallaxTile objects inside the buildGraphicTile method, so we should adapt it to our needs:


function buildGraphicTile(howMany:number, type: ParallaxType, tileSize: TileSize){
    ...
    for(let i=0; i< howMany; i++){
        const layerTile = new ParallaxTile(type, tileSize);
        ...
    }
    ...
}

                    

We need just to know the value of the tileSize variable, but this is easy since we were already computing it before, when we were using colored rectangles: the width and the height of the tiles are given by the getLayerSize function, which we wrote in the previous tutorial!

After some fine-tuning of the layer velocities in the HorizontalParallaxConfig.headedRightVelocitiesMagnitude constant and on the heightScaling and tileNumber variables given to the getLayerSize function, which we can rename to getGraphicTileSize, we're done. This is the final result:

Infinite level with parallax effect in Excalibur -3

Conclusions

In this tutorial, we've used real image assets for displaying our parallax layers. The amount of work was contained since we've structured our code in a good way, as it was displayed in this tutorial, where we've added the assets for the player character. Now that we have an environment where to move, we can proceed by giving our player a mission: in the next tutorial we'll add another character, the mentor. The mentor and our character will have a little talk thanks to a Twine integration. Thanks to Twine it will be possible to write dialogs that can evolve differently depending on the player's answer.

In the next tutorial. we'll add a mentor to give game instruction to our players, integrating Twinery for managing character dialogs.

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

Related Articles