You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
9.5 KiB
313 lines
9.5 KiB
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(0, 0, "0", {
|
|
fontFamily: "Arial Black",
|
|
fontSize: tileSize / 2,
|
|
color: "#ffffff",
|
|
});
|
|
|
|
// this.scoreText.setMask(mask);
|
|
|
|
// method to adjust score position
|
|
this.adjustScorePosition();
|
|
|
|
// 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
|
|
);
|
|
} else {
|
|
// we need to adjust score position at each frame
|
|
this.adjustScorePosition();
|
|
}
|
|
}
|
|
// 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 adjust score position
|
|
adjustScorePosition() {
|
|
// adjust score position according to its bounding box and player position
|
|
this.scoreText.x =
|
|
this.player.x + (tileSize - this.scoreText.getBounds().width) / 2;
|
|
this.scoreText.y =
|
|
this.player.y + (tileSize - this.scoreText.getBounds().height) / 2;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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: 100,
|
|
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(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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|