phaser icon indicating copy to clipboard operation
phaser copied to clipboard

removeInteractive() error when called in pointerover event's handler

Open TomorrowToday opened this issue 2 years ago • 2 comments

Version

  • Phaser Version: 3.60.0-beta.18
  • Operating system: MacOS 13.x
  • Browser: Chrome 109

Description

Calling removeInteractive on a game object after a pointover event results in the following error. calling disableInteractive before calling remove, sometimes helps. It might take as many as 10 to 20 events to get the error.

Uncaught TypeError: Cannot read properties of undefined (reading 'cursor')
    at InputManager.resetCursor (3.60.0-beta.18.js:96605:31)
    at InputPlugin.disable (3.60.0-beta.18.js:98115:25)
    at InputPlugin.clear (3.60.0-beta.18.js:98031:14)
    at InputPlugin.preUpdate (3.60.0-beta.18.js:97769:22)
    at EventEmitter.emit (3.60.0-beta.18.js:220:33)
    at Systems.step (3.60.0-beta.18.js:186633:16)
    at SceneManager.update (3.60.0-beta.18.js:183875:21)
    at Game.step (3.60.0-beta.18.js:16986:20)
    at TimeStep.step (3.60.0-beta.18.js:17991:14)
    at step (3.60.0-beta.18.js:30961:19)

Example Test Code

class Bat extends Phaser.GameObjects.Container
{
    constructor (scene, x, y)
    {
        super(scene, x, y);

        const body = scene.add.image(0, 0, 'assets', 'Body02_01');
        const wing1 = scene.add.image(-50, 10, 'assets', 'Wing02_01').setOrigin(1, 0.5);
        const wing2 = scene.add.image(50, 10, 'assets', 'Wing02_02').setOrigin(0, 0.5);

        this.add([ wing2, body, wing1 ]);

        this.body = body;
        this.wing1 = wing1;
        this.wing2 = wing2;

        scene.add.existing(this);

        body.setInteractive(new Phaser.Geom.Circle(170, 170, 100), Phaser.Geom.Circle.Contains);

        body.once('pointerover', () => this.kill());

        this.fly();
    }

    fly ()
    {
        const y = this.y - (Phaser.Math.Between(150, 280));

        this.scene.tweens.add({
            targets: this,
            y,
            ease: 'sine.inout',
            yoyo: true,
            repeat: -1,
            duration: Phaser.Math.Between(900, 1200)
        });

        this.scene.tweens.add({
            targets: this.wing1,
            angle: { start: -20, to: 20 },
            ease: 'sine.inout',
            yoyo: true,
            repeat: -1,
            duration: 200
        });

        this.scene.tweens.add({
            targets: this.wing2,
            angle: { start: 20, to: -20 },
            ease: 'sine.inout',
            yoyo: true,
            repeat: -1,
            duration: 200
        });
    }

    kill ()
    {
        this.body.removeInteractive()
        this.scene.tweens.killTweensOf([ this, this.wing1, this.wing2 ]);

        this.wing1.setAngle(20);
        this.wing2.setAngle(-20);

        this.body.setFrame('Body02_02');

        this.scene.tweens.chain({
            targets: this,
            tweens: [
                {
                    y: '-=100',
                    angle: 270,
                    scale: 0.3,
                    duration: 500
                },
                {
                    angle: 180,
                    y: 800,
                    ease: 'power1',
                    duration: 500
                }
            ],
            onComplete: () => {

                const x = Phaser.Math.Between(100, 700);
                const y = Phaser.Math.Between(300, 500);
                const scale = Phaser.Math.FloatBetween(0.4, 1);

                new Bat(this.scene, x, y).setScale(scale);

                this.destroy();

            }
        });
    }
}

class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.image('bg', 'assets/skies/spooky.png');
        this.load.atlas('assets', 'assets/atlas/tweenparts.png', 'assets/atlas/tweenparts.json');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');

        new Bat(this, 600, 300).setScale(0.4);
        new Bat(this, 180, 400).setScale(0.7);
        new Bat(this, 440, 500);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Additional Information

TomorrowToday avatar Feb 12 '23 02:02 TomorrowToday

Need anything additional from me to reproduce?

TomorrowToday avatar Feb 18 '23 17:02 TomorrowToday

Still having this issue with 3.60.0 and Chrome 112. Here is a less complex example. It might take a couple dozen times to get the error, but usually i'll see it after 3 or 4:

class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
    }

    create ()
    {
        const sprite = this.add.sprite(400, 300, 'eye').setInteractive();

        sprite.on('pointerover', function (event)
        {
            sprite.removeInteractive();
            setTimeout(()=>{
                sprite.setInteractive()
                this.setTint(0xffffff)
            }, 300)
            this.setTint(0xff0000);
        });
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

TomorrowToday avatar Apr 28 '23 15:04 TomorrowToday

Adding {useHandCursor: true} to the setInteractive config with the above example helps visualize the issue.

Thank you for submitting this issue. We have fixed this and the fix has been pushed to the master branch. It will be part of the next release. If you get time to build and test it for yourself we would appreciate that.

rgk avatar Feb 05 '24 23:02 rgk