How to Drag Groups in Phaser
I’ve often come across the problem of needing to drag groups in one of my projects. This has stumped me for the last few days with a project I’ve been working on for my day job with Gaiaonline.com. I’m currently building an app that requires me to display a lot of thumbnails in a grid. The grid will need to scroll vertically and the thumbnails have to be clicked.
Now if you’ve ever tried to implement dragging on groups in Phaser, you know if is not an easy task.
The creator of Phaser, Richard Davey explains why there is not a built-in way to drag groups in this post from html5gamedevs forum:
It’s actually a bit more complex than you’d think. A Group doesn’t have any area itself, it’s infinite in all directions – so there’s no detecting if you’ve clicked on it. But you can click on a child easily, however should the drag update all other children, just that child OR the group x/y placement itself? (which in turn would update the other children too, only maybe not in the way you’d expect). And then what about nested groups?
I like the feature, it’s just not as straightforward to implement as it appears on the surface
Brainstorming the solution
So what am I to do? The first solution I came up with is to create a sprite that is the same height and width of the group and to make that draggable. Although that works, the new problem that presents itself is that you can not click through the sprite to get to the individual thumbnails. This is a vital part of the project. So now I know I need a better solution. This is what I know so far:
My Requirements:
- Drag groups
- Leave children clickable
What I know so far:
- A sprite that is clickable will block mouse events to another sprite.
- Groups can not detect mouse events.
- Groups can not be dragged by using Phaser’s built in dragging.
- The game.input is not blocked by anything.
That last point has me thinking what a drag is:
- A mouse down event
- Checking if the mouse is within the grid’s bounds
- Checking the moving of the mouse and update the grid as needed.
Although I’m using the term mouse here, it can also be a touch event.
My solution and the code
Here is a solution I am using in the app now. This example uses Javascript classes.
If you are not familiar with Javascript Classes, please see my post here.
For the dragging, I am using a class called DragBox that also extends groups. I am not adding children here but am extending group to be able to use the built-in update function. The object needing to be dragged is passed to the instance of DragBox as the only parameter. I’m going to be using a grid.
class DragBox extends Phaser.Group { constructor(dragObj) { super(game); this.dragObj = dragObj; game.input.onDown.add(this.onDown, this); } onDown() { //set canDrag to false this.canDrag = false; //check if the mouse is in bounds //since my dragObject is the full width of the game //I only need to check the y position //The top of the drag object is the y position //and the bottom is the top plus the height // if (game.input.y > this.dragObj.y && game.input.y < (this.dragObj.y + this.dragObj.height)) { //set the canDrag to true; this.canDrag = true; //get the start position of the dragObject //so we may compare it to the current position //when dragging this.startY = this.dragObj.y; //the offset is how far down on the grid the //mouse is when the user started dragging //without this line the grid will jump when //the drag starts this.offSet = this.dragObj.y - game.input.y; } } update() { if (game.input.mousePointer.isDown) { if (this.canDrag == true) { //calculate the difference between the startY //and the current mouse y position //and add in the offSet var diff = game.input.y - this.startY + this.offSet; //update the start position //by adding the difference to the start position this.dragObj.y = this.startY + diff; } } } }
As you can see most of the code is comments. There are only about 23 lines of code without the notes.
class DragBox extends Phaser.Group { constructor(dragObj) { super(game); this.dragObj = dragObj; game.input.onDown.add(this.onDown, this); } onDown() { this.canDrag = false; if (game.input.y > this.dragObj.y && game.input.y < (this.dragObj.y + this.dragObj.height)) { this.canDrag = true; this.startY = this.dragObj.y; this.offSet = this.dragObj.y - game.input.y; } } update() { if (game.input.mousePointer.isDown) { if (this.canDrag == true) { var diff = game.input.y - this.startY + this.offSet; this.dragObj.y = this.startY + diff; } } } }
The Easy way
All you need to do to drag a group yourself is to copy the code class directly above and include it in your project.
Then use this code to implement just after you create your object.
var dragBox = new DragBox(objectYouWantToDrag);
That’s it!
This will only allow you to drag the object vertically but the code can be adapted to allow for horizontal was well.
Here is an example I of what I am doing:
First I made a grid class that simply extends a Phaser group. This grid class is for child elements of the same size. It takes a parameter of ‘cols’ for the number of columns.
class Grid extends Phaser.Group { constructor(cols) { super(game); this.cols = cols; } arrange() { var xx = 0; var yy = 0; this.children.forEach(function(item) { //place the item on a column with a 10 percent buffer item.x = item.width * xx * 1.1; //place the item on a row with a 10 percent buffer item.y = item.height * yy * 1.1; xx++; if (xx ==this.cols) { xx = 0; yy++; } }.bind(this)); } center() { this.x = game.width / 2-this.width/2; this.y = game.height / 2-this.height/2; } }
For the thumbnails, I am using a class called Box. This is simply a class that is extending group that contains an image with a mouse listener attached.
class Box extends Phaser.Group { constructor() { super(game); var thumb=this.create(0,0,"box"); thumb.anchor.set(0.5,0.5); thumb.inputEnabled=true; thumb.events.onInputDown.add(this.clicked); } clicked() { console.log("click"); } }
And this is my stateMain.js code
var StateMain = { preload: function() { game.load.image("box", "images/box.png"); }, create: function() { //make a grid // this.grid = new Grid(5); //make 30 boxes and add to the grid // for (var i = 0; i < 30; i++) { var box = new Box(); this.grid.add(box); } //arrange the child objects into a grid // this.grid.arrange(); //center on stage // this.grid.center(); //make a dragBox and pass the grid to it // var dragBox = new DragBox(this.grid); } }
And here is the drag groups code in action. If you open the dev console, you can also see that the squares are still clickable. To make sure squares are not accidentally clicked during drag I am going to implement a function that checks the time between mouse down and mouse up. If it is quick then it will be a click otherwise, I assume the user is dragging.