Phaser 3 and Firebase

In Mobsor version one I had created the beginning of a user management system but it was far from finished. Integrating Phaser and Firebase would have been easier both on me and the end user.

In this version I will be using Firebase because it offers all of the functionality I want and it’s free.

I have worked with Firebase on various past projects but I have never integrated it with Phaser.

Phaser 3 dom added login / register form

To begin with I will just be using email based authentication but a large list of capabilities are available.

I have so far only used and tested email based authentication.

  • Phone (I believe this has a limit per time period)
  • Google (Similar to email but linked to user Google account?)
  • Play Games (I’ve only seen used on Android apps)
  • Game Center (Apple based?)
  • Facebook
  • Twitter
  • GitHub
  • Yahoo
  • Microsoft
  • Anonymous (Could allow a user to begin play without creating an account and then register or abandon)

In this fork (github.com/matlintz/phaser3-typescript-webpack/tree/Firebase) I first updated all dependencies as every now and then things seemed to hang up during the build / browser sync process. That is still happening now and then so I will take a deeper look at that when I have time. The production build should process the assets directory for a final package or folder to deploy to a live server with little or no finding of all things needed.

Next I installed Firebase – npm install firebase –save

In the initial repo ( https://mobsor.com/blog/2019/07/getting-started-on-mobsor-version-ii/ ) I copied an existing phaser 3 boiler plate with no scenes or assets so I could use it as a base for anything.

Now I have added a menu scene that for now just allows a user to register or login and to set their Firebase display name.

Main.ts is now

import 'phaser';
import firebase = require("firebase/app");
import 'firebase/auth';
import firebaseConfig from './firebaseConfig';
import Menu from './scenes/Menu';
// Initialize Firebase
firebase.initializeApp(firebaseConfig);

const config: Phaser.Types.Core.GameConfig = {
  type: Phaser.AUTO,
  parent: 'content',
  dom: { 
    createContainer: true
  },
  width: window.innerWidth,
  height: window.innerHeight,

  resolution: 1,
  scale: {
    mode: Phaser.Scale.RESIZE,
    autoCenter: Phaser.Scale.CENTER_BOTH
  },

  backgroundColor: "#000000",
  scene: [Menu]
};

let game: Phaser.Game = new Phaser.Game(config);

I have not included firebaseConfig to the repo as it contains my Firebase info. It’s not really a security issue as any JavaScript based Firebase project will expose this info.

Add your Firebase info to your project src/firebaseConfig.ts


let firebaseConfig:any = {
    apiKey: "api key here",
    authDomain: "your auth domain here.firebaseapp.com",
    databaseURL: "not using database but Firebase databaseURL will be here",
    projectId: "Your Project ID",
    storageBucket: "",
    messagingSenderId: "Your Messenger ID",
    appId: "Your appID"
  };
  export default firebaseConfig;

Menu Scene – Menu.ts

import Firebase = require("firebase/app");
import 'firebase/auth';
class Menu extends Phaser.Scene {
    private user: Firebase.User;
    private displayName: string;
    private registerElement: any;
    private userInfoElement: any;
    private headerText: Phaser.GameObjects.Text;
    constructor() {
        super({
            key: 'Menu'
        });
    }

    hideRegisterElement() {
        this.registerElement.removeListener('click');
        this.scene.scene.tweens.add({ targets: this.registerElement.rotate3d, x: 1, w: 90, duration: 2000, ease: 'Power3' });
        let self = this;
        this.scene.scene.tweens.add({
            targets: this.registerElement, scaleX: 2, scaleY: 2, y: 700, duration: 2000, ease: 'Power3',
            onComplete: function () {
                self.registerElement.setVisible(false);
            }
        });
    }

    showUserInfo() {
        if (this.user !== null) {
            let self = this;
            this.userInfoElement.setVisible(true);
            let inputDisplayName = this.userInfoElement.getChildByName("displayName");
            inputDisplayName.value = this.user.displayName;
            this.userInfoElement.addListener('click');
            this.userInfoElement.on('click', function (event) {
                if (event.target.id === 'closeSettings') {
                    self.userInfoElement.removeListener('click');
                    self.userInfoElement.setVisible(false);
                }

                if (event.target.id === 'saveSettings') {
                    //if name not changed then skip update to firebase
                    if (self.displayName != inputDisplayName.value) {
                        Firebase.auth().currentUser.updateProfile({ displayName: inputDisplayName.value }).then(
                            function (r) {
                                self.displayName = inputDisplayName.value;
                                //this doesn't work well on smaller screens
                                self.headerText.setText('Welcome ' + self.displayName);
                                let posX = self.cameras.main.centerX - (24 * (self.displayName.length / 2 + 8));
                                self.headerText.setPosition(posX, 25);
                            }
                        );
                    }
                    self.userInfoElement.removeListener('click');
                    self.userInfoElement.setVisible(false);
                }
            });
        }
    }
    
    preload() {
        this.load.svg('usericon', '/assets/icons/zondicons/user.svg');
        this.headerText = this.add.text(this.cameras.main.centerX - 35, 50, 'Welcome', { fontFamily: 'Verdana, "Times New Roman", Tahoma, serif', fontSize: 48, fill: '#1212FF' });
        let self = this;
        Firebase.auth().onAuthStateChanged(function (user) {
            self.user = user;
            if (user !== null) {
                self.hideRegisterElement();
                self.displayName = self.user.displayName !== null ? self.user.displayName : self.user.email;
                //duplicate code - move in future
                self.headerText.setText('Welcome ' + self.displayName);
                let posX: number = self.cameras.main.centerX - (24 * (self.displayName.length / 2 + 8));
                if (posX < 1) { posX = 1; }
                self.headerText.setPosition(posX, 25);
            }
        });
        this.load.html('register', '/assets/html/register.html');
        this.load.html('userinfo', '/assets/html/userinfo.html');
    }
    
    create() {
        let self = this;
        let userInfoButton = this.add.image(25, 25, 'usericon').setScale(.2).setInteractive();
        userInfoButton.setTintFill(0xFFFFFF);
        userInfoButton.on('pointerdown', this.showUserInfo, this);
        this.userInfoElement = this.add.dom(this.cameras.main.centerX, this.cameras.main.centerY).createFromCache('userinfo');
        this.userInfoElement.setVisible(false);
        if (this.user == null) {
            this.registerElement = this.add.dom(this.cameras.main.centerX, this.cameras.main.centerY).createFromCache('register');
            this.registerElement.setPerspective(800); /* not quite sure what this does but found more info at https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.DOMElement.html */

            this.registerElement.addListener('click');
            this.registerElement.on('click', function (event) {

                let inputUsername = this.getChildByName('username');
                let inputPassword = this.getChildByName('password');
                if (inputUsername.value.length > 0 && inputPassword.value.length > 0) {
                    //todo
                    //handle various error results from Firebase
                    if (event.target.id === 'Rlogin') {

                        Firebase.auth().signInWithEmailAndPassword(inputUsername.value, inputPassword.value).then(
                            function (r) {
                                self.hideRegisterElement();
                            }).catch(function(error){
                                alert(error.message);
                            });
                    }
                    if (event.target.id === 'Rregister') {
                        try {
                            Firebase.auth().createUserWithEmailAndPassword(inputUsername.value, inputPassword.value).catch(function (error) {
                                console.log(error); 
                                alert(error.message);
                            });
                        }
                        catch (error) {
                            console.log(error);
                            alert(error.message);

                        }
                    }
                }
            });
        }
    }
}
export default Menu;
/*
Reference Links:
https://www.phaser.io/examples/v3/view/game-objects/dom-element/form-input

*/

This was a quick run at integrating Phaser 3 and Firebase and not what I’d consider 100%. I still need logout functionality and for the menu state to change. Right now if you use dev tools and delete the firebase local db the menu should update seeing that the same as a log out.

More work needs done on presentation and text size and positioning.

Could add ability for user to upload an avatar to tie to the Firebase account associated with the app.

Allow for and encourage email validation through the user settings dialog.

Current test version for this branch mobsor.com/phaser-firebase/

Getting Started on Mobsor Version II

The initial phaser game was the closest I came to finishing a game project.

  • Phaser 2
  • Ionic Framework 2
  • 146,696 Stars
  • 349,446 Planets
  • 6845,89 Asteroids

But beyond mining, continually upgrading, and never ending battles it was not headed to what my original plans were.

The account creation still needed account management and account recovery / password reset functionality.

Mobsor Version I (game)
Version II Plans
  • Phaser 3
  • Firebase for account creation / management
  • Dropping Ionic for now

I have been creating most of my Phaser 3 projects using troyedwardsjr/phaser3-typescript-webpack. Since I always start by removing and making the same modifications I have started my own for at matlintz/phaser3-typescript-webpack

I will keep that pretty much as is and create a separate repo for the game.

main.ts
import 'phaser';


const config:Phaser.Types.Core.GameConfig = {
    type: Phaser.AUTO,
    parent: 'content',
    width: window.innerWidth,
    height: window.innerHeight,
    resolution: 1, 
    scale: {
      mode: Phaser.Scale.RESIZE,
      autoCenter: Phaser.Scale.CENTER_BOTH
    },

    backgroundColor: "#000000",
    scene: [
      
    ]
};
//sclae:mode - https://rexrainbow.github.io/phaser3-rex-notes/docs/site/scalemanager/

let game:Phaser.Game = new Phaser.Game(config);

I updated Phaser 3.11.0 to Phaser 3.18.1, removed the example scene and player. The typescript definitions for Phaser are already included in 3.18 and I removed the ones provided by the initial boilerplate.

This version now will just provide a full window re-sizable black screen.

Back end Development

Most of the back end for Version I will be somewhat usable by version II. Moving from self managed account creation to Firebase will require some changes in token handling.

Fresh Install – Editor Unusable

While the purpose of this blog was not to deal with WordPress issues I ran into this one right away.

Installed two plugins: Yoast and Gutenberg editor. The Gutenberg editor only had 2 stars but I figured I’d give it a try to see if it was really a two star.

First page created fine (about) and I finally decided to write the first post.

WordPress 5.2.2 default editor error

The editor failed to load with an error bar that said the editor had failed and I could attempt to recover or copy the error which was this.

TypeError: Cannot read property ‘show_ui’ of undefined
at https://mobsor.com/blog/wp-includes/js/dist/editor.min.js?ver=9.2.6:17:119556
at i (https://mobsor.com/blog/wp-includes/js/dist/vendor/lodash.min.js?ver=4.17.11:6:91)
at An.filter (https://mobsor.com/blog/wp-includes/js/dist/vendor/lodash.min.js?ver=4.17.11:99:338)
at https://mobsor.com/blog/wp-includes/js/dist/editor.min.js?ver=9.2.6:17:119521
at Td (https://mobsor.com/blog/wp-includes/js/dist/vendor/react-dom.min.js?ver=16.8.4:82:11)
at hi (https://mobsor.com/blog/wp-includes/js/dist/vendor/react-dom.min.js?ver=16.8.4:102:385)
at Qg (https://mobsor.com/blog/wp-includes/js/dist/vendor/react-dom.min.js?ver=16.8.4:144:217)
at Rg (https://mobsor.com/blog/wp-includes/js/dist/vendor/react-dom.min.js?ver=16.8.4:145:76)
at Sc (https://mobsor.com/blog/wp-includes/js/dist/vendor/react-dom.min.js?ver=16.8.4:158:109)
at Z (https://mobsor.com/blog/wp-includes/js/dist/vendor/react-dom.min.js?ver=16.8.4:156:492)

Searching lead to a few results none of which was useful to me (Only solution was based on nginx I’m using apache).

I disabled the Gutenberg editor but the error remained.

Finally I installed the classic editor plugin and I was able to post.

From one of the nginx related issues

 

Looks like familiar issue on nginx server

https://github.com/WordPress/gutenberg/issues/9912

Update Nginx try_files directive with following:

try_files $uri $uri/ /index.php$is_args$args;

So now I am assuming I must have some change needed to my Apache rewrite but not sure what yet.