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.