Phaser 3 Input Text – Form Ui- Part 1

Phaser 3 Input Text

About a month ago I needed to find a way to implement a Phaser 3 input text field. This was due to needing to put a form into a Phaser 3 virtual world project I was working on. I had already placed an input element below the canvas for users to send chats to each other, but this needed much more space. 

Could I accomplish my project’s goals without having to totally rewrite a text field, dropdowns, and other form elements?

Then I remembered in Phaser 2 we did have an input field that was a plug-in. I recalled that the developer added an actual HTML form element over the existing HTML5 canvas. Could I use some of the experiments I’ve been doing with the AlignGrid for form elements as well? So far I had only been using it to align sprites, text, and images inside the canvas.

I realized I could do it but with a limitation.  That limit being that the canvas had to be in the top left-hand corner of the webpage. If I did need it to be in the middle of a webpage I could accomplish the task using an iframe. This did not pose too much trouble with my work because my games are either on mobile which means they’re in the top left-hand corner already or I use a pop-up window if the player is trying to use the game on a desktop or laptop. This post will focus on the Phaser 3 text input and I will follow up with additional posts covering buttons and dropdowns. 
  Here is the final result below:

Phaser 3 Input Text example
Phaser 3 Input Text and Form Elements

The Phaser Code

Instead of adding a text field dynamically I decided to work with existing form elements placed into the index.html. I would then write a class that would allow me to keep writing in the Phaser code style I was accustomed to. It was important that I did all the scaling and aligning from a Phaser Scene. You can see what I mean in a sample of my sceneMain.

class SceneMain extends Phaser.Scene {
    constructor() {
        super('SceneMain');
    }
    preload() {}
    create() {
        this.formUtil = new FormUtil({
            scene: this,
            rows: 11,
            cols: 11
        });
        this.formUtil.showNumbers();
        //
        //
        //
        this.formUtil.scaleToGameW("myText", .3);
        this.formUtil.placeElementAt(16, 'myText', true);
        //
        //
        //
        this.formUtil.scaleToGameW("area51", .8);
        this.formUtil.scaleToGameH("area51", .5);
        this.formUtil.placeElementAt(60, "area51", true, true);
        this.formUtil.addChangeCallback("area51", this.textAreaChanged, this);
        //
        //
        //
    }
    textAreaChanged() {
        var text = this.formUtil.getTextAreaValue("area51");
        console.log(text);
    }
    update() {}
}

The Html Code

For the HTML I used simple form elements after the game div tag with the id tags set to ‘myText’ for the text field and ‘area51’ for the text area.

<!DOCTYPE html>
<html>
    <head>
        <title>
        </title>
        <meta content="user-scalable=0, initial-scale=1,minimum-scale=1, maximum-scale=1, width=device-width, minimal-ui=1" name="viewport">
        </meta>
    </head>
</html>
<script src="js/phaser.min.js" type="text/javascript">
</script>
<script src="js/sceneMain.js" type="text/javascript">
</script>
<script src="js/main.js" type="text/javascript">
</script>
<script src="js/util/formUtil.js" type="text/javascript">
</script>
<script src="js/util/alignGrid.js" type="text/javascript">
</script>
<body>
    <div id="phaser-game">
    </div>

    <input type="text" id="myText"/>
    <textarea id="area51">SOME TEXT HERE</textarea>
</body>

The FormUtil Class

For the formUtil class, I borrowed a lot of code from my AlignGrid class.  I also added a few functions to get the text value as well. The text area on change only works when the text area loses focus, but you could easily check the value when a button is pressed, which I will cover in the next section. 

class FormUtil {
    constructor(config) {
        //super();
        this.scene = config.scene;
        //get the game height and width
        this.gameWidth = this.scene.game.config.width;
        this.gameHeight = this.scene.game.config.height;
        this.alignGrid = new AlignGrid({
            scene: this.scene,
            rows: config.rows,
            cols: config.cols
        });
    }
    showNumbers() {
        this.alignGrid.showNumbers();
    }
    scaleToGameW(elName, per) {
        var el = document.getElementById(elName);
        var w = this.gameWidth * per;
        el.style.width = w + "px";
    }
    scaleToGameH(elName, per) {
        var el = document.getElementById(elName);
        var h = this.gameHeight * per;
        el.style.height = h + "px";
    }
    placeElementAt(index, elName, centerX = true, centerY = false) {
        //get the position from the grid
        var pos = this.alignGrid.getPosByIndex(index);
        //extract to local vars
        var x = pos.x;
        var y = pos.y;
        //get the element
        var el = document.getElementById(elName);
        //set the position to absolute
        el.style.position = "absolute";
        //get the width of the element
        var w = el.style.width;
        //convert to a number
        w = this.toNum(w);
        //
        //
        //center horizontal in square if needed
        if (centerX == true) {
            x -= w / 2;
        }
        //
        //get the height
        //        
        var h = el.style.height;
        //convert to a number
        h = this.toNum(h);
        //
        //center verticaly in square if needed
        //
        if (centerY == true) {
            y -= h / 2;
        }
        //set the positions
        el.style.top = y + "px";
        el.style.left = x + "px";
    }
    //changes 100px to 100
    toNum(s) {
        s = s.replace("px", "");
        s = parseInt(s);
        return s;
    }
    //add a change callback
    addChangeCallback(elName, fun, scope = null) {
        var el = document.getElementById(elName);
        if (scope == null) {
            el.onchange = fun;
        } else {
            el.onchange = fun.bind(scope);
        }
    }
    getTextAreaValue(elName) {
        var el = document.getElementById(elName);
        return el.value;
    }
    getTextValue(elName) {
        var el = document.getElementById(elName);
        return el.innerText;
    }
}
Phaser 3 input text example

I hope this has been helpful. If you give it a try, I’d love your feedback.


3 thoughts on “Phaser 3 Input Text – Form Ui- Part 1”

Leave a Comment