import Phaser from "phaser"; import tiles from "../assets/tiles.png"; import bg from "../assets/bg.jpg"; import gridTile from "../assets/gridTile.png"; const gameWidth = 924; const tileSpace = 3; const offsetLeft = (1080 - gameWidth) / 2; const offsetTop = 200; const columns = 9; const rows = 20; const getRandom = () => Math.floor(Math.random() * 5); const getValues = () => Phaser.Utils.Array.Shuffle([ 0, 1, 2, 3, 4, getRandom(), getRandom(), getRandom(), getRandom(), ]); const tileSize = (gameWidth - tileSpace * (columns - 1)) / columns; export default class Rect extends Phaser.Scene { constructor() { super({ key: "Rect" }); this.gameOptions = { // number of columns columns, // number of rows, must be high enough to allow object pooling rows, // tile speed in pixels per second tileSpeed: 100, }; } preload() { this.load.image("bg", bg); this.load.image("gridTile", gridTile); this.load.spritesheet("tiles", tiles, { frameWidth: 202, frameHeight: 202, }); } create() { this.bg = this.add.image( this.game.config.width / 2, this.game.config.height / 2, "bg" ); this.bg.displayWidth = this.game.config.width; this.bg.displayHeight = this.game.config.height; // physics group which manages all tiles in game this.tileGroup = this.physics.add.group(); const shape = this.make.graphics(); shape.beginPath(); shape.moveTo(0, offsetTop); shape.lineTo(1080, offsetTop); shape.lineTo(1080, tileSize * 8 + tileSpace * 7 + offsetTop); shape.lineTo(0, tileSize * 8 + tileSpace * 7 + offsetTop); shape.lineTo(0, offsetTop); shape.fillPath(); const mask = shape.createGeometryMask(); mask.setInvertAlpha(true); for (let i = 0; i < this.gameOptions.columns; i++) { for (let j = 0; j < 8; j++) { const defaultTile = this.add.image( offsetLeft + i * (tileSize + tileSpace), offsetTop + j * (tileSize + tileSpace), "gridTile" ); defaultTile.setOrigin(0); defaultTile.displayWidth = tileSize; defaultTile.displayHeight = tileSize; } } // time to add tiles to the game for (let i = 0; i < this.gameOptions.rows; i++) { // build an array with integers between 0 and this.gameOptions.columns - 1: [0, 1, 2, ..., this.gameOptions.columns - 1] let values = getValues(); // save middle column color of first row if (i == 0) { var middleColor = values[Math.floor(this.gameOptions.columns / 2)]; } // now we place the tiles, row by row for (let j = 0; j < this.gameOptions.columns; j++) { // add a tile. Tile frame is set according to "values" shuffled array let tile = this.tileGroup.create( offsetLeft + j * (tileSize + tileSpace), i * (tileSize + tileSpace) + (this.game.config.height / 4) * 3, "tiles", values[j] ); tile.setMask(mask); // call adjustTile method to adjust tile origin and display size this.adjustTile(tile); } } // let's build once again an array with integers between 0 and this.gameOptions.columns - 1 let values = getValues(); // remove the item at "middlecolor" position because we don't want it to be randomly selected values = values.filter((v) => middleColor != v); // add the player to the this.this.game. Player color is picked amoung "values" array items, which does not contain anymore "middlecolor" value this.player = this.tileGroup.create( offsetLeft + (tileSize + tileSpace) * Math.floor(this.gameOptions.columns / 2), (this.game.config.height / 4) * 3 - (tileSize + tileSpace), "tiles", Phaser.Utils.Array.GetRandom(values) ); this.player.setMask(mask); // adjust player origin and display size this.adjustTile(this.player); // the score this.score = 0; // add score text this.scoreText = this.add.text(20, 20, "", { fontSize: 20, }); this.scoreText.text = "Game Score: " + this.score; // move entire tile group up by this.gameOptions.tileSpeed pixels / second this.tileGroup.setVelocityY(-this.gameOptions.tileSpeed); // can the player move? Yes, at the moment this.canMove = true; // did we match any tile? No, at the moment this.matched = false; // wait for player input this.input.off("pointerdown"); this.input.on("pointerdown", this.moveTile, this); } // method to be executed at each frame update() { // if the player touches the top of the screen... if (this.player.y < offsetTop) { // gmae over man, restart the this.game this.game.onLose && this.game.onLose(); this.tileGroup.setVelocityY(0); this.input.off("pointerdown"); this.input.on( "pointerdown", function () { this.scene.start("Rect"); }, this ); } } // method to set tile origin and display size adjustTile(sprite) { // set origin at the top left corner sprite.setOrigin(0); // set display width and height to "tileSize" pixels sprite.displayWidth = tileSize; sprite.displayHeight = tileSize; } // method to move player tile moveTile(pointer) { // if we can move... if (this.canMove) { // determine column according to input coordinate and tile size let column = Math.floor((pointer.x - offsetLeft) / tileSize); if (column > 8) column = 8; if (column < 0) column = 0; // get the ditance from current player tile and destination let distance = Math.floor( Math.abs(offsetLeft + column * (tileSize + tileSpace) - this.player.x) / tileSize ); // did we actually move? if (distance > 0) { // we can't move anymore this.canMove = false; this.player.x = offsetLeft + column * (tileSize + tileSpace); this.checkMatch(); // tween the player to destination tile // this.tweens.add({ // targets: [this.player], // x: offsetLeft + column * (tileSize + tileSpace), // duration: distance * 30, // callbackScope: this, // onComplete: function () { // // at the end of the tween, check for tile match // this.checkMatch(); // }, // }); } } } // method to check tile matches checkMatch() { // get tile below player tile let tileBelow = this.physics.overlapRect( this.player.x + tileSize / 2, this.player.y + tileSize * 1.5, 1, 1 ); // "tileBelow" is an array so we have to compare the first - and only - item frame with player frame. Are the two frames the same? if (tileBelow[0].gameObject.frame.name == this.player.frame.name) { // we have a match this.matched = true; // check the whole row below player tile let rowBelow = this.physics.overlapRect( 0, this.player.y + tileSize * 1.5, gameWidth, 1 ); // tween down the player this.tweens.add({ targets: [this.player], y: tileBelow[0].gameObject.y, duration: 50, callbackScope: this, onUpdate: function (tween, target) { // at each update, we have to adjust player position because tiles continue moving up this.player.y = Math.min(this.player.y, tileBelow[0].gameObject.y); }, // at the end of the tween, we have to move the row at the bottom, to reuse sprites onComplete: function () { // increment score this.score++; // update score text this.scoreText.setText("Game Score: " + this.score); // the good old array with all integers from zero to this.gameOptions.columns - 1 let values = getValues(); // place all tiles below the lowest row for (let i = 0; i < this.gameOptions.columns; i++) { rowBelow[i].gameObject.setFrame(values[i]); rowBelow[i].gameObject.y += (tileSize + tileSpace) * this.gameOptions.rows; } // check for matches again, there could be a combo this.checkMatch(); }, }); } // what to do when player moved but there isn't any match? else { // we can move again this.canMove = true; // is there a previous match? Did we come here from a previous match? if (this.matched) { // no more matches this.matched = false; // get the tile below the player let tileBelow = this.physics.overlapRect( this.player.x + tileSize / 2, this.player.y + tileSize * 1.5, 1, 1 ); // the good old array with all integers from zero to this.gameOptions.columns - 1 let values = [0, 1, 2, 3, 4]; // remove the item at "frame" value of tile below the player pbecause we don't want it to be randomly selected values.splice(tileBelow[0].gameObject.frame.name, 1); // change player frame this.player.setFrame(Phaser.Utils.Array.GetRandom(values)); } } } }