Example of a group of images being draggable as one unit

Hello

I’ve looked over a lot of the lab examples and can’t seem to find an example of a group of things being draggable as one unit.

Example: I have an HP bar, which is a dynamic fillRect to fill it a certain ways depending on how many HP the player has, a border addimage graphic which is static that sits on top of the HP bar for asthetics, and a textfield via .add.text() that is dynamic and sits on top of the fillRect displaying the numerical value of the HP that sits on top of the fillRect call. All three of these are considered one UI element which I wish that when dragged, would drag all three together so the user could change the position of this element as a whole, rather than the elements individually.

Cheers.

1 Like

I do not know if it works with a group, but it works great with a custom container.

class DraggableContainer extends Phaser.GameObjects.Container {
  private _dragX: number
  private _dragY: number

  constructor(
    scene: Phaser.Scene,
    x: number,
    y: number,
    width: number,
    height: number,
    children: Phaser.GameObjects.GameObject[] | undefined
  ) {
    super(scene, x, y, children)

    scene.add.existing(this)

    this.setSize(width, height)
      .setInteractive()
      .on('drag', (pointer: Phaser.Input.Pointer) => {
        this.setX(pointer.x - this._dragX + width / 2)
        this.setY(pointer.y - this._dragY + height / 2)
      })
      .on('pointerdown', (pointer: any, x: number, y: number) => {
        this._dragX = x
        this._dragY = y
      })

    scene.input.setDraggable(this, true)
  }
}

export default class MainScene extends Phaser.Scene {
  constructor() {
    super({ key: 'MainScene' })
  }

  create() {
    let rect1 = this.add.rectangle(0, 0, 120, 40, 0xff00ff)
    let rect2 = this.add.rectangle(0, 0, 100, 30, 0xffff00)
    let text = this.add.text(0, 0, 'some-text', { color: 'black' }).setOrigin(0.5)
    let container = new DraggableContainer(this, 250, 250, 120, 40, [rect1, rect2, text])
  }
}

Yeah I tried with the native container, but it wasn’t allowing me to use draggable upon it, is a custom class the only way around this?

I do not know if there is a built-in way for doing this. But since a custom class is more flexible anyways, I would use this approach.

You can use a group (or even an array of game objects) and pass to IncXY.

2 Likes

Tried fiddling with your stuff @yannick but nothing is rendering to my screen :frowning:

Code I am working with (with your class used, and my scene I made as a ui channel to the main game scene is getting the draw() calls including the fillrects, but nothing is rendering to my screen unfortunately):

class DraggableContainer extends Phaser.GameObjects.Container {  
    constructor(scene, x, y, width, height, children){
      super(scene, x, y, children)

      scene.add.existing(this)

      this.setSize(width, height)
        .setInteractive()
        .on('drag', p => {
           this.setX(p.x - this._dragX + width / 2)
           this.setY(p.y - this._dragY + height / 2)
         })
        .on('pointerdown', (p, x, y) => {
           this._dragX = x
           this._dragY = y
        })

      scene.input.setDraggable(this, true)
   }
}


class Player_Resources_Scene extends Phaser.Scene {
    constructor(args){
        super({key: "Player_Resources_Scene", active: true});
        this.width = 200;
        this.height = 20;
        this.elements = 3;
    }
    creategraphic(){
        const {width, height} = this.sys.game.canvas;
        this.hpbar = this.add.graphics()
        this.hpbar.fillStyle(0xff0000,1);
        this.endbar = this.add.graphics()
        this.endbar.fillStyle(0x00ff00,1);
        this.manabar = this.add.graphics()
        this.manabar.fillStyle(0x0000ff,1);
        this.container = new DraggableContainer(
            this, 
            width/2-this.width/2, 
            height-this.height*this.elements, 
            this.width, 
            this.height*this.elements, 
            [this.hpbar,this.endbar,this.manabar]
       )
    }
   draw(data){
        const {currenthp, maxhp, currentmana, maxmana, currentend, maxend} = data;
        const hpmod = currenthp / maxhp;
        const manamod = currentmana / maxmana;
        const endmod = currentend / maxend;
        const {width, height} = this.sys.game.canvas;
        this.hpbar.fillRect(width/2-this.width/2, height-this.height*this.elements, this.width*hpmod, height)
        this.endbar.fillRect(width/2-this.width/2, height-this.height*(this.elements-1), this.width*hpmod, height)
        this.manabar.fillRect(width/2-this.width/2, height-this.height*(this.elements-2), this.width*hpmod, height)
    
   }
   create(){
       this.creategraphic();
       this.scene.get('game').events.on('updateresources', data=>{
           this.draw(data);
       })
   }
}

I guess there is something strange.
I will have a look, but next time I will be on my computer will only be tomorrow evening.

Just now figured it out, when I’m rendering fillRect i need to pass relative coordinates inside the container. I was rendering waaaaay far outside it’s bounds. If I pass

this.hpbar.fillRect(0, 0, this.width*hpmod, this.height)

it works :slight_smile:
cheers

Oooh this is it! All objects have to be relative to the container.

Glad you solved it :smiley:

Having one other small problem though and it has to deal with relative units I believe.

Draggable event handler is only occuring when dragging in the upper left bounds of the box for some reason. and not the entire body of the container, any idea why?

Here I modified your example to show what I mean (the yellow square is no longer draggable, even though the container should be encapsulating it?)

class DraggableContainer extends Phaser.GameObjects.Container {

  constructor(
    scene,
    x,
    y,
    width,
    height,
    children) {
    super(scene, x, y, children)

    scene.add.existing(this)

    this.setSize(width, height)
      .setInteractive()
      .on('drag', (pointer) => {
        this.setX(pointer.x - this._dragX + width / 2)
        this.setY(pointer.y - this._dragY + height / 2)
      })
      .on('pointerdown', (pointer, x, y) => {
        this._dragX = x
        this._dragY = y
      })

    scene.input.setDraggable(this, true)
  }
}

class MainScene extends Phaser.Scene {
  constructor() {
    super({ key: 'MainScene' })
  }

  create() {
    let rect1 = this.add.rectangle(0, 0, 120, 40, 0xff00ff)
    let rect2 = this.add.rectangle(0, 40, 120, 40, 0xffff00)
    let text = this.add.text(0, 0, 'some-text', { color: 'black' }).setOrigin(.5)
    let container = new DraggableContainer(this, 250, 250, 120, 80, [rect1, rect2, text])
  }
}

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: [
        new MainScene(),
    ]
};

var game = new Phaser.Game(config);

It looks like the default origin of the container is set to 0.5

console.log(this.originX)
console.log(this.originY)

Unfortunately this.setOrigin() does not work. Either you find a way to change this, or you calculate the offset manually.

Or try to change the origin of the rects.

1 Like

figured out solution, and yes you were correct. i had to modify your class.

this is working:

class Draggable_Container extends Phaser.GameObjects.Container {  
    constructor(scene, x, y, width, height, children){
      super(scene, x, y, children)
      this.x = x;
      this.y = y;
      this.width = width;
      this.height = height;
  
      scene.add.existing(this)
      this.setSize(width, height, false)
      this.setInteractive()
        .on('drag', (p, x, y) => {
          this.setX(p.x - this._dragX + width / 2)
          this.setY(p.y - this._dragY + height / 2)
        })
        .on('pointerdown', (p, x, y) => {
          this._dragX = x
          this._dragY = y
        })
      
      this.input.hitArea.x += width/2;
      this.input.hitArea.y += height/2;
  
      scene.input.setDraggable(this, true)
    }
}
1 Like

Nice. This looks like a good and clean solution :slight_smile: