Browse Source

动物消消乐

master
jiannibang 6 years ago
parent
commit
f479fe33d7
  1. BIN
      games/match3/bg.jpg
  2. BIN
      games/match3/font.ttf
  3. 32
      games/match3/game.js
  4. 146
      games/match3/gems.json
  5. BIN
      games/match3/gems.png
  6. 252
      games/match3/match3.js
  7. 268
      games/match3/scene.js
  8. 4
      games/match3/style.css
  9. 2
      phaser.js

BIN
games/match3/bg.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 KiB

BIN
games/match3/font.ttf

Binary file not shown.

32
games/match3/game.js

@ -0,0 +1,32 @@
import Phaser from "phaser";
import Scene from "./scene";
function launch({ containerId, onLose }) {
const container = document.getElementById(containerId);
const width = container.clientWidth;
const height = container.clientHeight;
const game = new Phaser.Game({
type: Phaser.CANVAS,
width,
height,
parent: containerId,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
parent: containerId,
width: 2160,
height: 2160,
},
scene: Scene,
});
Object.assign(game, {
onLose,
restart() {
this.scene.scenes[0].scene.start("scene");
},
});
return game;
}
export default launch;
export { launch };

146
games/match3/gems.json

@ -0,0 +1,146 @@
{
"textures": [
{
"image": "gems.png",
"format": "RGBA8888",
"size": {
"w": 1428,
"h": 238
},
"scale": 1,
"frames": [
{
"filename": "1.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 238,
"h": 238
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 238,
"h": 238
},
"frame": {
"x": 0,
"y": 0,
"w": 238,
"h": 238
}
},
{
"filename": "2.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 238,
"h": 238
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 238,
"h": 238
},
"frame": {
"x": 238,
"y": 0,
"w": 238,
"h": 238
}
},
{
"filename": "3.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 238,
"h": 238
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 238,
"h": 238
},
"frame": {
"x": 476,
"y": 0,
"w": 238,
"h": 238
}
},
{
"filename": "4.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 238,
"h": 238
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 238,
"h": 238
},
"frame": {
"x": 714,
"y": 0,
"w": 238,
"h": 238
}
},
{
"filename": "5.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 238,
"h": 238
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 238,
"h": 238
},
"frame": {
"x": 952,
"y": 0,
"w": 238,
"h": 238
}
},
{
"filename": "6.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 238,
"h": 238
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 238,
"h": 238
},
"frame": {
"x": 1190,
"y": 0,
"w": 238,
"h": 238
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:eab6ef09769fec1413e2bf10cd7551b1:a2f99735c3306898d28ffc7880e0dbf4:81fc68276d1596f8f7ad75d59a9ce9b5$"
}
}

BIN
games/match3/gems.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

252
games/match3/match3.js

@ -0,0 +1,252 @@
export default class Match3 {
// constructor, simply turns obj information into class properties
constructor(obj) {
this.rows = obj.rows;
this.columns = obj.columns;
this.items = obj.items;
}
// generates the game field
generateField() {
this.gameArray = [];
this.selectedItem = false;
for (let i = 0; i < this.rows; i++) {
this.gameArray[i] = [];
for (let j = 0; j < this.columns; j++) {
do {
let randomValue = Math.floor(Math.random() * this.items);
this.gameArray[i][j] = {
value: randomValue,
isEmpty: false,
row: i,
column: j,
};
} while (this.isPartOfMatch(i, j));
}
}
}
// returns true if there is a match in the board
matchInBoard() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
if (this.isPartOfMatch(i, j)) {
return true;
}
}
}
return false;
}
// returns true if the item at (row, column) is part of a match
isPartOfMatch(row, column) {
return (
this.isPartOfHorizontalMatch(row, column) ||
this.isPartOfVerticalMatch(row, column)
);
}
// returns true if the item at (row, column) is part of an horizontal match
isPartOfHorizontalMatch(row, column) {
return (
(this.valueAt(row, column) === this.valueAt(row, column - 1) &&
this.valueAt(row, column) === this.valueAt(row, column - 2)) ||
(this.valueAt(row, column) === this.valueAt(row, column + 1) &&
this.valueAt(row, column) === this.valueAt(row, column + 2)) ||
(this.valueAt(row, column) === this.valueAt(row, column - 1) &&
this.valueAt(row, column) === this.valueAt(row, column + 1))
);
}
// returns true if the item at (row, column) is part of an horizontal match
isPartOfVerticalMatch(row, column) {
return (
(this.valueAt(row, column) === this.valueAt(row - 1, column) &&
this.valueAt(row, column) === this.valueAt(row - 2, column)) ||
(this.valueAt(row, column) === this.valueAt(row + 1, column) &&
this.valueAt(row, column) === this.valueAt(row + 2, column)) ||
(this.valueAt(row, column) === this.valueAt(row - 1, column) &&
this.valueAt(row, column) === this.valueAt(row + 1, column))
);
}
// returns the value of the item at (row, column), or false if it's not a valid pick
valueAt(row, column) {
if (!this.validPick(row, column)) {
return false;
}
return this.gameArray[row][column].value;
}
// returns true if the item at (row, column) is a valid pick
validPick(row, column) {
return (
row >= 0 &&
row < this.rows &&
column >= 0 &&
column < this.columns &&
this.gameArray[row] != undefined &&
this.gameArray[row][column] != undefined
);
}
// returns the number of board rows
getRows() {
return this.rows;
}
// returns the number of board columns
getColumns() {
return this.columns;
}
// sets a custom data on the item at (row, column)
setCustomData(row, column, customData) {
this.gameArray[row][column].customData = customData;
}
// returns the custom data of the item at (row, column)
customDataOf(row, column) {
return this.gameArray[row][column].customData;
}
// returns the selected item
getSelectedItem() {
return this.selectedItem;
}
// set the selected item as a {row, column} object
setSelectedItem(row, column) {
this.selectedItem = {
row: row,
column: column,
};
}
// deleselects any item
deleselectItem() {
this.selectedItem = false;
}
// checks if the item at (row, column) is the same as the item at (row2, column2)
areTheSame(row, column, row2, column2) {
return row == row2 && column == column2;
}
// returns true if two items at (row, column) and (row2, column2) are next to each other horizontally or vertically
areNext(row, column, row2, column2) {
return Math.abs(row - row2) + Math.abs(column - column2) == 1;
}
// swap the items at (row, column) and (row2, column2) and returns an object with movement information
swapItems(row, column, row2, column2) {
let tempObject = Object.assign(this.gameArray[row][column]);
this.gameArray[row][column] = Object.assign(this.gameArray[row2][column2]);
this.gameArray[row2][column2] = Object.assign(tempObject);
return [
{
row: row,
column: column,
deltaRow: row - row2,
deltaColumn: column - column2,
},
{
row: row2,
column: column2,
deltaRow: row2 - row,
deltaColumn: column2 - column,
},
];
}
// return the items part of a match in the board as an array of {row, column} object
getMatchList() {
let matches = [];
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.columns; j++) {
if (this.isPartOfMatch(i, j)) {
matches.push({
row: i,
column: j,
});
}
}
}
return matches;
}
// removes all items forming a match
removeMatches() {
let matches = this.getMatchList();
matches.forEach(
function (item) {
this.setEmpty(item.row, item.column);
}.bind(this)
);
}
// set the item at (row, column) as empty
setEmpty(row, column) {
this.gameArray[row][column].isEmpty = true;
}
// returns true if the item at (row, column) is empty
isEmpty(row, column) {
return this.gameArray[row][column].isEmpty;
}
// returns the amount of empty spaces below the item at (row, column)
emptySpacesBelow(row, column) {
let result = 0;
if (row != this.getRows()) {
for (let i = row + 1; i < this.getRows(); i++) {
if (this.isEmpty(i, column)) {
result++;
}
}
}
return result;
}
// arranges the board after a match, making items fall down. Returns an object with movement information
arrangeBoardAfterMatch() {
let result = [];
for (let i = this.getRows() - 2; i >= 0; i--) {
for (let j = 0; j < this.getColumns(); j++) {
let emptySpaces = this.emptySpacesBelow(i, j);
if (!this.isEmpty(i, j) && emptySpaces > 0) {
this.swapItems(i, j, i + emptySpaces, j);
result.push({
row: i + emptySpaces,
column: j,
deltaRow: emptySpaces,
deltaColumn: 0,
});
}
}
}
return result;
}
// replenished the board and returns an object with movement information
replenishBoard() {
let result = [];
for (let i = 0; i < this.getColumns(); i++) {
if (this.isEmpty(0, i)) {
let emptySpaces = this.emptySpacesBelow(0, i) + 1;
for (let j = 0; j < emptySpaces; j++) {
let randomValue = Math.floor(Math.random() * this.items);
result.push({
row: j,
column: i,
deltaRow: emptySpaces,
deltaColumn: 0,
});
this.gameArray[j][i].value = randomValue;
this.gameArray[j][i].isEmpty = false;
}
}
}
return result;
}
}

268
games/match3/scene.js

@ -0,0 +1,268 @@
import gems from "./gems.png";
import Match3 from "./match3";
import bg from "./bg.jpg";
import "./style.css";
const gameOptions = {
gemSize: 260,
frameSize: 238,
swapSpeed: 200,
fallSpeed: 100,
destroySpeed: 200,
boardOffset: {
x: 300,
y: 520,
},
};
export default class playGame extends Phaser.Scene {
constructor() {
super("scene");
}
preload() {
this.load.image("bg", bg);
this.load.spritesheet("gems", gems, {
frameWidth: gameOptions.frameSize,
frameHeight: gameOptions.frameSize,
});
}
updateTexts() {
this.score.text = this.scoreNum;
this.moves.text = this.moveNum;
}
create() {
const bg = this.add.image(
this.game.config.width / 2,
this.game.config.height / 2,
"bg"
);
bg.displayWidth = this.game.config.width;
bg.displayHeight = this.game.config.height;
this.scoreNum = 0;
this.moveNum = 30;
this.score = this.add.text(700, 320, this.scoreNum, {
fontSize: 100,
fontFamily: "game",
color: "#3aab7d",
});
this.moves = this.add.text(1552, 320, this.moveNum, {
fontSize: 100,
fontFamily: "game",
color: "#3aab7d",
});
this.match3 = new Match3({
rows: 6,
columns: 6,
items: 6,
});
this.match3.generateField();
this.canPick = true;
this.dragging = false;
this.drawField();
this.input.on("pointerdown", this.gemSelect, this);
}
drawField() {
this.poolArray = [];
for (let i = 0; i < this.match3.getRows(); i++) {
for (let j = 0; j < this.match3.getColumns(); j++) {
let gemX =
gameOptions.boardOffset.x +
gameOptions.gemSize * j +
gameOptions.gemSize / 2;
let gemY =
gameOptions.boardOffset.y +
gameOptions.gemSize * i +
gameOptions.gemSize / 2;
let gem = this.add.sprite(
gemX,
gemY,
"gems",
this.match3.valueAt(i, j)
);
this.match3.setCustomData(i, j, gem);
}
}
}
gemSelect(pointer) {
if (this.canPick) {
this.dragging = true;
let row = Math.floor(
(pointer.y - gameOptions.boardOffset.y) / gameOptions.gemSize
);
let col = Math.floor(
(pointer.x - gameOptions.boardOffset.x) / gameOptions.gemSize
);
if (this.match3.validPick(row, col)) {
let selectedGem = this.match3.getSelectedItem();
if (!selectedGem) {
this.match3.customDataOf(row, col).setScale(1.2);
this.match3.customDataOf(row, col).setDepth(1);
this.match3.setSelectedItem(row, col);
} else {
if (
this.match3.areTheSame(
row,
col,
selectedGem.row,
selectedGem.column
)
) {
this.match3.customDataOf(row, col).setScale(1);
this.match3.deleselectItem();
} else {
if (
this.match3.areNext(row, col, selectedGem.row, selectedGem.column)
) {
this.match3
.customDataOf(selectedGem.row, selectedGem.column)
.setScale(1);
this.match3.deleselectItem();
if (this.moveNum === 0) {
return this.game.onLose && this.game.onLose();
}
this.moveNum--;
this.updateTexts();
this.swapGems(
row,
col,
selectedGem.row,
selectedGem.column,
true
);
} else {
this.match3
.customDataOf(selectedGem.row, selectedGem.column)
.setScale(1);
this.match3.customDataOf(row, col).setScale(1.2);
this.match3.setSelectedItem(row, col);
}
}
}
}
}
}
swapGems(row, col, row2, col2, swapBack) {
let movements = this.match3.swapItems(row, col, row2, col2);
this.swappingGems = 2;
this.canPick = false;
movements.forEach(
function (movement) {
this.tweens.add({
targets: this.match3.customDataOf(movement.row, movement.column),
x:
this.match3.customDataOf(movement.row, movement.column).x +
gameOptions.gemSize * movement.deltaColumn,
y:
this.match3.customDataOf(movement.row, movement.column).y +
gameOptions.gemSize * movement.deltaRow,
duration: gameOptions.swapSpeed,
callbackScope: this,
onComplete: function () {
this.swappingGems--;
if (this.swappingGems == 0) {
if (!this.match3.matchInBoard()) {
if (swapBack) {
this.swapGems(row, col, row2, col2, false);
} else {
this.canPick = true;
}
} else {
this.handleMatches();
}
}
},
});
}.bind(this)
);
}
handleMatches() {
let gemsToRemove = this.match3.getMatchList();
this.scoreNum += gemsToRemove.length * 10;
this.updateTexts();
let destroyed = 0;
gemsToRemove.forEach(
function (gem) {
this.poolArray.push(this.match3.customDataOf(gem.row, gem.column));
destroyed++;
this.tweens.add({
targets: this.match3.customDataOf(gem.row, gem.column),
alpha: 0,
duration: gameOptions.destroySpeed,
callbackScope: this,
onComplete: function (event, sprite) {
destroyed--;
if (destroyed == 0) {
this.makeGemsFall();
}
},
});
}.bind(this)
);
}
makeGemsFall() {
let moved = 0;
this.match3.removeMatches();
let fallingMovements = this.match3.arrangeBoardAfterMatch();
fallingMovements.forEach(
function (movement) {
moved++;
this.tweens.add({
targets: this.match3.customDataOf(movement.row, movement.column),
y:
this.match3.customDataOf(movement.row, movement.column).y +
movement.deltaRow * gameOptions.gemSize,
duration: gameOptions.fallSpeed * Math.abs(movement.deltaRow),
callbackScope: this,
onComplete: function () {
moved--;
if (moved == 0) {
this.endOfMove();
}
},
});
}.bind(this)
);
let replenishMovements = this.match3.replenishBoard();
replenishMovements.forEach(
function (movement) {
moved++;
let sprite = this.poolArray.pop();
sprite.alpha = 1;
sprite.y =
gameOptions.boardOffset.y +
gameOptions.gemSize * (movement.row - movement.deltaRow + 1) -
gameOptions.gemSize / 2;
(sprite.x =
gameOptions.boardOffset.x +
gameOptions.gemSize * movement.column +
gameOptions.gemSize / 2),
sprite.setFrame(this.match3.valueAt(movement.row, movement.column));
this.match3.setCustomData(movement.row, movement.column, sprite);
this.tweens.add({
targets: sprite,
y:
gameOptions.boardOffset.y +
gameOptions.gemSize * movement.row +
gameOptions.gemSize / 2,
duration: gameOptions.fallSpeed * movement.deltaRow,
callbackScope: this,
onComplete: function () {
moved--;
if (moved == 0) {
this.endOfMove();
}
},
});
}.bind(this)
);
}
endOfMove() {
if (this.match3.matchInBoard()) {
this.time.addEvent({
delay: 250,
callback: this.handleMatches(),
});
} else {
this.canPick = true;
this.selectedGem = null;
}
}
}

4
games/match3/style.css

@ -0,0 +1,4 @@
@font-face {
font-family: "game";
src: url("./font.ttf") format("truetype");
}

2
phaser.js

@ -4,6 +4,7 @@ import game2048 from "./games/game2048/game";
import rect from "./games/game-rect/game";
import floodFill from "./games/game-flood-fill/game";
import fishMaster from "./games/fishMaster/index";
import match3 from "./games/match3/game";
const attachMethods = (fn) => (...p) =>
Object.assign(fn(...p), {
pause() {
@ -33,6 +34,7 @@ const games = Object.entries({
rect,
floodFill,
fishMaster,
match3,
})
.map(([name, fn]) => ({ [name]: attachMethods(fn) }))
.reduce((acc, nxt) => Object.assign(acc, nxt), {});

Loading…
Cancel
Save