All About HTML5 Game Development, from Publishing to Monetization

Ionic React for Game UI in Excaliburjs HTML5 game engine - 8/11

Ionic React for Game UI in Excaliburjs HTML5 game engine - 8/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 scene transitions. 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: Ionic React

In this tutorial we integrate Ionic to our game, to manage the graphical interface.

In the last tutorial we've coded the level logic of the main scene of our game. Now, when the player takes the sword, the level is completed. We show a simple message displaying the score and a button to go to the the main menu of our game (which doesn't exist yet; we'll create it in this tutorial). We want to avoid handling by hand the DOM as much as possible: it would be great to take advantage of a library or a framework that does all the work by itself and that has a set of ready-to-use components. In this way we shouldn't worry about style and browser compatibility and we could focus on the game. There is a plenty of tools we can use; we've decided to use Ionic React (you can read here why). In this tutorial, we'll find out how to integrate it with our Excalibur game!

What we suppose you already know

In this tutorial we assume you have a basic knowledge of how React works. You can have a look at their documentation here We will also assume you know what Ionic is, here is its documentation.

What we are going to do

In this tutorial we'll use React to give the end-of-level message a nicer look. It won't be sophisticated design work, our goal is to show how to use React. We will also create a simple Menu Scene from which the user will be able to start the Play Level or change the settings. We'll implement sound and language settings (together with data persistence) in another tutorial!.

Ionic React set up

First thing first we need to install all the dependencies we need. In your terminal run


npm i @ionic/react react react-dom
npm i @types/react save--dev
                

These commands install the Ionic and React dependencies.

To use Ionic we also need to add some imports in the index.ts file, as explained in their documentation:


...
import '@ionic/react/css/core.css';
/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';
/* Optional CSS utils that can be commented out */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';
...

setupIonicReact();

const domService: DOMService = Container.get(DOMService);
...
                

Remember to call setupIonicReact() otherwise you won't see any Ionic style in your project!

Use React in the End of Level modal

As we've written, we won't do any sophisticated work while redesigning this modal. The purpose is understand how to integrate React in our game, to create it and attach it to the document body. That said, the first thing we'll do is to create a ui folder under the root src one, at the same level of the services directory. In the new folder we'll create an EndOfLevelModal.tsx file which is the React component we'll use to display the score at the end of the play level:


export default function EndOfLevelModal({ score }: { score:number }) {
  return (
    <div>
          <p>
            Your score is
            {score}.
          </p>
    </div>
  );
}
                

As you can see, the content of this component is the same as the temporary one we've created in the last tutorial. Since we're going to use this new component, remember to go back to the index.html and DOMService.ts files and clean up a bit the temporary code we've written last time.

Now that we have the EndOfLevelModal, we'd like to use it. To do so, go to the ModalService which is responsible for opening the modal, and change its showEndOfLevelModal method:


@Service()
export class ModalService {
  private domService = Container.get(DOMService);

  public showEndOfLevelModal(score:number) {
    const container: HTMLElement = this.domService.getElement('modal-container');
    ReactDOM.render(React.createElement(EndOfLevelModal, { score }), container);
    this.domService.toggleElementVisibility('modal-container', true);
  }
}
                

Before, this service asked for the coins-score HTML element and changed its inner text. Now, the ModalService uses the React API to create and render the EndOfLevelModal component, appending it to the modal-container document body element. In particular, the React.createElement is creating the component passing it the score which is the number of coins taken while playing, while the ReactDOM.render call is displaying it in the browser.

After adding some style to the EndOfLevelModal (you can check them on Github), here's how the modal looks like:

End of level modal in our Excalibur game

The Last thing we need to add to the modal is a button to go to the Menu Level of our game, which doesn't exist yet. After having created it as we've done for the PlayLevel, we can add the button as follows:


export default function EndOfLevelModal({ score }: { score:number }) {
  return (
    <div className="full-size">
      <div className="margin--auto width--60 full-height">
        <div className="flex--vertical flex--justify-center flex-align-items--center">
          <p>
            Your score is
            {score}
            .
          </p>
          <IonButton
            onClick={() => {
              const game = Game.getInstance();
              game.goTo('menuLevel');
            }}
            color="primary"
          >
            Go to the main menu
          </IonButton>
        </div>
      </div>
    </div>
  );
}
                

As you can see, there's an ionic-button which when clicked changes the scene to the menu one.

The Menu Level

As for the end-of-level modal, we'll create a simple menu to find out how to integrate an Ionic React UI with our Excalibur game. While the modal was a single isolated React component, for the main menu we'll create a complete Ionic App.

Following Ionic documentation, we'll start by creating an IonicReactUI.tsx component which looks as:


export default function IonicReactUI() {
  return (
    <React.StrictMode>
      <IonApp>
        <MainMenu />
      </IonApp>

    </React.StrictMode>
  );
}
                

The MainMenu is a component we'll write right now, containing the 'Play' and 'Settings' buttons.


export default function MainMenu() {
  return (
    <div className="full-size white-background" id="main-menu">
      <div className="margin--auto width--60 full-height">
        <div className="flex--vertical flex--justify-center flex-align-items--center">
          <p>Sword Adventure</p>
          <IonButton
            onClick={() => {
              const game = Game.getInstance();
              game.goTo('playLevel');
            }}
            color="primary"
          >
            Play
          </IonButton>
          <IonButton
            onClick={() => {
              alert('sound settings, to be implemented');
            }}
            color="primary"
          >
            Settings
          </IonButton>
        </div>
      </div>
    </div>
  );
}
                

Now that we have our simple menu, we can think about how to render it. We want it to be displayed when the MenuScene is started. It's a good start to make something similar to what we already did for the EndOfLevelModal, that is calling the React API to create and render the component in a chosen HTMLElement, which for us will be the menu div element we've added in index.html.

The question is then: where to write the code? Our first approach was to write the code in the onActivate method of the MenuScene:


export class MenuScene extends Scene {
  private domService = Container.get(DOMService);

  public onActivate(): void {
    const root: HTMLElement = this.domService.getElement('menu');
    ReactDOM.render(React.createElement(IonicReactUI), root);
    this.domService.toggleElementVisibility('menu', true);
    this.domService.toggleElementVisibility('modal-container', false);
  }

  public onDeactivate(): void {
    // next step: clear ui HTML elements before going to other scenes
  }
}

                

And this is a reasonable solution, except for one point: imagine we have a lot of scenes. We are choosing Ionic React as a framework and by writing the related code in their onActivate method, we are spreading it everywhere. This makes it more difficult to maintain the code in the long term: for example, if the import of React would change from import React from 'react' to import React from 'react-this-is-an-example' we would need to look for that import a lot of files to correct it. Another chance is that one day we want to change the framework to, let's say, Angular. It would be a lot of work to look for React in every scene to change it.

We also foresee another problem that could emerge in the future. Imagine that the graphical interface is very slow to be built and rendered. Then the onActivate method is not the best place to create it, because it would slow everything down while the player is playing, resulting in glitches or a blocked UI. The best solution is to first prepare the scene while a loading page is shown, and when everything is ready then to remove the loading page and show the scene. This aspect can be managed from within the IonicReactUI component,

To write better code while considering the two topics that we've just written about, we can do two things:

  • Use a service whose responsibility is to call the React-related code, as the DOMService does for the DOM API
  • Improve the IonicReactUI component so that it shows a loading page while it's not ready yet. This is more an improvement for heavy UIs. This is not our case, so we won't implement it.

A React service

By looking at the existing code we see that the ModalService could be the right place where to move the code. We rename it to UIService and add a method to it:


@Service()
export class UIService {
  private domService = Container.get(DOMService);

  public showEndOfLevelModal(score:number) {
    const container: HTMLElement = this.domService.getElement('modal-container');
    ReactDOM.render(React.createElement(EndOfLevelModal, { score }), container);
    this.domService.toggleElementVisibility('modal-container', true);
  }

  public showMainMenu(){
    const root: HTMLElement = this.domService.getElement('menu');
    ReactDOM.render(React.createElement(IonicReactUI), root);
    this.domService.toggleElementVisibility('menu', true);
    this.domService.toggleElementVisibility('modal-container', false);
  }
}
                

In the MenuScene.onActivate method we'll simply call it:


export class MenuScene extends Scene {
  private uiService = Container.get(UIService);

  public onActivate(): void {
    this.uiService.showMainMenu();
  }

  public onDeactivate(): void {
    // next step: clear ui html elements before going to other scenes
  }
}
                

At this stage, this is the result you should see when playing the game:

First UI in our Sword Adventure game

Karma and Jest configs for Ionic React

Last but not least, to write tests we need to change a little the Karma and Jest configurations.

About Karma: it's enough to make it aware of *.tsx files, by adding them to the rules and resolve fields:

Karma configuration

While for Jest, we need to instruct the tool to ignore the ionic-related node modules, otherwise, it will try to transform it is according to its rules:

Jest configuration

Conclusions

In this tutorial, we've added an Ionic React simple interface to show the main menu for our game. The menu allows to go back to the Play Level or adjust the sound settings, which we do not have yet: the topic of the next tutorial is exactly the sound management and the data persistence of the user choice! We will add a data storage which is going to work both on computers and Android devices, so that if the user chooses not to play sound, at the next opening of our game, it still will be silenced. We can also use the storage to save the game state, as the number of coins that the player has already taken while playing the Play Level. Read here here the data persistence tutorial!

In the next tutorial we'll add a persistent data storage to remember user preferences and game state.

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

Related Articles