All About HTML5 Game Development, from Publishing to Monetization

Scene transitions in ExcaliburJS - 11/11

Scene transitions in Excalibur JS

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!

Welcome to the 11th tutorial about HTML5 game development with the ExcaliburJS engine! 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 me on Twitter and Instagram to be caught up on my latest news!

Table of Contents

In this tutorial: scene transitions

In this tutorial, we add scene transitions to our game. We'll add hooks to execute code before the scene starts or ends, and for preparing it.

An application example is to show interstitial ads when changing scenes. Another could be to load specific scene resources instead of loading them at the game start. In this tutorial we'll add a simple sword animation using the animate.css library.

 

What we are going to do

In this tutorial we'll wrap the goToScene Excalibur method so that to execute and wait for asynchronous code to be completed before changing the scene. In our experience, the onActivate and onInitialize methods of the Excalibur Scene is not the right place, since they do not wait for promises to be completed.

Let's wrap the goToScene method

We start by going to the Game class and modifying the goToScene method as follows:


export class Game extends Engine {

    ...

    public goTo({toScene, fromScene}: GoToSceneConfig): void {
        this.endSceneTransitions(fromScene).then(async () => {
            await this.prepareSceneAndGoto(toScene);
        });
    }

    private async prepareSceneAndGoto(toScene: SceneKeys): Promise<void> {
        const {prepareScene,sceneStart} = SceneTransitions.transitions[toScene];
        await prepareScene();
        sceneStart().then(()=> {
          Game.getInstance().goToScene(toScene);
        });
    }

    private endSceneTransitions(fromScene: SceneKeys): Promise<void> {
        if (Util.isDefined(fromScene)) {
            const {sceneEnds} = SceneTransitions.transitions[fromScene];
            const currentScene = Game.getInstance().currentScene;
            return sceneEnds(currentScene)
        } else {
            return Promise.resolve();
        }
    }

    ...

}
                

As you can see, when calling the goTo method it's now possible to specify from which scene it is made the transition. The private methods endSceneTransitions and prepareSceneAndGoto wait for some promises to resolve before going on.

The scene transition configuration is written in the SceneTransitions module, which is done as follows:


export module SceneTransitions {

    export const transitions: SceneTransitions = {
        menuLevel: {
            prepareScene: () => {
                return Promise.resolve();
            },
            sceneStart: () => {
                return Promise.resolve();
            },
            sceneEnds: () => {
                return Promise.resolve();
            }
        },
        playLevel: {
            prepareScene: () => {
                return Promise.resolve();
            },
            sceneStart: () => {
                return Container.get(TransitionAnimationService).animateSceneTransition('playLevel');
            },
            sceneEnds: () => {
                return Promise.resolve();
            }
        }
    }


}

export type SceneTransitions = {[key in SceneKeys]: SceneTransition};
export type SceneTransition = {
    prepareScene: () => Promise<any>,
    sceneStart: (scene?: Scene) => Promise<any>
    sceneEnds: (scene?: Scene) => Promise<any>
 };

                

The only transition which is written here is the one when starting the PlayLevel. If we wanted to have other transitions, we should only fill the corresponding field in the transition variable of this module.

As you can see, this mechanism is simple. Let's have a look at the TransitionAnimationService:


@Service()
export class TransitionAnimationService {

    private domService: DOMService = Container.get(DOMService);
    private randomService: RandomService = Container.get(RandomService);
    private animationTokens: string[] = ['animate__zoomInRight', 'animate__tada', 'animate__bounce', 'animate__jello', 'animate__backInLeft', 'animate__lightSpeedInRight', 'animate__rotateInUpRight', 'animate__rollOut'];

    public animateSceneTransition(sceneKey: SceneKeys): Promise<void> {
        const sceneTransitionPromise: Promise<void> = firstValueFrom(sceneTransitionAnimationEnd);
        this.transitionAnimation(sceneKey);
        return sceneTransitionPromise;
    }

    private transitionAnimation(sceneKey: SceneKeys): void {
        const config = TransitionsConfig.animations[sceneKey];
        if(Util.isDefined(config)){
            const {animationDuration} = config;

            const layerIds: LayerIds[] = ['game', 'menu','joystick'];
            layerIds.forEach(id => this.domService.toggleElementVisibility(id, false));

            this.domService.toggleElementVisibility('animation-place', true)
            const animatedElement: HTMLElement = this.domService.getElement(uiConfig.sceneTransitionDiv);

            const animationToken = this.randomService.pickOne(this.animationTokens)

            const listener = () => {
                animatedElement.classList.remove(animationToken);
                this.domService.toggleElementVisibility('animation-place', false)
                animatedElement.style.removeProperty('--animate-duration');
                animatedElement.removeEventListener('animationend', listener);
                layerIds.forEach(id => this.domService.toggleElementVisibility(id, true));
                sceneTransitionAnimationEnd.next();
            };

            animatedElement.addEventListener('animationend', listener);
            animatedElement.style.setProperty('--animate-duration', `${animationDuration}s`);
            animatedElement.classList.add(animationToken);
        }
    }
}

                

This service is following the animate.css library instructions to animate an html element we've added in our index.html file:


<body>
...
<div class="position-absolute position-top full-size" id="animation-place" style="visibility: hidden;">
    <div class='flex--horizontal flex--justify-center flex-align-items--center gradient-background'>
        <div class='animate__animated' id="scene-transition">
            <img src='assets/voxel-pack/PNG/Items/sword_iron.png'/>
        </div>
    </div>
</div>
...
</body>
                

This is the result we've obtained:

Scene transitions effect in an Excalibur game

That's all about scene transitions!

Conclusions

In this tutorial, we've added scene transitions to our game. In one of the next tutorials, we'll use the hooks we've added in the SceneTransitions module to show ads on Android devices. Stay tuned!

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

 

Related Articles