Search code examples
javascriptevent-handlingdragphaser-framework

Phaser 3: Check if draggable Object is over interactive area of a Sprite


What I want to achieve is to be able to drag a Sprite, and when it moves over the interactive area of another Sprite, it emits particles.

However the Pointermove event doesn't get called if I am dragging a different sprite (likely since my pointer is over the draggable sprite, not the interactive sprite.) I tried adding an If-Condition to my drag event instead, which tests if my draggable object is over the interactive Sprite, which sort of works, but it reacts over any area of the sprite, as opposed to exclusively the interactive area. This is important because my Sprite's interactive area uses pixel perfect.

Is there any way to achieve the result I am looking for?

My try with Pointermove:

gameState.chara.setInteractive({cursor: "pointer", pixelPerfect: true});

gameState.chara.on('pointermove', function(activePointer){ 
    if (activePointer.isDown && gameState.clean_cursor) {
        switch(gameState.clean_cursor.frame.name.split('_')[0]){
        case "sponge":
            gameState.bubble.emitParticle(1, activePointer.x, activePointer.y);
            break;
        case "showerhead":
            gameState.splash.emitParticle(1, activePointer.x-50, activePointer.y+50);
            break;
        }
    }
});

this.input.on('drag', (activePointer, gameObject, dragX, dragY) => {
    switch(gameObject.frame.name.split('_')[0]){
    case "sponge":
        gameState.clean_cursor.play('sponge_cursor', true);
        break;
    case "showerhead":
        gameState.clean_cursor.play('shower_cursor', true);
        break;
    }
        
    gameObject.x = dragX;
    gameObject.y = dragY;
});
    
this.input.on('dragend', function(pointer, gameObject) {
    gameObject.destroy();
    gameState.clean_cursor = false;
});

(Pointermove Event stops reacting once I drag the other sprite)

My attempt with Drag only:

gameState.chara.setInteractive({cursor: "pointer", pixelPerfect: true});

this.input.on('drag', (activePointer, gameObject, dragX, dragY) => {
    if(gameState.chara.getBounds().contains(dragX, dragY)) {
        switch(gameState.clean_cursor.frame.name.split('_')[0]){
        case "sponge":
            gameState.bubble.emitParticle(1, activePointer.x, activePointer.y);
            break;
        case "showerhead":
            gameState.splash.emitParticle(1, activePointer.x-50, activePointer.y+50);
            break;
        }
    }

    switch(gameObject.frame.name.split('_')[0]){
    case "sponge":
        gameState.clean_cursor.play('sponge_cursor', true);
        break;
    case "showerhead":
        gameState.clean_cursor.play('shower_cursor', true);
        break;
    }
        
    gameObject.x = dragX;
    gameObject.y = dragY;
});
    
this.input.on('dragend', function(pointer, gameObject) {
    gameObject.destroy();
});

(Works, but over entire sprite, as opposed to the Pixel Perfect interactive Area only)


Solution

  • With the solution how to dragg the button/image https://stackoverflow.com/a/71310078/1679286

    the solution, for this question should be easy.

    • Just check if the sprite overlaps with the boundaries. (is not pixelperfect, because it matches boxes) here the documentation

    if you need pixel-perfect, this would be more difficult.

    Here the code, for the "simple" solution:
    (the code needs some tweaking)

    let Example = {
        preload () {
            this.load.spritesheet('brawler', 'https://labs.phaser.io/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
                this.load.atlas('flares', 'https://labs.phaser.io/assets/particles/flares.png', 'https://labs.phaser.io/assets/particles/flares.json');
        },
        create () {
        
            this._particles = this.add.particles('flares');
    
            this._emitter = this._particles.createEmitter({
                frame: 'blue',
                lifespan: 2000,
                speed: { min: 50, max: 400 },
                angle: 330,
                gravityY: 300,
                scale: { start: 0.4, end: 0 },
                quantity: 2,
                blendMode: 'ADD',
                on: false
            });
            
            let buttonPos = { x:20, y:20};
            let buttonPlaceholder = this.add.image(buttonPos.x, buttonPos.y, 'brawler', ).setOrigin(0.5);
            let button = this.add.image(buttonPos.x, buttonPos.y, 'brawler', ).setOrigin(0.5);
            
            let interact = this.add.image(200, 100, 'brawler', 3).setOrigin(0.5);
            
            interact.setDepth(-1); 
    
            button.setInteractive({ useHandCursor: true });
            this.input.setDraggable(button);
            
            this.input.on('drag', function(pointer, gameObject, dragX, dragY) {
                gameObject.x = dragX;
                gameObject.y = dragY;
                if(Phaser.Geom.Intersects.RectangleToRectangle(gameObject.getBounds(), interact.getBounds() )){
                    this._emitter.start()
                    this._particles.x = interact.x;
                    this._particles.y = interact.y;
                } else {
                  if (this._emitter.active){
                       this._emitter.stop();
                  }
                }
            }, this);
            
            this.input.on('dragend', function(pointer, gameObject) {
                gameObject.x = buttonPos.x;
                gameObject.y = buttonPos.y;
              this._emitter.stop();
            }, this);
        }
    }
    
    const config = {
        type: Phaser.AUTO,
        width: 400,
        height: 200,
        scene: [ Example ]
    };
    
    const game = new Phaser.Game(config);
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js"></script>

    Extra info, to improve: You could check overlap with CircleToCircle, and keeping the radius abit smaller. (Or other intersection functions, that fit your needs better https://photonstorm.github.io/phaser3-docs/Phaser.Geom.Intersects.html)

    A pixel perfect version, could be done like this:
    (just found this property this.input.topOnly, docs)
    If set to false this property allows the input events to be passed to other object, that are under the top most object.

    let Example = {
        preload () {
            this.load.spritesheet('brawler', 'https://labs.phaser.io/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
                this.load.atlas('flares', 'https://labs.phaser.io/assets/particles/flares.png', 'https://labs.phaser.io/assets/particles/flares.json');
        },
        create () {
        
            this.input.topOnly = false;
    
            let isDragging = false;
            
            this._particles = this.add.particles('flares');
    
            this._emitter = this._particles.createEmitter({
                frame: 'blue',
                lifespan: 2000,
                speed: { min: 400, max: 600 },
                angle: 330,
                gravityY: 300,
                scale: { start: 0.4, end: 0 },
                quantity: 2,
                blendMode: 'ADD',
                on: false
            });
            
            let buttonPos = { x:20, y:20};
            let buttonPlaceholder = this.add.image(buttonPos.x, buttonPos.y, 'brawler', )
                 .setOrigin(0.5);
                 
            let button = this.add.image(buttonPos.x, buttonPos.y, 'brawler', )
                 .setOrigin(0.5);
            
            let interact = this.add.image(200, 100, 'brawler', 3)
                 .setOrigin(0.5);
    
            interact
             .setInteractive(this.input.makePixelPerfect());
    
            interact.on('pointerover', function(pointer){
                if(isDragging){
                    this._particles.x = interact.x;
                    this._particles.y = interact.y;
                    this._emitter.start();
                }
            }, this);
            
            interact.on('pointerout', function(pointer){
                this._emitter.stop();
            }, this);
            
            interact.setDepth(-1); 
    
            button.setInteractive({ useHandCursor: true });
    
            this.input.setDraggable(button);
            
            this.input.on('drag', function(pointer, gameObject, dragX, dragY) {
                isDragging = true;
                gameObject.x = dragX;
                gameObject.y = dragY;
    
            }, this);
            
            this.input.on('dragend', function(pointer, gameObject) {
                gameObject.x = buttonPos.x;
                gameObject.y = buttonPos.y;
                this._emitter.stop();
                isDragging = false;
            }, this);
        }
    }
    
    const config = {
        type: Phaser.AUTO,
        width: 400,
        height: 200,
        scene: [ Example ]
    };
    
    const game = new Phaser.Game(config);
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js"></script>