How to use plugins in Phaser with Typescript

Hello!

I’m trying to create a dialog in my game, similar to this:


Image credit goes to : Nikolai Evtihiev

But I’m stuck! I tried to use a plugin: this plugin

However, every method that I tried gave me an error.
How to install the plugin so that I can use it in all my scenes?

const game = new Phaser.Game({
    width: 1920,
    height: 1080,
    backgroundColor: "#2f2f2f",
    parent: 'game',
    scale: {
        mode: Phaser.Scale.ScaleModes.FIT,
        autoCenter: Phaser.Scale.Center.CENTER_BOTH
    },
    dom: {
        createContainer: true
    },
    scene: [Boot, Preload, Level, Lab],
    plugins: {
        global: [{
            key: 'rexUI',
            plugin: UIPlugin,
            mapping: 'rexUI',
            start: false
        }]
    }
});

This is my game object, it is located in index.ts.
I’m trying to use the plugin in lab.ts, but I keep getting an error:

var dialog = this.scene.rexUI.add.dialog({

Property 'rexUI' does not exist on type 'ScenePlugin'.ts

How to solve this issue?
Or is there a way to achieve my goal without using any plugin?

Really appreciate your help!
Thank you!

https://rexrainbow.github.io/phaser3-rex-notes/docs/site/ui-overview/#using-typescript-declaration-file

Thank you for the reply!
I saw that example before posting here, but I couldn’t get it to work.

This is my index.ts

import Phaser from "phaser";
import Level from "./scenes/Level";
import preloadPackUrl from "../static/assets/preload-asset-pack.json";
import Preload from "./scenes/Preload";
import Lab from "./scenes/Lab";
import RexUIPlugin from 'phaser3-rex-plugins/templates/ui/ui-plugin.js';

class Boot extends Phaser.Scene {

    constructor() {
        super("Boot");
    }

    preload() {

        this.load.pack("pack", preloadPackUrl);
    }

    create() {

       this.scene.start("Preload");
    }
}

const game = new Phaser.Game({
    width: 1920,
    height: 1080,
    backgroundColor: "#2f2f2f",
    parent: 'game',
    scale: {
        mode: Phaser.Scale.ScaleModes.FIT,
        autoCenter: Phaser.Scale.Center.CENTER_BOTH
    },
    dom: {
        createContainer: true
    },
    scene: [Boot, Preload, Level, Lab],
    plugins: {
        global: [{
            key: 'rexUI',
            plugin: RexUIPlugin,
            mapping: 'rexUI'
        }]
    }
});

game.scene.start("Boot");

and this is my lab.ts (the scene where I want to use the plugin):

// You can write more code here

/* START OF COMPILED CODE */

import Phaser from "phaser";
import UIPlugin from 'phaser3-rex-plugins/templates/ui/ui-plugin.js';
import { CustomShapes } from 'phaser3-rex-plugins/templates/ui/ui-components';

const COLOR_PRIMARY = 0x4e342e;
const COLOR_LIGHT = 0x7b5e57;
const COLOR_DARK = 0x260e04;

class SpeechBubble extends CustomShapes {
    constructor(
        scene: Phaser.Scene,
        fillColor?: number,
        strokeColor?: number
    ) {
        super(scene, {
            create: { lines: 1 },
            update: function () {
                var radius = 20;
                var indent = 15;

                var left = 0, right = this.width,
                    top = 0, bottom = this.height, boxBottom = bottom - indent;
                var lines = this.getShapes()[0] as CustomShapes.Lines;
                lines
                    .lineStyle(2, this.getData('strokeColor'), 1)
                    .fillStyle(this.getData('fillColor'), 1)
                    // top line, right arc
                    .startAt(left + radius, top).lineTo(right - radius, top).arc(right - radius, top + radius, radius, 270, 360)
                    // right line, bottom arc
                    .lineTo(right, boxBottom - radius).arc(right - radius, boxBottom - radius, radius, 0, 90)
                    // bottom indent                    
                    .lineTo(right * 0.5, boxBottom).lineTo(right * 0.4, bottom).lineTo(right * 0.3, boxBottom)
                    // bottom line, left arc
                    .lineTo(left + radius, boxBottom).arc(left + radius, boxBottom - radius, radius, 90, 180)
                    // left line, top arc
                    .lineTo(left, top + radius).arc(left + radius, top + radius, radius, 180, 270)
                    .close();

            }
        })

        this
            .setData('fillColor', fillColor)
            .setData('strokeColor', strokeColor)

        scene.add.existing(this);
    }
}

export default class Lab extends Phaser.Scene {
  rexUI: UIPlugin;
  constructor() {
    super("Lab");

    /* START-USER-CTR-CODE */
    // Write your code here.
    /* END-USER-CTR-CODE */
  }

  editorCreate(): void {
    // bG_challenge1
    this.add.image(960, 540, "BG_challenge1");

    var element = this.add.dom(400, 600).createFromCache("nameform");

    var text = this.add.text(10, 10, "Please login to play", {
      color: "white",
      fontFamily: "Arial",
      fontSize: "32px ",
    });

    element.setPerspective(800);

    element.addListener("click");

    element.on("click", function (this: any, event: any) {
      if (event.target.name === "loginButton") {
        var inputUsername = this.getChildByName("username");
        var inputPassword = this.getChildByName("password");

        //  Have they entered anything?
        if (inputUsername.value !== "" && inputPassword.value !== "") {
          //  Turn off the click events
          this.removeListener("click");

          //  Tween the login form out
          this.scene.tweens.add({
            targets: element.rotate3d,
            x: 1,
            w: 90,
            duration: 3000,
            ease: "Power3",
          });

          this.scene.tweens.add({
            targets: element,
            scaleX: 2,
            scaleY: 2,
            y: 700,
            duration: 3000,
            ease: "Power3",
            onComplete: function () {
              element.setVisible(false);
            },
          });

          //  Populate the text with whatever they typed in as the username!
          text.setText("Welcome " + inputUsername.value);
        } else {
          //  Flash the prompt
          this.scene.tweens.add({
            targets: text,
            alpha: 0.1,
            duration: 200,
            ease: "Power3",
            yoyo: true,
          });
        }
      }
    });

    this.tweens.add({
      targets: element,
      y: 300,
      duration: 3000,
      ease: "Power3",
    });

    this.events.emit("scene-awake");
  }

  /* START-USER-CODE */

  // Write your code here

  preload() {
    this.load.html("nameform", "assets/loginForm.html");
  }

  create() {
    this.editorCreate();

    var content = `Phaser is a fast, free, and fun open source HTML5 game framework that offers WebGL and Canvas rendering across desktop and mobile web browsers. Games can be compiled to iOS, Android and native apps by using 3rd party tools. You can use JavaScript or TypeScript for development.`;

        this.rexUI.add.sizer({
            x: 400, y: 300,
            width: 500,
            orientation: 'x',

            space: { left: 10, right: 10, top: 10, bottom: 25, item: 10 }
        })
            .addBackground(
                new SpeechBubble(this, COLOR_PRIMARY, 0xffffff)
            )
            .add(
                this.rexUI.add.roundRectangle(0, 0, 0, 0, 20, COLOR_LIGHT),
                {
                    proportion: 0,
                    align: 'bottom'
                }
            )
            .add(
                this.rexUI.wrapExpandText(this.add.text(0, 0, content)),
                {
                    proportion: 1,
                    align: 'center',
                    expand: true
                }
            )
            .add(
                this.rexUI.add.roundRectangle(0, 0, 0, 0, 20, COLOR_LIGHT),
                {
                    proportion: 0,
                    align: 'bottom'
                }
            )
            .layout()
            .drawBounds(this.add.graphics(), 0xff0000);
  }

  /* END-USER-CODE */
}

/* END OF COMPILED CODE */

// You can write more code here

I still get an error in this line:

rexUI: UIPlugin;

Property 'rexUI' has no initializer and is not definitely assigned in the constructor.

I’m sure I’m doing something wrong, but I couldn’t figure it out.
Sorry if this is stupid, but I have been at it since last night.

Have you added this line?

class Demo extends Phaser.Scene {
    rexUI: UIPlugin;
    // ...
}

yes, that’s the line that gives me an error.

It’s the plugin installing issue, see these lines.
rexUI is a scene plugin :

    plugins: {
        scene: [{
            key: 'rexUI',
            plugin: UIPlugin,
            mapping: 'rexUI'
        }]
    }

not

    plugins: {
        global: [{
            key: 'rexUI',
            plugin: RexUIPlugin,
            mapping: 'rexUI'
        }]
    }

I edited these lines. Same result, that error is still there.

plugins: {
        scene: [{
            key: 'rexUI',
            plugin: UIPlugin,
            mapping: 'rexUI'
        }]
    }

The only solution I could come up with is this:

rexUI!: UIPlugin;

but I’m not sure if this is a good solution or not.

Which version of phaser3-rex-plugins in your environment?

This version

"name": "phaser3-rex-plugins",
    "version": "1.1.63",
    "description": "Plugins of phaser3",
    "author": "Rex",
    "files": [
        "plugins",
        "templates",
        "dist"
    ],

This version has typescript define already.

Here is a snapshot of using typescript in ts file, it shows the type of rexUI member of scene.

And also running well in my desktop environment.

Sorry I have no idea why these error messages appeared.

Not at all!
Thank you for your help!

Could Typescript’s version be the problem?
Or Webpack?

Result of google “Property ‘rexUI’ has no initializer and is not definitely assigned in the constructor.”

1 Like

Thank you!

Another question regarding the plugin:
Is there a way to make the sizer object orientation from right-to-left?

I basically want the character image to be on the right of the box, and then the text next to it.
It is an Arabic game, so the alignment has to be RTL.

sizer does not have RTL parameter. Since this request has been proposed several times, I can try to add this RTL parameter.

Edit

rtl parameter added, see this demo, line 26

2 Likes

Thank you so much!!
I really really appreciate it!

Stupid question: how can I update my version of the plugin to have this parameter?
I tried “npm i phaser3-rex-plugins”, but nothing changed.

I have not upgraded npm package yet, will upgrade it this weekend.

1 Like

Hello!

Another stupid question,

How do I close the text box after the text is done?

textBox
    .setInteractive()
    .on(
      "pointerdown",
      function () {
        var icon = textBox.getElement("action").setVisible(false);
        textBox.resetChildVisibleState(icon);
        if (textBox.isTyping) {
          textBox.stop(true);
        } else {
          textBox.typeNextPage();
        }
      },
      textBox
    )
    .on(
      "pageend",
      function () {
        if (textBox.isLastPage) {
          return;
        }

I can see that the code returns without doing anything when it is the last page, but how do I close it?
I tried multiple solutions but couldn’t get it to work.

Thank you

Destroy textbox :

textBox.destroy();

or

textBox.fadeOutDestroy(duration);

Or set to invisible

textBox.setVisible(false)

Thank you @rexrainbow!
I tried “destroy()”; it works but with an error!

Uncaught TypeError: Cannot read properties of undefined (reading 'length')
    at TextPage.get pageCount [as pageCount] (TextPage.js:96)
    at TextPage.get isLastPage [as isLastPage] (TextPage.js:104)
    at TextBox.get isLastPage [as isLastPage] (TextBox.js:80)
    at TextBox.onPageEnd (TextBox.js:108)
    at EventEmitter.emit (phaser.js:1902)
    at TextTyping.emit (EventEmitterMethods.js:49)
    at TextTyping.stop (TextTyping.js:138)
    at TextBox.stop (TextBox.js:66)
    at TextBox.eval (Lab.ts:89)
    at TextBox.emit (phaser.js:1904)
get pageCount @ TextPage.js:96
get isLastPage @ TextPage.js:104
get isLastPage @ TextBox.js:80
onPageEnd @ TextBox.js:108
emit @ phaser.js:1902
emit @ EventEmitterMethods.js:49
stop @ TextTyping.js:138
stop @ TextBox.js:66
eval @ Lab.ts:89
emit @ phaser.js:1904
processDownEvents @ phaser.js:186070
update @ phaser.js:185801
updateInputPlugins @ phaser.js:93085
onMouseDown @ phaser.js:93271
onMouseDown @ phaser.js:94473

The error is caused by this line:

if (textBox.isTyping) {
          textBox.stop(true);
        } 

I kept changing things and testing different approaches, and I discovered that the “next” function/action in the text box doesn’t work when I type in Arabic. It works fine for English text, but with Arabic I just get the first part of the text and then it stops.
I don’t get the “icon” to click for more.

I’m mainly following the code in here.

Can you provide a simplest, runnable test code for this case?