What are the best practices for Phaser and External DOM - JavaScript Interactions

Hi,
I want to interact with the DOM and JavaScript functions outside of the Phaser game. The following link was great - [Is there a Phaser event for when you type in dom inputs?](// Is there a Phaser event for when you type in dom inputs?) but it unfortunately spawned some more questions when I tried some examples.

  1. How do you give a scene a name?
  2. The add.dom() method did not work when the code was inside the Scene class (it did work if phaser was just setup in a set of script tags). Why is that?
  3. What is the preferred method to call Scene functionality from an external javascript method - should you use events or just invoke the method?

index.html

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser-arcade-physics.min.js"></script>
    <script src="phaser.js"></script>
</head>
<body>

    <div id='phaser-example'></div>
    <input type="hidden" id="domArea" />
    <div id='divArea'>
        <br/>
        <h1>DOM Elements that need to interact with the Phaser Game</h1>
        <button onclick="moveGameLogo()">MOVE LOGO BY FINDING CHILD ELEMENT</button>
        <br/>
        <button onclick="moveGameLogoEvents()">MOVE LOGO USING EVENTS</button>
    </div>

    <script>

        function moveGameLogo()
        {
            console.log("moveGameLogo() called");

            // https://phaser.discourse.group/t/is-there-a-phaser-event-for-when-you-type-in-dom-inputs/10333
            var gameElements = window.game.scene.scenes[0].children.list;
            
            for (let i = 0; i < gameElements.length; i++) {
                console.log("Name: " + gameElements[i].name)
                if (gameElements[i].name == "logo") {
                    var logo = gameElements[i];

                    if (logo.x == 210) {
                        logo.x = 400; 
                        logo.y = 320; 
                    }
                    else {
                        logo.x = 210; 
                        logo.y = 540; 
                    }
                    break;
                }
            }

            // https://phaser.discourse.group/t/is-there-a-phaser-event-for-when-you-type-in-dom-inputs/10333
            // Call a method in the phaser.scene.
            var scene = window.game.scene.keys.default; // How do you give a scene a name?
            scene.arbitrarySceneMethod(300,400,"logo");
        }

        function moveGameLogoEvents()
        {
            console.log("moveGameLogoEvents() called");
            var phaserGame = window.game;
            var emitter = phaserGame.scene.scenes[0].emitter; // This requires that emitter be global in the Example Scene class.
            // Move image to bottom right.
            emitter.emit('moveImage', 800, 525, "logo");
        }

        domArea.addEventListener("phaserEvent", function(event) {
            respondToPhaserEvent(event, this);
        });

        function respondToPhaserEvent(event, listeningObject) {
            console.log(event.detail.name + " " + listeningObject.id);
            document.getElementById("divArea").style.backgroundColor = "lightblue"; // Turns the DIV light blue if the logo is clicked.
        }

    </script>

</body>
</html>

phaser.js

class Example extends Phaser.Scene
{

    //  Create our own EventEmitter instance
    emitter = new Phaser.Events.EventEmitter();

    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.css('80s', 'assets/loader-tests/80stypography.css');
        this.load.image('logo', 'assets/sprites/phaser3-logo.png');
    }

    create ()
    {
        // DOM Elements - floating over the top of your game per https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.DOMElement.html
        // Try - https://phaser.io/examples/v3/view/game-objects/dom-element/css-text#
        var h1 = this.add.dom(450, 100, 'h1', null, 'TEXAS');
        h1.setClassName('chrome');
    
        var h2 = this.add.dom(570, 180, 'h2', null, 'Holdem');
        h2.setClassName('dreams');
        h2.setAngle(-15);

        this.add.text(0, 300, "DOM 'Texas Holdem' Text IS NOT SHOWING when extending the Phaser.Scene class", { fontSize: '20px', fontFamily: 'Arial' });

        // Setup the image at the bottom right and make it interactive.
        var logo = this.add.image(750, 540, 'logo').setInteractive();;
        logo.name = "logo";
        logo.once('pointerup', this.handleClick, this);

        //  Set-up an event handler
        this.emitter.on('moveImage', this.handler, this);

        //  Emit it a few times with varying arguments
        this.emitter.emit('moveImage', 210, 50, "logo");  // Send image to top left.
    }

    handler (x, y, name)
    {
        console.log("handler() name=" + name + " x=" + x + " y=" + y);
        var objectToMove = this.children.getByName(name);
        objectToMove.x = x;
        objectToMove.y = y;
    }

    handleClick()
    {
        console.log("handleClick() fired");
        var eventDetail =  {
            detail: {
              name: "PhaserClick"
            }
          };
        
        domArea.dispatchEvent(new CustomEvent("phaserEvent", eventDetail));
    }

    arbitrarySceneMethod(x,y,name)
    {
        console.log("handler() name=" + name + " x=" + x + " y=" + y);
        var objectToMove = this.children.getByName(name);
        objectToMove.x = x;
        objectToMove.y = y;
    }

}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 1000,
    height: 600,
    backgroundColor: '#009B3A',
    scene: [ Example ]
};


var game = new Phaser.Game(config);

Scenes are identified by key (or “default” if no key is given).

super('example');
var exampleScene = game.scene.getScene('example');

For DOM game objects to work you need parent and dom.createContainer.

new Phaser.Game({
  parent: 'phaser-example',
  dom: { createContainer: true }
});
1 Like

There are a lot of ways to do the DOM–scene part and maybe it’s up to personal preference. In addition to those you can

  • Emit an event through game.events and let the scenes subscribe to that
  • Call addEventListener() within the scene itself
1 Like

THANKS so much for this samme! That helps me to understand a lot! I tried what you have suggested and it works perfectly. I like the easy syntax of the following so I will probably use that a lot.

            var exampleScene = window.game.scene.getScene('example');
            exampleScene.arbitrarySceneMethod(200,500,"logo");

However, I can imagine that a lot of the other ways for handling events will be useful depending on your criteria at the time. I especially found the idea of using addEventListener() inside of the Scene interesting.

Thanks again! Code is now as follows:

index.html

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser-arcade-physics.min.js"></script>
    <script src="phaser.js"></script>
</head>
<body>

    <div id='phaser-example'></div>
    <input type="hidden" id="domArea" />
    <div id='divArea'>
        <br/>
        <h1>DOM Elements that need to interact with the Phaser Game</h1>
        <button onclick="moveGameLogo()">MOVE LOGO BY FINDING CHILD ELEMENT</button>
        <br/>
        <button onclick="moveGameLogoEvents()">MOVE LOGO USING EVENTS</button>
        <br/>
        <button id='externalbutton'>LOG EXTERNAL EVENT</button>
    </div>

    <script>

        function moveGameLogo()
        {
            console.log("moveGameLogo() called");

            // https://phaser.discourse.group/t/is-there-a-phaser-event-for-when-you-type-in-dom-inputs/10333
            var gameElements = window.game.scene.scenes[0].children.list;
            
            for (let i = 0; i < gameElements.length; i++) {
                console.log("Name: " + gameElements[i].name)
                if (gameElements[i].name == "logo") {
                    var logo = gameElements[i];

                    if (logo.x == 210) {
                        logo.x = 400; 
                        logo.y = 320; 
                    }
                    else {
                        logo.x = 210; 
                        logo.y = 540; 
                    }
                    break;
                }
            }

            // https://phaser.discourse.group/t/is-there-a-phaser-event-for-when-you-type-in-dom-inputs/10333
            // Call a method in the phaser.scene.
            var scene = window.game.scene.keys.default; 
            if (scene){
                // This works if the scene was NOT named 'example'
                scene.arbitrarySceneMethod(300,400,"logo");
            }
            else {
                // The scene 'example' was specified in the Scene constructor. 
                var exampleScene = window.game.scene.getScene('example');
                exampleScene.arbitrarySceneMethod(200,500,"logo");
            }

        }

        function moveGameLogoEvents()
        {
            console.log("moveGameLogoEvents() called");
            var phaserGame = window.game;
            var emitter = phaserGame.scene.scenes[0].emitter; // This requires that emitter be global in the Example Scene class.
            // Move image to bottom right.
            emitter.emit('moveImage', 800, 525, "logo");
        }

        domArea.addEventListener("phaserEvent", function(event) {
            respondToPhaserEvent(event, this);
        });

        function respondToPhaserEvent(event, listeningObject) {
            console.log(event.detail.name + " " + listeningObject.id);
            document.getElementById("divArea").style.backgroundColor = "lightblue"; // Turns the DIV light blue if the logo is clicked.
        }

        function logExternalEvent()
        {
            var eventDetail =  {
                detail: {
                name: "ExternalButtonClick",
                x: 10,
                y: 10
                }
            };
        
            this.dispatchEvent(new CustomEvent("externalButtonEvent", eventDetail));
        }

    </script>

</body>
</html>

phaser.js

class Example extends Phaser.Scene
{

    //  Create our own EventEmitter instance
    emitter = new Phaser.Events.EventEmitter();

    constructor ()
    {
        super('example');
    }

    preload ()
    {
        this.load.css('80s', 'assets/loader-tests/80stypography.css');
        this.load.image('logo', 'assets/sprites/phaser3-logo.png');
    }

    create ()
    {
        // DOM Elements - floating over the top of your game per https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.DOMElement.html
        // Try - https://phaser.io/examples/v3/view/game-objects/dom-element/css-text#
        var h1 = this.add.dom(450, 100, 'h1', null, 'TEXAS');
        h1.setClassName('chrome');
    
        var h2 = this.add.dom(570, 180, 'h2', null, 'Holdem');
        h2.setClassName('dreams');
        h2.setAngle(-15);

        this.add.text(0, 300, "DOM 'Texas Holdem' Text IS NOT SHOWING when extending the Phaser.Scene class", { fontSize: '20px', fontFamily: 'Arial' });

        // Setup the image at the bottom right and make it interactive.
        var logo = this.add.image(750, 540, 'logo').setInteractive();;
        logo.name = "logo";
        logo.once('pointerup', this.handleClick, this);

        //  Set-up an event handler
        this.emitter.on('moveImage', this.handler, this);

        //  Emit the moveImage event to be handled by 'handler'.
        this.emitter.emit('moveImage', 210, 50, "logo");  // Send image to top left.

        // You do not need your own emitter - the 'events' emitter can be used from the game or from the scene. e.g. See:
        // https://phaser.io/examples/v3/view/events/emit-scene-event

        // Call addEventListener() within the scene itself to respond to EXTERNAL button click.
        var externalbutton = document.getElementById("externalbutton");
        externalbutton.addEventListener("click", this.respondToExternalButtonEvent);

        // Also see https://newdocs.phaser.io/docs/3.52.0/Phaser.Input.Events

    }

    handler (x, y, name)
    {
        console.log("handler() name=" + name + " x=" + x + " y=" + y);
        var objectToMove = this.children.getByName(name);
        objectToMove.x = x;
        objectToMove.y = y;
    }

    handleClick()
    {
        console.log("handleClick() fired");
        var eventDetail =  {
            detail: {
              name: "PhaserClick"
            }
          };
        
        domArea.dispatchEvent(new CustomEvent("phaserEvent", eventDetail));
    }

    arbitrarySceneMethod(x,y,name)
    {
        console.log("handler() name=" + name + " x=" + x + " y=" + y);
        var objectToMove = this.children.getByName(name);
        objectToMove.x = x;
        objectToMove.y = y;
    }

    respondToExternalButtonEvent()
    {
        console.log("respondToExternalButtonEvent() called...");
    }

}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 1000,
    height: 600,
    backgroundColor: '#009B3A',
    dom: { createContainer: true },
    scene: [ Example ]
};


var game = new Phaser.Game(config);



I’ve created a development setup using Quasar, Vue3 and Phaser3 which uses Events and CustomEvents on window to pass game data to Vue3 components and back again. Seems to be working well.

https://bitbucket.org/bkirvin/quasar-phaser/src/master/

you can preview the demo game at