|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 997 B |
|
After Width: | Height: | Size: 6.5 KiB |
@ -0,0 +1,32 @@ |
|||
import Phaser from "phaser"; |
|||
import flipbird from "./scenes/flipbird/flipbird"; |
|||
|
|||
function launch({ containerId, onLose }) { |
|||
const container = document.getElementById(containerId); |
|||
const width = container.clientWidth; |
|||
const height = container.clientHeight; |
|||
const game = new Phaser.Game({ |
|||
type: Phaser.AUTO, |
|||
width, |
|||
height, |
|||
parent: containerId, |
|||
physics: { |
|||
default: "arcade", |
|||
arcade: { |
|||
//gravity: { y: 300 },
|
|||
debug: false, |
|||
}, |
|||
}, |
|||
scene: flipbird, |
|||
}); |
|||
Object.assign(game, { |
|||
onLose, |
|||
restart() { |
|||
this.scene.scenes[0].scene.start("flipbird"); |
|||
}, |
|||
}); |
|||
return game; |
|||
} |
|||
|
|||
export default launch; |
|||
export { launch }; |
|||
@ -0,0 +1,17 @@ |
|||
import { Scene } from "phaser"; |
|||
|
|||
export default class BootScene extends Scene { |
|||
constructor() { |
|||
super({ key: "BootScene" }); |
|||
} |
|||
|
|||
preload() { |
|||
|
|||
} |
|||
|
|||
create() { |
|||
//打开flipbird
|
|||
this.scene.start("startFlipBird"); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
import { Scene } from "phaser"; |
|||
import bg from "@/game/game/assets/flipbird/bg.png"; |
|||
|
|||
import gameoverbg from "@/game/game/assets/flipbird/gameoverbg.png"; |
|||
import gameover from "@/game/game/assets/flipbird/gameover.png"; |
|||
|
|||
export default class gameOverScene extends Scene { |
|||
constructor() { |
|||
super({ key: "flipBirdGameOver" }); |
|||
this.score = ""; |
|||
this.topScore = ""; |
|||
} |
|||
|
|||
init(data) { |
|||
//方法1. 引入sceneA 在初始化的时候就可以获得场景Scene传递的值;
|
|||
this.score = data.score; |
|||
this.topScore = data.topScore; |
|||
} |
|||
preload() { |
|||
this.load.image("bg", bg); |
|||
this.load.image("gameoverbg", gameoverbg); |
|||
this.load.image("gameover", gameover); |
|||
} |
|||
|
|||
create() { |
|||
this.bg = this.add.image(0, 0, "bg"); |
|||
this.bg.setScale(3.1); |
|||
this.gameoverbg = this.add.image( |
|||
this.game.config.width / 2, |
|||
this.game.config.height / 2, |
|||
"gameoverbg" |
|||
); |
|||
this.gameover = this.add.image( |
|||
this.game.config.width / 2, |
|||
this.game.config.height / 2 - 80, |
|||
"gameover" |
|||
); |
|||
this.scoreText = this.add.text( |
|||
this.game.config.width / 2, |
|||
this.game.config.height / 2 + 80, |
|||
"", |
|||
{ fontSize: "55px", fill: "black", fontWeight: 800 } |
|||
); |
|||
this.scoreText.setOrigin(0.5); |
|||
this.scoreText.text = "Score: " + this.score + "\nBest: " + this.topScore; |
|||
this.bg.setInteractive(); |
|||
this.bg.on("pointerdown", () => { |
|||
this.scene.start("flipbird"); |
|||
}) |
|||
} |
|||
} |
|||
@ -0,0 +1,165 @@ |
|||
import Phaser from "phaser"; |
|||
import pipeImg from "@/game/game/assets/flipbird/pipe.png"; |
|||
import birdImg from "@/game/game/assets/flipbird/clumsy.png"; |
|||
import bg from "@/game/game/assets/flipbird/bg.png"; |
|||
import ground from "@/game/game/assets/flipbird/ground.png"; |
|||
/** |
|||
* 素材再找 https://github.com/ellisonleao/clumsy-bird/blob/master/data/img/new.png
|
|||
* https://www.emanueleferonato.com/2019/05/02/flappy-bird-html5-prototype-updated-to-phaser-3-16-2/
|
|||
* |
|||
*/ |
|||
export default class PlayScene extends Phaser.Scene { |
|||
constructor() { |
|||
super({ key: "flipbird" }); |
|||
this.bg = null; |
|||
this.pipeGroup = null; |
|||
this.gameOptions = { |
|||
// bird gravity, will make bird fall if you dont flap
|
|||
birdGravity: 800, |
|||
|
|||
// horizontal bird speed
|
|||
birdSpeed: 120, //125
|
|||
|
|||
// flap thrust
|
|||
birdFlapPower: 300, |
|||
|
|||
// minimum pipe height, in pixels. Affects hole position
|
|||
minPipeHeight: 50, |
|||
|
|||
// distance range from next pipe, in pixels
|
|||
pipeDistance: [220, 280], |
|||
|
|||
// hole range between pipes, in pixels
|
|||
pipeHole: [200, 250], //[100, 130],
|
|||
|
|||
// local storage object name
|
|||
localStorageName: "bestFlappyScore", |
|||
}; |
|||
} |
|||
preload() { |
|||
//this.load.image("bird", "bird.png");
|
|||
this.load.image("bg", bg); |
|||
this.load.image("pipe", pipeImg); |
|||
this.load.image("ground", ground); |
|||
|
|||
this.load.spritesheet("bird", birdImg, { |
|||
frameWidth: 85, |
|||
frameHeight: 60, |
|||
}); |
|||
} |
|||
create() { |
|||
this.bg = this.add.image(0, 0, "bg"); |
|||
this.bg.setScale(1); |
|||
this.pipeGroup = this.physics.add.group(); |
|||
this.pipePool = []; |
|||
for (let i = 0; i < 4; i++) { |
|||
this.pipePool.push(this.pipeGroup.create(0, 0, "pipe")); |
|||
this.pipePool.push(this.pipeGroup.create(0, 0, "pipe")); |
|||
this.placePipes(false); |
|||
} |
|||
this.pipeGroup.setVelocityX(-this.gameOptions.birdSpeed); |
|||
this.bird = this.physics.add.sprite( |
|||
80, |
|||
this.game.config.height / 2, |
|||
"bird" |
|||
); |
|||
this.ground = this.add.tileSprite( |
|||
0, |
|||
this.game.config.height, |
|||
this.game.config.width, |
|||
0, |
|||
"ground" |
|||
); |
|||
this.ground.setOrigin(0); |
|||
|
|||
this.bird.body.gravity.y = this.gameOptions.birdGravity; |
|||
|
|||
this.anims.create({ |
|||
key: "fly", |
|||
frames: this.anims.generateFrameNumbers("bird", { start: 0, end: 3 }), |
|||
frameRate: 10, |
|||
repeat: -1, |
|||
}); |
|||
this.input.on("pointerdown", this.flap, this); |
|||
this.score = 0; |
|||
this.scoreText1 = this.add.text(20, 20, "", { |
|||
fontSize: 20, |
|||
}); |
|||
this.updateScore(this.score); |
|||
} |
|||
updateScore(inc) { |
|||
this.score += inc; |
|||
this.scoreText1.text = "Game Score: " + this.score; |
|||
} |
|||
placePipes(addScore) { |
|||
let rightmost = this.getRightmostPipe(); |
|||
let pipeHoleHeight = Phaser.Math.Between( |
|||
this.gameOptions.pipeHole[0], |
|||
this.gameOptions.pipeHole[1] |
|||
); |
|||
let pipeHolePosition = Phaser.Math.Between( |
|||
this.gameOptions.minPipeHeight + pipeHoleHeight / 2, |
|||
this.game.config.height - |
|||
this.gameOptions.minPipeHeight - |
|||
pipeHoleHeight / 2 |
|||
); |
|||
this.pipePool[0].x = |
|||
rightmost + |
|||
this.pipePool[0].getBounds().width + |
|||
Phaser.Math.Between( |
|||
this.gameOptions.pipeDistance[0], |
|||
this.gameOptions.pipeDistance[1] |
|||
); |
|||
this.pipePool[0].y = pipeHolePosition - pipeHoleHeight / 2; |
|||
this.pipePool[0].setOrigin(0, 1); |
|||
this.pipePool[1].x = this.pipePool[0].x; |
|||
this.pipePool[1].y = pipeHolePosition + pipeHoleHeight / 2; |
|||
this.pipePool[1].setOrigin(0, 0); |
|||
this.pipePool[1].flipY = true; |
|||
this.pipePool = []; |
|||
if (addScore) { |
|||
this.updateScore(1); |
|||
} |
|||
} |
|||
flap() { |
|||
this.bird.body.velocity.y = -this.gameOptions.birdFlapPower; |
|||
this.bird.anims.play("fly", true); |
|||
} |
|||
getRightmostPipe() { |
|||
let rightmostPipe = 0; |
|||
this.pipeGroup.getChildren().forEach(function (pipe) { |
|||
rightmostPipe = Math.max(rightmostPipe, pipe.x); |
|||
}); |
|||
return rightmostPipe; |
|||
} |
|||
update() { |
|||
this.physics.world.collide( |
|||
this.bird, |
|||
this.pipeGroup, |
|||
function () { |
|||
this.die(); |
|||
}, |
|||
null, |
|||
this |
|||
); |
|||
if (this.bird.y > this.game.config.height || this.bird.y < 0) { |
|||
this.die(); |
|||
} |
|||
this.pipeGroup.getChildren().forEach(function (pipe) { |
|||
if (pipe.getBounds().right < 0) { |
|||
this.pipePool.push(pipe); |
|||
if (this.pipePool.length == 2) { |
|||
this.placePipes(true); |
|||
} |
|||
} |
|||
}, this); |
|||
this.ground.tilePositionX += 2; |
|||
// this.bg.tilePositionY += 0.5
|
|||
} |
|||
die() { |
|||
if (this.game.onLose) { |
|||
this.game.onLose(); |
|||
this.scene.pause(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
import { Scene } from "phaser"; |
|||
import bg from "@/game/game/assets/flipbird/bg.png"; |
|||
|
|||
import birdImg from "@/game/game/assets/flipbird/clumsy.png"; |
|||
import startBtn from "@/game/game/assets/flipbird/start.png"; |
|||
|
|||
export default class startScene extends Scene { |
|||
constructor() { |
|||
super({ key: "startFlipBird" }); |
|||
} |
|||
|
|||
preload() { |
|||
this.load.image("bg", bg); |
|||
this.load.image("startBtn", startBtn); |
|||
this.load.spritesheet("bird", birdImg, { |
|||
frameWidth: 85, |
|||
frameHeight: 60 |
|||
}); |
|||
} |
|||
|
|||
create() { |
|||
this.bg = this.add.image(0, 0, "bg"); |
|||
this.startBtn = this.add.image( |
|||
this.game.config.width / 2, |
|||
this.game.config.height / 2 + 100, |
|||
"startBtn" |
|||
); |
|||
this.startBtn.setScale(0.5); |
|||
this.bg.setScale(3.1); |
|||
this.bird = this.physics.add.sprite( |
|||
this.game.config.width / 2, |
|||
this.game.config.height / 2, |
|||
"bird" |
|||
); |
|||
// this.tip = this.add.text(
|
|||
// this.game.config.width / 2,
|
|||
// this.game.config.height / 2 - 100,
|
|||
// "点击屏幕开始游戏",
|
|||
// {
|
|||
// fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif',
|
|||
// fontSize: "30px",
|
|||
// color: "#000",
|
|||
// align: "center"
|
|||
// }
|
|||
// );
|
|||
this.startBtn.setInteractive(); |
|||
this.startBtn.on("pointerdown", () =>{ |
|||
this.scene.start("flipbird"); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<font> |
|||
<info face="font" size="72" bold="0" italic="0" charset="" unicode="" stretchH="100" smooth="1" aa="1" padding="2,2,2,2" spacing="0,0" outline="0"/> |
|||
<common lineHeight="81" base="46" scaleW="90" scaleH="253" pages="1" packed="0"/> |
|||
<pages> |
|||
<page id="0" file="font.png"/> |
|||
</pages> |
|||
<chars count="10"> |
|||
<char id="48" x="2" y="2" width="49" height="48" xoffset="3" yoffset="0" xadvance="51" page="0" chnl="15"/> |
|||
<char id="49" x="2" y="52" width="16" height="48" xoffset="3" yoffset="0" xadvance="18" page="0" chnl="15"/> |
|||
<char id="50" x="2" y="102" width="33" height="48" xoffset="3" yoffset="0" xadvance="35" page="0" chnl="15"/> |
|||
<char id="51" x="2" y="152" width="32" height="49" xoffset="3" yoffset="0" xadvance="34" page="0" chnl="15"/> |
|||
<char id="52" x="20" y="52" width="32" height="48" xoffset="2" yoffset="0" xadvance="33" page="0" chnl="15"/> |
|||
<char id="53" x="53" y="2" width="34" height="48" xoffset="3" yoffset="0" xadvance="36" page="0" chnl="15"/> |
|||
<char id="54" x="2" y="203" width="34" height="48" xoffset="2" yoffset="0" xadvance="35" page="0" chnl="15"/> |
|||
<char id="55" x="36" y="152" width="29" height="48" xoffset="2" yoffset="0" xadvance="30" page="0" chnl="15"/> |
|||
<char id="56" x="37" y="102" width="32" height="48" xoffset="3" yoffset="0" xadvance="34" page="0" chnl="15"/> |
|||
<char id="57" x="54" y="52" width="34" height="48" xoffset="2" yoffset="0" xadvance="35" page="0" chnl="15"/> |
|||
<char id="32" x="0" y="0" width="0" height="0" xoffset="2" yoffset="0" xadvance="11" page="0" chnl="15"/> |
|||
</chars> |
|||
</font> |
|||
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 476 B |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 45 KiB |
@ -0,0 +1,6 @@ |
|||
export const ROW = 1; //0
|
|||
export const COL = 1; |
|||
export const TILE_SIZE = 125; //80
|
|||
export const TILE_SPACING = 10; //8
|
|||
export const TWEEN_DURATION = 100; |
|||
export const LOCAL_STORAGE_NAME = "L2048"; |
|||
@ -0,0 +1,34 @@ |
|||
import Phaser from "phaser"; |
|||
import PlayScene from "./scenes/PlayScene"; |
|||
import { TILE_SIZE, TILE_SPACING } from "./constant"; |
|||
|
|||
function launch({ containerId, onLose }) { |
|||
const container = document.getElementById(containerId); |
|||
const width = container.clientWidth; |
|||
const height = container.clientHeight; |
|||
const game = new Phaser.Game({ |
|||
type: Phaser.AUTO, |
|||
width, |
|||
height, |
|||
parent: containerId, |
|||
backgroundColor: 0xbbada0, |
|||
physics: { |
|||
default: "arcade", |
|||
arcade: { |
|||
gravity: { y: 300 }, |
|||
debug: true, |
|||
}, |
|||
}, |
|||
scene: PlayScene, |
|||
}); |
|||
Object.assign(game, { |
|||
onLose, |
|||
restart() { |
|||
this.scene.scenes[0].scene.start("PlayScene"); |
|||
}, |
|||
}); |
|||
return game; |
|||
} |
|||
|
|||
export default launch; |
|||
export { launch }; |
|||
@ -0,0 +1,520 @@ |
|||
import Phaser from "phaser"; |
|||
|
|||
import tile_default from "@/game/game2048/assets/sprites/tile_default.png"; |
|||
import restart from "@/game/game2048/assets/sprites/restart.png"; |
|||
import score_img from "@/game/game2048/assets/sprites/score.png"; |
|||
import score_best from "@/game/game2048/assets/sprites/score_best.png"; |
|||
import tiles from "@/game/game2048/assets/sprites/tiles125.png"; |
|||
|
|||
import move_mp3 from "@/game/game2048/assets/sounds/move.mp3"; |
|||
import move_ogg from "@/game/game2048/assets/sounds/move.ogg"; |
|||
import grow_mp3 from "@/game/game2048/assets/sounds/grow.mp3"; |
|||
import grow_ogg from "@/game/game2048/assets/sounds/grow.ogg"; |
|||
|
|||
import { |
|||
COL, |
|||
ROW, |
|||
TILE_SIZE, |
|||
TILE_SPACING, |
|||
TWEEN_DURATION, |
|||
LOCAL_STORAGE_NAME, |
|||
} from "../constant"; |
|||
|
|||
class PlayScene extends Phaser.Scene { |
|||
tileArray = []; |
|||
canMove = false; |
|||
score = 0; // 分数
|
|||
|
|||
constructor() { |
|||
super({ |
|||
key: "PlayScene", |
|||
}); |
|||
} |
|||
|
|||
preload() { |
|||
this.load.image("tile_default", tile_default); |
|||
this.load.image("restart", restart); |
|||
this.load.image("score", score_img); |
|||
this.load.image("score_best", score_best); |
|||
|
|||
this.load.spritesheet("tiles", tiles, { |
|||
frameWidth: TILE_SIZE, |
|||
frameHeight: TILE_SIZE, |
|||
}); |
|||
|
|||
this.load.audio("move", [move_mp3, move_ogg]); |
|||
this.load.audio("grow", [grow_mp3, grow_ogg]); |
|||
} |
|||
|
|||
create() { |
|||
this.layout(); |
|||
|
|||
// 随机生成 2 个数字方块
|
|||
this.addTile(); |
|||
this.addTile(); |
|||
|
|||
this.addEvent(); |
|||
|
|||
this.addSound(); |
|||
} |
|||
//添加声音
|
|||
addSound() { |
|||
this.moveSound = this.sound.add("move"); |
|||
this.growSound = this.sound.add("grow"); |
|||
} |
|||
|
|||
addEvent() { |
|||
// 移动端操作
|
|||
this.input.on("pointerup", this.handleTouch); |
|||
|
|||
// 键盘操作
|
|||
this.input.keyboard.on("keydown", this.handleKey); |
|||
} |
|||
|
|||
handleTouch = (e) => { |
|||
if (this.canMove) { |
|||
// 计算按住时间
|
|||
let swipeTime = e.upTime - e.downTime; |
|||
|
|||
// 生成 {x: v1, y: v2} 格式
|
|||
let swipe = new Phaser.Geom.Point(e.upX - e.downX, e.upY - e.downY); |
|||
|
|||
// 获取对角线长度
|
|||
let swipeMagnitude = Phaser.Geom.Point.GetMagnitude(swipe); |
|||
|
|||
// 偏向方向比例 1 为直着朝一个方向
|
|||
let swipeNormal = new Phaser.Geom.Point( |
|||
swipe.x / swipeMagnitude, |
|||
swipe.y / swipeMagnitude |
|||
); |
|||
|
|||
/** |
|||
* 滑动的对角线长度大于 20 |
|||
* 滑动的时间小于 1000 毫秒 |
|||
* 滑动的角度尽量偏一个方向 |
|||
*/ |
|||
if ( |
|||
swipeMagnitude > 20 && |
|||
swipeTime < 1000 && |
|||
(Math.abs(swipeNormal.y) > 0.8 || Math.abs(swipeNormal.x) > 0.8) |
|||
) { |
|||
let children = this.tileGroup.getChildren(); |
|||
if (swipeNormal.x > 0.8) { |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 设置层级,越左侧层级越高
|
|||
children[i].depth = this.game.config.width - children[i].x; |
|||
} |
|||
console.log("向右"); |
|||
this.move(0, 1); |
|||
} |
|||
|
|||
if (swipeNormal.x < -0.8) { |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 越向右层级越高
|
|||
children[i].depth = children[i].x; |
|||
} |
|||
console.log("向左"); |
|||
this.move(0, -1); |
|||
} |
|||
|
|||
if (swipeNormal.y > 0.8) { |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 越上面层级越高
|
|||
children[i].depth = this.game.config.height - children[i].y; |
|||
} |
|||
console.log("向下"); |
|||
this.move(1, 0); |
|||
} |
|||
|
|||
if (swipeNormal.y < -0.8) { |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 越下面层级越高
|
|||
children[i].depth = children[i].y; |
|||
} |
|||
console.log("向上"); |
|||
this.move(-1, 0); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
handleKey = (e) => { |
|||
if (this.canMove) { |
|||
let children = this.tileGroup.getChildren(); |
|||
switch (e.code) { |
|||
case "KeyA": |
|||
case "ArrowLeft": |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 越向右层级越高
|
|||
children[i].depth = children[i].x; |
|||
} |
|||
console.log("向左"); |
|||
|
|||
// 行为 0,不变化,列为 -1,向左
|
|||
this.move(0, -1); |
|||
break; |
|||
case "KeyD": |
|||
case "ArrowRight": |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 设置层级,越左侧层级越高
|
|||
children[i].depth = this.game.config.width - children[i].x; |
|||
} |
|||
console.log("向右"); |
|||
|
|||
// 行为 0,不变化,列为 1,向右
|
|||
this.move(0, 1); |
|||
break; |
|||
case "KeyW": |
|||
case "ArrowUp": |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 越下面层级越高
|
|||
children[i].depth = children[i].y; |
|||
} |
|||
console.log("向上"); |
|||
|
|||
// 列为 0,不变化,行为 -1,向上
|
|||
this.move(-1, 0); |
|||
break; |
|||
case "KeyS": |
|||
case "ArrowDown": |
|||
for (var i = 0; i < children.length; i++) { |
|||
// 越上面层级越高
|
|||
children[i].depth = this.game.config.height - children[i].y; |
|||
} |
|||
console.log("向下"); |
|||
|
|||
// 列为 0,不变化,行为 1,向下
|
|||
this.move(1, 0); |
|||
break; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* |
|||
* @param stepRow 行的变化,上下变化,上下滑动,0 为不变化, 1为进1块 -1为退1块 |
|||
* @param stepCol 列的变化,左右变化,左右滑动,0 为不变化, 1为进1块 -1为退1块 |
|||
*/ |
|||
move(rowStep, colStep) { |
|||
this.canMove = false; |
|||
this.movingTiles = 0; |
|||
let somethingMoved = false; |
|||
let moveScore = 0; |
|||
|
|||
for (let i = 0; i < 4; i++) { |
|||
for (let j = 0; j < 4; j++) { |
|||
/** |
|||
* 一块一块移动去判断 |
|||
* 正确的移动顺序应该是: |
|||
* 向左移动(move(0, -1)):从左到右,从上到下遍历判断移动【默认遍历 i,j 即可】 |
|||
* 向右移动(move(0, 1)) :从右到左,从上到下遍历判断移动【默认遍历 i,j 取反值】 |
|||
* 向上移动(move(-1, 0) :从左到右,从上到下遍历判断移动【默认遍历 i,j 即可】 |
|||
* 向下移动(move(1, 0)) :从左到右,从下到上遍历判断移动【默认遍历 j,i 取反值】 |
|||
*/ |
|||
let row = rowStep === 1 ? 3 - i : i; |
|||
let col = colStep === 1 ? 3 - j : j; |
|||
|
|||
let tileValue = this.tileArray[row][col].tileValue; |
|||
if (tileValue !== 0) { |
|||
// 横向移动单位距离
|
|||
let rowSteps = rowStep; |
|||
|
|||
// 纵向移动单位距离
|
|||
let colSteps = colStep; |
|||
|
|||
// 条件一:靠边移动,不可超出
|
|||
// 条件二:移动的地方没有数值方块
|
|||
while ( |
|||
this.isInsideBoard(row + rowSteps, col + colSteps) && |
|||
this.tileArray[row + rowSteps][col + colSteps].tileValue === 0 |
|||
) { |
|||
colSteps += colStep; |
|||
rowSteps += rowStep; |
|||
} |
|||
|
|||
// 条件一:靠边移动,不可超出
|
|||
// 条件二:目标方块与当前方块 tileValue 相等,也就是数值相等
|
|||
// 条件三:目标方块 canUpgrade 为 true【控制一次只能被覆盖一次】
|
|||
// 条件四:当前方块 canUpgrade 为 true
|
|||
// 条件五:tileValue 小于 12 Math.pow(2,12) 4096,最大 4096
|
|||
if ( |
|||
this.isInsideBoard(row + rowSteps, col + colSteps) && |
|||
this.tileArray[row + rowSteps][col + colSteps].tileValue === |
|||
tileValue && |
|||
this.tileArray[row + rowSteps][col + colSteps].canUpgrade && |
|||
this.tileArray[row][col].canUpgrade && |
|||
tileValue < 12 |
|||
) { |
|||
// 目标方块 tileValue + 1, 本来是 Math.pow(2,1) 变成了 Math.pow(2,2),也就是方块 2 变成 4
|
|||
this.tileArray[row + rowSteps][col + colSteps].tileValue++; |
|||
|
|||
// 移动分数
|
|||
moveScore += Math.pow( |
|||
2, |
|||
this.tileArray[row + rowSteps][col + colSteps].tileValue |
|||
); |
|||
|
|||
// 目标块只能被覆盖一次
|
|||
this.tileArray[row + rowSteps][col + colSteps].canUpgrade = false; |
|||
|
|||
// 设置当前方块 tileValue 为 0
|
|||
this.tileArray[row][col].tileValue = 0; |
|||
|
|||
// 当前方块移动到目标方块
|
|||
// 参数一:当前数值精灵
|
|||
// 参数二:横向位置
|
|||
// 参数三:纵向位置
|
|||
// 参数四:移动单位距离
|
|||
// 参数五: bool
|
|||
this.moveTile( |
|||
this.tileArray[row][col], |
|||
row + rowSteps, |
|||
col + colSteps, |
|||
Math.abs(rowSteps + colSteps), |
|||
true |
|||
); |
|||
somethingMoved = true; |
|||
} else { |
|||
// while 时最后一次条件不成立,但 colSteps 与 rowSteps 已经加了col与row,所以这里减回去。
|
|||
rowSteps = rowSteps - rowStep; |
|||
colSteps = colSteps - colStep; |
|||
|
|||
// 若横向或纵向有移动,则开始移动
|
|||
if (colSteps !== 0 || rowSteps !== 0) { |
|||
// console.log(row, rowSteps, col, colSteps)
|
|||
// 设置移动到的地方值为当前值
|
|||
this.tileArray[row + rowSteps][ |
|||
col + colSteps |
|||
].tileValue = tileValue; |
|||
|
|||
// 设置当前块的值为 0
|
|||
this.tileArray[row][col].tileValue = 0; |
|||
|
|||
// 参数一:精灵
|
|||
// 参数二:横向位置
|
|||
// 参数三:纵向位置
|
|||
// 参数四:移动单位距离
|
|||
// 参数五: bool
|
|||
this.moveTile( |
|||
this.tileArray[row][col], |
|||
row + rowSteps, |
|||
col + colSteps, |
|||
Math.abs(rowSteps + colSteps), |
|||
false |
|||
); |
|||
somethingMoved = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!somethingMoved) { |
|||
this.canMove = true; |
|||
} else { |
|||
this.moveSound.play(); |
|||
this.score += moveScore; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Sprite} tile |
|||
* @param {Number} row |
|||
* @param {Number} col |
|||
* @param {Number} distance 根据距离动态改变动画运行时间 |
|||
* @param {Booler} changeNumber |
|||
*/ |
|||
moveTile(tile, row, col, distance, changeNumber) { |
|||
this.movingTiles++; |
|||
this.tweens.add({ |
|||
targets: [tile.tileSprite], |
|||
x: this.setPosition(col, COL), |
|||
y: this.setPosition(row, ROW), |
|||
duration: TWEEN_DURATION * distance, |
|||
onComplete: () => { |
|||
this.movingTiles--; |
|||
if (changeNumber) { |
|||
this.transformTile(tile, row, col); |
|||
} |
|||
if (this.movingTiles === 0) { |
|||
this.scoreText.setText(this.score); |
|||
this.resetTiles(); |
|||
this.addTile(); |
|||
} |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
transformTile(tile, row, col) { |
|||
this.growSound.play(); |
|||
this.movingTiles++; |
|||
tile.tileSprite.setFrame(this.tileArray[row][col].tileValue - 1); |
|||
this.tweens.add({ |
|||
targets: [tile.tileSprite], |
|||
scaleX: 1.1, |
|||
scaleY: 1.1, |
|||
duration: TWEEN_DURATION, |
|||
yoyo: true, |
|||
repeat: 1, |
|||
onComplete: () => { |
|||
this.movingTiles--; |
|||
if (this.movingTiles === 0) { |
|||
this.scoreText.setText(this.score); |
|||
this.resetTiles(); |
|||
this.addTile(); |
|||
} |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
// 精灵归位,数组中指定的 tileValue 信息来显示数组中
|
|||
resetTiles() { |
|||
for (let i = 0; i < 4; i++) { |
|||
for (let j = 0; j < 4; j++) { |
|||
this.tileArray[i][j].canUpgrade = true; |
|||
this.tileArray[i][j].tileSprite.x = this.setPosition(j, COL); |
|||
this.tileArray[i][j].tileSprite.y = this.setPosition(i, ROW); |
|||
if (this.tileArray[i][j].tileValue > 0) { |
|||
this.tileArray[i][j].tileSprite.alpha = 1; |
|||
this.tileArray[i][j].tileSprite.visible = true; |
|||
|
|||
// 假如 tileValue = 2,则方块数值为 Math.pow(2,2) == 4, 4 的精灵索引为1,所以等于 tileValue - 1
|
|||
this.tileArray[i][j].tileSprite.setFrame( |
|||
this.tileArray[i][j].tileValue - 1 |
|||
); |
|||
} else { |
|||
this.tileArray[i][j].tileSprite.alpha = 0; |
|||
this.tileArray[i][j].tileSprite.visible = false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 布局
|
|||
layout() { |
|||
// 头部
|
|||
this.layout_header(); |
|||
|
|||
// 4 * 4 方块
|
|||
this.layout_body(); |
|||
} |
|||
//设置头部
|
|||
layout_header() { |
|||
// 添加分数背景
|
|||
this.add.sprite( |
|||
this.setPosition(0, COL) + 30, |
|||
this.setPosition(0, ROW) - 115, |
|||
"score" |
|||
); |
|||
|
|||
// 添加最高分数背景
|
|||
this.add.sprite( |
|||
this.setPosition(1, COL) + 40, |
|||
this.setPosition(0, ROW) - 115, |
|||
"score_best" |
|||
); |
|||
|
|||
// 重新开始游戏
|
|||
let restartButton = this.add.sprite( |
|||
this.setPosition(3, COL) - 10, |
|||
this.setPosition(0, ROW) - 110, |
|||
"restart" |
|||
); |
|||
restartButton.setInteractive(); |
|||
restartButton.on("pointerdown", () => { |
|||
this.scene.start("PlayScene"); |
|||
}); |
|||
|
|||
// 分数
|
|||
this.scoreText = this.add |
|||
.text( |
|||
this.setPosition(0, COL) + 30, |
|||
this.setPosition(0, ROW) - 105, |
|||
"0", |
|||
{ |
|||
fontFamily: "Arial", |
|||
fontSize: 22, |
|||
fill: "#ffffff", |
|||
} |
|||
) |
|||
.setOrigin(0.5); |
|||
} |
|||
//设置底部布局
|
|||
layout_body() { |
|||
this.tileGroup = this.add.group(); |
|||
|
|||
for (let i = 0; i < 4; i++) { |
|||
this.tileArray[i] = []; |
|||
for (let j = 0; j < 4; j++) { |
|||
this.add |
|||
.sprite( |
|||
this.setPosition(j, COL), |
|||
this.setPosition(i, ROW), |
|||
"tile_default" |
|||
) |
|||
.setScale(1.5625); |
|||
|
|||
let tile = this.add.sprite( |
|||
this.setPosition(j, COL), |
|||
this.setPosition(i, ROW), |
|||
"tiles" |
|||
); |
|||
|
|||
tile.alpha = 0; |
|||
tile.visible = false; |
|||
|
|||
this.tileArray[i][j] = { |
|||
tileValue: 0, |
|||
tileSprite: tile, |
|||
canUpgrade: true, |
|||
}; |
|||
|
|||
this.tileGroup.add(tile); |
|||
} |
|||
} |
|||
} |
|||
|
|||
setPosition(pos, direction) { |
|||
let top = direction === ROW ? 100 : 0; |
|||
return ( |
|||
pos * (TILE_SIZE + TILE_SPACING) + TILE_SIZE * 0.5 + TILE_SPACING + top |
|||
); |
|||
} |
|||
//添加随机的2
|
|||
addTile() { |
|||
let emptyTiles = []; |
|||
for (let i = 0; i < 4; i++) { |
|||
for (let j = 0; j < 4; j++) { |
|||
if (this.tileArray[i][j].tileValue === 0) { |
|||
emptyTiles.push({ |
|||
row: i, |
|||
col: j, |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (emptyTiles.length > 0) { |
|||
let chosenTile = Phaser.Utils.Array.GetRandom(emptyTiles); |
|||
this.tileArray[chosenTile.row][chosenTile.col].tileValue = 1; |
|||
this.tileArray[chosenTile.row][chosenTile.col].tileSprite.visible = true; |
|||
this.tileArray[chosenTile.row][chosenTile.col].tileSprite.setFrame(0); |
|||
|
|||
this.tweens.add({ |
|||
targets: [this.tileArray[chosenTile.row][chosenTile.col].tileSprite], |
|||
alpha: 1, |
|||
duration: TWEEN_DURATION, |
|||
onComplete: () => { |
|||
this.canMove = true; |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
isInsideBoard(row, col) { |
|||
return row >= 0 && col >= 0 && row < 4 && col < 4; |
|||
} |
|||
} |
|||
|
|||
export default PlayScene; |
|||