How to restrict the drag movement of a sprite to both X & Y axis in Phaser
Phaser has a built-in function (i.e. setDragLock
) that restricts a sprite’s drag movement only on a given axis (i.e. either horizontal or vertical). There are cases though that you want to allow a sprite to move both horizontally and vertically, but not allow movement to any other directions.
I ran into a situation like that a few days ago, while building -just for fun- a match-3 type game (like Bejeweled and Candy Crush Saga). Although I ended up making the tiles clickable instead of draggable, I managed to find a workaround that would restrict movement to both axis.
Before we dive into the code, have a look at this demo to understand what we are about to build. All four squares are draggable; square 1 can be dragged freely in all directions; square 2’s movement is restricted to the horizontal axis; square 3’s to the vertical axis; and finally square 4’s to both axis.
The HTML/CSS part is very simple, therefore I’ll omit it; you can always have a look at the source code. Since what we’re going to build is a small proof of concept, we will use a very simple Phaser setup without different states, etc. Let’s start by initialising Phaser and the basic functions we’ll use.
window.onload = function() { //Initialise Phaser var game = new Phaser.Game(800, 600, Phaser.AUTO, 'gameContainer', { init: init, preload: preload, create: create }); //Set some configuration values function init() { this.input.maxPointers = 1; this.stage.disableVisibilityChange = true; this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; this.scale.maxWidth = 800; this.scale.maxHeight = 600; this.scale.windowConstraints.bottom = "visual"; this.scale.pageAlignHorizontally = true; } //Preload assets function preload() { this.load.image('square_1', 'images/square-1.jpg'); this.load.image('square_2', 'images/square-2.jpg'); this.load.image('square_3', 'images/square-3.jpg'); this.load.image('square_4', 'images/square-4.jpg'); } //Add assets to the game function create() {} };
In the init
function, we set some configuration values for the game, i.e. its maximum height and width, its scale mode, etc., and in the preload
function we load the assets we’ll use in the demo. In the create
function we’ll add the assets in the game, but we’ll get to that towards the end of the tutorial.
To make things easier, we’ll include all the logic for a square in a class-like function called Square
. Note that functions/variables which are declared using the var
keyword in front of them are only accessible within the Square
function.
function Square(newOptions) { var options = {}; var defaultOptions = { phaser: null, //an instance of Phaser assetKey: '', //the asset's key x: 0, //x position y: 0, //y position dragType: '', //type of drag movement allowed bounds: null //an instance of a Phaser Rectangle }; var extend = function(out) { //source: http://youmightnotneedjquery.com/#extend }; options = extend({}, defaultOptions, newOptions); var square = null; var initCursorPositionX = null; var initCursorPositionY = null; var lockedAxis = false; this.init = function() {} var dragUpdate = function() {} var dragStop = function() {} }
We have a set of default options stored in the defaultOptions
object. We combine those with the newOptions
object that we pass when we create a new Square
object. To merge them giving “priority” to any values stored in newOptions
, we use the extend
function. Its functionality is identical to jQuery’s extend
function and was taken from here, just to avoid loading jQuery solely for one function.
this.init
function will be called from Phaser’s create
function (thus the declaration using this
) to initialise the square. dragUpdate
and dragStop
will be called based on the square’s drag events. The four variables will be used inside those functions (we’ll see how while we discuss them). Let’s start with this.init
:
this.init = function() { //create the sprite square = options.phaser.add.sprite(options.x, options.y, options.assetKey); //enable input square.inputEnabled = true; //change the cursor to the hand version on hover square.input.useHandCursor = true; //enable drag square.input.enableDrag(false, true); //restrict dragging within the Phaser rectangle (in our case it covers the whole visible screen) square.input.boundsRect = options.bounds; //restrict dragging movement based on selected drag type switch (options.dragType) { case 'horizontal': square.input.setDragLock(true, false); break; case 'vertical': square.input.setDragLock(false, true); break; case 'both': square.events.onDragUpdate.add(dragUpdate, this); square.events.onDragStop.add(dragStop, this); break; } }
Most of the function is pretty basic; we create the sprite, enable dragging, and restrict its dragging bounds. That part is common for all of our squares. Their difference lies in the value of options.dragType
.
When there is no dragType
selected (that will be the case for square 1), no restrictions will be placed and the square can be dragged freely within the bounds; note that we don’t have a default
case in our switch
statement. In horizontal
and vertical
cases, we utilise Phaser’s build-in setDragLock
function to restrict movement to either axis.
To be able to restrict the dragging movement to both axis (based on the position we started dragging from), we apply two events on our sprite; onDragUpdate
which will be constantly fired while the sprite is being dragged, and onDragStop
which will be fired once when the dragging stops.
The idea is that we want to allow the player to move the sprite to any direction for a few pixels, just enough to allow us to determine the axis she wants to move the sprite on. As soon as we do, we will lock the movement to that axis using Phaser’s setDragLock
function.
var dragUpdate = function() { //if we already locked to either axis, then exit if(lockedAxis) { return false; } //if we don't have a record of the initial cursor's position when it started dragging, grab one and exit if(initCursorPositionX === null || initCursorPositionY === null) { initCursorPositionX = options.phaser.input.x; initCursorPositionY = options.phaser.input.y; return false; } //calculate the absolute difference between the initial cursor position and the current one for both axis var differenceX = Math.abs(initCursorPositionX - options.phaser.input.x); var differenceY = Math.abs(initCursorPositionY - options.phaser.input.y); //allow at least one of the axis to move 5 pixels before restricting movement to either if(differenceX < 5 && differenceY < 5) { return false; } //if the cursor moved a greater distance in X-axis than in Y-axis, then restrict dragging horizontally if(differenceX > differenceY) { square.y = options.y; square.input.setDragLock(true, false); lockedAxis = true; return false; } //alternatively, restrict dragging vertically square.x = options.x; square.input.setDragLock(false, true); lockedAxis = true; }; //reset values and store new X & Y coordinates var dragStop = function() { initCursorPositionX = null; initCursorPositionY = null; options.x = square.x; options.y = square.y; lockedAxis = false; };
As the dragUpdate
function will be constantly called during the dragging, we want to keep some values outside of it; this is where lockedAxis
, initCursorPositionX
, and initCursorPositionY
variables are used. The first thing to do in the function is to determine if we already locked the movement to either axis. If that’s the case, then there is nothing else to do, and we exit. If we ​haven’t locked the movement, we check if we have stored the initial X & Y coordinates of the cursor. If we haven’t stored the coordinates already, we store them and exit.
As soon as we have that information, we calculate the absolute difference between the initial cursor position and the current one for both axis. We wait until one of those values is at least 5 pixels, and then we determine which one is greater than the other. If the cursor has covered more distance on the X-axis, we restrict dragging horizontally, alternatively we restrict dragging vertically.
Finally, in the dragStop
function we reset the cursor position and lockedAxis
values and we store the new X & Y coordinates of the sprite so we can be ready for the next drag action. The only thing left to do is to create and initialise our squares. Let’s do that in our create
function:
function create() { //this is used to restrict dragging within the visible screen var bounds = new Phaser.Rectangle(0, 0, 800, 600); var square1 = new Square({ phaser: this, assetKey: 'square_1', x: 96, y: 56, bounds: bounds }); var square2 = new Square({ phaser: this, assetKey: 'square_2', x: 272, y: 192, dragType: 'horizontal', bounds: bounds }); var square3 = new Square({ phaser: this, assetKey: 'square_3', x: 448, y: 328, dragType: 'vertical', bounds: bounds }); var square4 = new Square({ phaser: this, assetKey: 'square_4', x: 624, y: 464, dragType: 'both', bounds: bounds }); square1.init(); square2.init(); square3.init(); square4.init(); }
We’re done! Here’s what we’ve just built. You can download the source code of this tutorial from this Gist.