Display a hint using tween in Phaser

This is next task I planned to implement in Number Spread game I discussed in the previous article series. In a game we have many scenarios when we want to display a hint and I considered many options as to how this can be implemented in Phaser. The options could be either using a tween or animation. For Number Spread it was a simple scenario when I just needed to display a hand which moved in certain direction to give a hint to player what is to be done. A tween for this purpose seemed fine. Look at the complete code for it below.

var TheGame = {
};

TheGame.Params = {
	baseWidth: 1920,
	baseHeight: 1080,
	minPadding: 50,
	iconSize: 364,
	    fieldSize: {
        rows: 6,
        cols: 6
    }
};

TheGame.Boot = function (game) { };

TheGame.Boot.prototype =  {
    init: function () {
        this.scale.scaleMode = Phaser.ScaleManager.RESIZE;
    },
    preload: function () {
        this.load.image("loading", "loadingback.png");
    },
    create: function () {
        this.state.start("Loading");
    }	
};

TheGame.Loading = function (game) {
};

TheGame.Loading.prototype = {
    init: function () {
    },
    preload: function () {
        this.stage.backgroundColor = 0x222222;
        var loadingBar = this.add.sprite(this.world.centerX, this.world.centerY, "loading");
        loadingBar.anchor.setTo(0.5);
        this.load.setPreloadSprite(loadingBar);

        this.load.spritesheet("settings", "settings.png", 364, 364);
		this.load.image("background", "background.png");
	    this.load.image("current", "current.png");
        this.load.image("best", "best.png");
        this.load.image("hand", "hand.png");
        this.load.spritesheet("tiles", "tiles.png", 120, 120);
	
    },
    create: function () {
       this.state.start("TheGame");
    }
};

TheGame.MyGame = function (game) {
};

TheGame.MyGame.prototype = {
    preload: function () {
    },
    create: function () {
		this.background = this.add.image(0, 0, "background");
		this.background.height = this.game.height;
		this.background.width = this.game.width;

        // First add all sprites to the game and then position them after scaling
		this.scoreTile = this.game.add.image(-200, -200, "current");
		this.scoreTile.anchor.setTo(0.5);
		this.bestScoreTile = this.game.add.image(-200, -200 , "best");
		this.bestScoreTile.anchor.setTo(0.5);
		this.soundButton = this.game.add.button(-200, -200, "settings", this.toggleSound, this);
		this.soundButton.anchor.setTo(0.5);
		this.soundButton.frame = 2;
		this.soundButton.clicked = false;
		this.newButton = this.game.add.button(-200 , -200, "settings", this.toggleSound, this);
		this.newButton.anchor.setTo(0.5);
		this.newButton.frame = 5;
		this.newButton.clicked = false;
		this.helpButton = this.game.add.button(-200 , -200, "settings", this.toggleSound, this);
		this.helpButton.anchor.setTo(0.5);
		this.helpButton.frame = 4;
		this.helpButton.clicked = false;
		
        // Add all grid tiles to the game
        this.initTiles();
                
        // Position the controls using available width and height in the game
		this.positionControls(this.game.width, this.game.height);
		
		this.showHint();
    },
	scaleSprite: function (sprite, availableSpaceWidth, availableSpaceHeight, padding, scaleMultiplier, isFullScale) {
		var scale = this.getSpriteScale(sprite._frame.width, sprite._frame.height, availableSpaceWidth, availableSpaceHeight, padding, isFullScale);
		sprite.scale.x = scale * scaleMultiplier;
		sprite.scale.y = scale * scaleMultiplier;
	},
	getSpriteScale: function (spriteWidth, spriteHeight, availableSpaceWidth, availableSpaceHeight, minPadding, isFullScale) {
		var ratio = 1;
		var currentDevicePixelRatio = window.devicePixelRatio;
		// Sprite needs to fit in either width or height
		var widthRatio = (spriteWidth * currentDevicePixelRatio + 2 * minPadding) / availableSpaceWidth;
		var heightRatio = (spriteHeight * currentDevicePixelRatio + 2 * minPadding) / availableSpaceHeight;
		if(widthRatio > 1 || heightRatio > 1){
			ratio = 1 / Math.max(widthRatio, heightRatio);
		} else {
			if(isFullScale)
				ratio = 1 / Math.min(widthRatio, heightRatio);
		}
		return ratio * currentDevicePixelRatio;	
	},
	resize: function (width, height) {
		this.background.height = height;
		this.background.width = width;
		this.positionControls(width, height);
	},
	positionControls: function (width, height) {
		// We would consider landscape orientation if height to width ratio is less than 1.3.
		// Pick any value you like or a preference for landscape or portrait orientation
        var isLandscape = height / width  < 1.3 ? true: false;
		if(isLandscape){
			var availableGridSpace = Math.min(width * 2 / 3, height);
			this.calculatedTileSize = (availableGridSpace * 0.9) / 6;
			this.verticalMargin = (height - 6 * this.calculatedTileSize) / 2;
			this.horizontalMargin = (width * 2 / 3 - 6 * this.calculatedTileSize) / 2;
			
			this.tileGroup.x = this.horizontalMargin;
			this.tileGroup.y = this.verticalMargin;

			this.scaleSprite(this.scoreTile, width / 3, height / 3, 50, 1);
			this.scoreTile.x = width * 2 / 3 + width / 6;
			this.scoreTile.y = this.verticalMargin + this.scoreTile.height / 2;

			this.scaleSprite(this.bestScoreTile, width / 3, height / 3, 50, 1);			
			this.bestScoreTile.x = width * 2 / 3 + width / 6;
			this.bestScoreTile.y = this.verticalMargin + this.scoreTile.height + 50;

			var calculatedSettingsVerticalSpace = height - 2 * this.verticalMargin - 2 * 50 - this.scoreTile.height - this.bestScoreTile.height;

			this.scaleSprite(this.soundButton, width / 3, calculatedSettingsVerticalSpace / 3, 20, 1);
			this.soundButton.x = width * 2 / 3 + width / 6;
			this.soundButton.y = height - this.verticalMargin - this.soundButton.height / 2;

			this.scaleSprite(this.newButton, width / 3, calculatedSettingsVerticalSpace / 3, 20, 1);
			this.newButton.x = width * 2 / 3 + width / 6;
			this.newButton.y = height - this.verticalMargin - this.soundButton.height - 50;
		
			this.scaleSprite(this.helpButton, width / 3, calculatedSettingsVerticalSpace / 3, 20, 1);
			this.helpButton.x = width * 2 / 3 + width / 6;
			this.helpButton.y = height - this.verticalMargin - this.soundButton.height - this.newButton.height/ 2 - 100;

			for (var i = 0; i < TheGame.Params.fieldSize.rows; i++) {
				for (var j = 0; j < TheGame.Params.fieldSize.cols; j++) {
					this.scaleSprite(this.tilesArray[i][j], this.calculatedTileSize, this.calculatedTileSize, 0, 1, true);
					var tileX = j * this.calculatedTileSize + this.calculatedTileSize / 2;
					var tileY = i * this.calculatedTileSize + this.calculatedTileSize / 2;
					this.tilesArray[i][j].x = tileX;
					this.tilesArray[i][j].y = tileY;
				}
			}

		} else {
			
			var availableGridSpace = this.game.width;
			this.calculatedTileSize = (availableGridSpace * 0.9) / 6;
			this.verticalMargin = (this.game.height - 6 * this.calculatedTileSize) / 2;
			this.horizontalMargin = (this.game.width - 6 * this.calculatedTileSize) / 2;
			
			this.tileGroup.x = this.horizontalMargin;
			this.tileGroup.y = this.verticalMargin;
	
			this.scaleSprite(this.scoreTile, width / 2, this.verticalMargin, 20, 1);
			this.scoreTile.x = width / 4;
			this.scoreTile.y = this.verticalMargin / 2;

			this.scaleSprite(this.bestScoreTile, width / 2, this.verticalMargin, 20, 1);			
			this.bestScoreTile.x = width * 3 / 4;
			this.bestScoreTile.y = this.verticalMargin / 2;

			this.scaleSprite(this.soundButton, width / 3, this.verticalMargin, 50, 0.75);
			this.soundButton.x = this.world.centerX;
			this.soundButton.y = height - this.verticalMargin / 2;

			this.scaleSprite(this.newButton, width / 3, this.verticalMargin, 50, 0.75);
			this.newButton.x = this.world.centerX - this.soundButton.width;
			this.newButton.y = height - this.verticalMargin / 2;
			
			this.scaleSprite(this.helpButton, width / 3, this.verticalMargin, 50, 0.75);
			this.helpButton.x = this.world.centerX + this.soundButton.width;
			this.helpButton.y = height - this.verticalMargin / 2;

			for (var i = 0; i < TheGame.Params.fieldSize.rows; i++) {
				for (var j = 0; j < TheGame.Params.fieldSize.cols; j++) {
					this.scaleSprite(this.tilesArray[i][j], this.calculatedTileSize, this.calculatedTileSize, 0, 1, true);
					var tileX = j * this.calculatedTileSize + this.calculatedTileSize / 2;
					var tileY = i * this.calculatedTileSize + this.calculatedTileSize / 2;
					this.tilesArray[i][j].x = tileX;
					this.tilesArray[i][j].y = tileY;
				}
			}
		}	
		if(this.hintTile && this.hand){
			this.hand.destroy();
			this.showHint();
		}		
	},
    initTiles: function () {

		this.tilesArray = [];
        this.tileGroup = this.game.add.group();
		this.tileGroup.x = this.horizontalMargin;
		this.tileGroup.y = this.verticalMargin;

        for (var i = 0; i < TheGame.Params.fieldSize.rows; i++) {
            this.tilesArray[i] = [];
            for (var j = 0; j < TheGame.Params.fieldSize.cols; j++) {

				var tileX = j * this.calculatedTileSize + this.calculatedTileSize / 2;
				var tileY = i * this.calculatedTileSize + this.calculatedTileSize / 2;
				var tile = this.game.add.sprite(tileX, tileY, "tiles");
				tile.anchor.set(0.5);
				tile.value = 0;
				tile.point = new Phaser.Point(j, i);
				this.scaleSprite(tile, this.calculatedTileSize, this.calculatedTileSize, 0, 1);
				this.tilesArray[i][j] = tile;
				this.tileGroup.add(tile);
			
            }
        }
		this.addNumericTiles();
    },
	addNumericTiles: function () {
		var blankTiles = [];
        for (var i = 0; i < TheGame.Params.fieldSize.rows; i++) {
            for (var j = 0; j < TheGame.Params.fieldSize.cols; j++) {
                if (this.tilesArray[i][j].value === 0) {
                    blankTiles.push(this.tilesArray[i][j]);
                }
            }
        }

	    for (i = 0; i < 2; i++) {
            var tile = Phaser.ArrayUtils.removeRandomItem(blankTiles);
            if (tile) {
                    var val = this.game.rnd.integerInRange(2, 9);
                    this.tilesArray[tile.point.y][tile.point.x].value = val;
                    this.tilesArray[tile.point.y][tile.point.x].frame = val;
					this.hintTile = this.tilesArray[tile.point.y][tile.point.x]; // Last generated tile to be used for hint
                    var tile = this.tilesArray[tile.point.y][tile.point.x];
            }
        }	
	},
	showHint: function () {
		if(this.hintTile) {
			var hintTileX = this.hintTile.point.x * this.calculatedTileSize + this.calculatedTileSize / 2;
			var hintTileY = this.hintTile.point.y * this.calculatedTileSize + this.calculatedTileSize / 2;

			var nextTile = null;
			
			if(this.tilesArray[this.hintTile.point.y][this.hintTile.point.x + 1] && this.tilesArray[this.hintTile.point.y][this.hintTile.point.x + 1].value === 0)
				nextTile = this.tilesArray[this.hintTile.point.y][this.hintTile.point.x + 1];
			else if(this.tilesArray[this.hintTile.point.y][this.hintTile.point.x - 1] && this.tilesArray[this.hintTile.point.y][this.hintTile.point.x - 1].value === 0)
				nextTile = this.tilesArray[this.hintTile.point.y][this.hintTile.point.x - 1];
			else if(this.tilesArray[this.hintTile.point.y + 1][this.hintTile.point.x] && this.tilesArray[this.hintTile.point.y + 1][this.hintTile.point.x].value === 0)
				nextTile = this.tilesArray[this.hintTile.point.y + 1][this.hintTile.point.x];
			else if(this.tilesArray[this.hintTile.point.y - 1][this.hintTile.point.x] && this.tilesArray[this.hintTile.point.y - 1][this.hintTile.point.x].value === 0)
				nextTile = this.tilesArray[this.hintTile.point.y - 1][this.hintTile.point.x];
			 
			
			if(nextTile){
				this.hand = this.game.add.image(hintTileX, hintTileY , "hand");
				this.tileGroup.add(this.hand);
				this.hand.anchor.y = 0;
				this.hand.anchor.x = 0.5
				this.scaleSprite(this.hand, this.calculatedTileSize, this.calculatedTileSize, 0, 1, true);

				var nextTileX = nextTile.point.x * this.calculatedTileSize + this.calculatedTileSize / 2;
				var nextTileY = nextTile.point.y * this.calculatedTileSize + this.calculatedTileSize / 2;
				this.game.add.tween(this.hand).to({ x:nextTileX, y: nextTileY }, 2000, Phaser.Easing.Quadratic.InOut, true, 0, 1000, true);
			}
				
		}
	},	
    toggleSound: function (button) {
    },
    restart: function (button) {
    },
    help: function (button) {
    }
};

var mygame;
window.onload = function () {
	mygame = new Phaser.Game(TheGame.Params.baseWidth, TheGame.Params.baseHeight, Phaser.AUTO);	
	mygame.state.add("Boot", TheGame.Boot);
	mygame.state.add("Loading", TheGame.Loading);
	mygame.state.add("TheGame", TheGame.MyGame);
	mygame.state.start("Boot");
}

We added two random numbers in the grid and player is supposed to drag the number in the grid. The code for displaying hint is highlighted in showHint method. In this method we find the start position and end position for the hint and then use a tween to move our hint icon between the two positions.

this.game.add.tween(this.hand).to({ x:nextTileX, y: nextTileY }, 2000, Phaser.Easing.Quadratic.InOut, true, 0, 1000, true);

Please note another important change in this code from the previous article to accommodate responsive behavior for the hint icon. Every time resize method is called and we are re-aligning elements in the game, we remove the hint icon from the game and re-add it by calling showHint method which calculates the correct position and scale for the hint icon and adds a new tween for displaying the hint.

if(this.hintTile && this.hand){
	this.hand.destroy();
	this.showHint();
}		

See below for the hint element at work

Click here if you want to check this on your device.


Leave A Comment

Your email address will not be published.