體驗連接html
當前版本一次只能消除一處!前端
如有不足,歡迎指正!git
let's go!github
需求:app
- 圖標成矩形隨機排列
- 能夠拖動一行或一列
- 3個或以上個相同圖案連在一塊兒時會被消除
0. 先把 Phaser
的主要代碼搭起來this
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Drag and Match</title> <style> * { margin: 0; padding: 0; } </style> </head> <body> <div id="game"></div> <script src="/public/js/phaser.min.js"></script> <script> let game let gameOptions = { iconW: 100, // 圖標寬度 iconH: 100, // 圖標高度 rows: 10, // 行數 columns: 7, // 列數 iconNum: 6, // 圖標數 gameArea: { // 遊戲區域 x: 25, y: 300 }, movingX: 'x', movingY: 'y', } let gameAreaW = gameOptions.iconW * gameOptions.columns let gameAreaH = gameOptions.iconH * gameOptions.rows class PlayGame extends Phaser.Scene { constructor() { super('PlayGame') } preload() { this.load.spritesheet('sprite', 'sprite.png', { frameWidth: gameOptions.iconW, frameHeight: gameOptions.iconH }) } create() { } } let gameConfig = { type: Phaser.AUTO, scale: { mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT, autoCenter: Phaser.Scale.CENTER_BOTH, parent: 'game', width: 750, height: 1464 }, scene: PlayGame } game = new Phaser.Game(gameConfig) </script> </body> </html>
1. 設置遊戲區域容器spa
class PlayGame extends Phaser.Scene { create() { this.itemContainer = this.add.container(gameOptions.gameArea.x, gameOptions.gameArea.y) let mask = this.make.graphics() mask.fillStyle(0x00ffff, 1); mask.fillRect(gameOptions.gameArea.x, gameOptions.gameArea.y, gameOptions.iconW * gameOptions.columns, gameOptions.iconH * gameOptions.rows) this.itemContainer.setMask(new Phaser.Display.Masks.GeometryMask(this, mask)) this.itemArr = [] // 用來存放全部的小球 } }
2. 放置小球code
class PlayGame extends Phaser.Scene { create() { this.putSpirte() } putSpirte() { for (let i = 0; i < gameOptions.columns; i++) { this.itemArr[i] = [] for (let j = 0; j < gameOptions.rows; j++) { let iconX = gameOptions.iconW * i let iconY = gameOptions.iconH * j let type = Phaser.Math.Between(0, gameOptions.iconNum - 1) let icon = this.add.sprite(iconX, iconY, 'sprite', type) icon.setOrigin(0, 0) icon.rows = j icon.columns = i icon.type = type this.itemArr[i][j] = icon this.itemContainer.add(icon) } } } }
3. 拖動小球htm
3.1 檢測觸碰區域獲取觸碰位置遊戲
class PlayGame extends Phaser.Scene { checkArea(pointer) { let gameArea = new Phaser.Geom.Rectangle(gameOptions.gameArea.x, gameOptions.gameArea.y, gameOptions.columns * gameOptions.iconW, gameOptions.rows * gameOptions.iconH) return Phaser.Geom.Rectangle.Contains(gameArea, pointer.x, pointer.y) } getPos(pointer) { let col = (pointer.downX - gameOptions.gameArea.x) / gameOptions.iconW let row = (pointer.downY - gameOptions.gameArea.y) / gameOptions.iconH this.curRow = Math.floor(row) this.curCol = Math.floor(col) } }
3.2 監聽拖動
class PlayGame extends Phaser.Scene { create() { this.input.on('pointerdown', this.startHandler, this) this.input.on('pointermove', this.moveHandler, this) this.input.on('pointerup', this.endHandler, this) } startHandler(pointer) { this.inArea = this.checkArea(pointer) if (this.inArea) { this.getPos(pointer) this.movingDir = null // 移動方向 this.moveNum = 0 // 移動個數 this.offset = 0 // 偏移量 this.dir = 1 // 正負方向,1: 正方向, -1: 負方向 } } moveHandler(pointer) { if (!this.inArea) return true let vector = new Phaser.Math.Vector2(pointer.x - pointer.downX, pointer.y - pointer.downY) // 移動向量 this.movingItem = [] // 存放移動的小球 if (this.movingDir) { if (this.movingDir === gameOptions.movingY) { // y 方向移動 this.dir = vector.y > 0 ? 1 : -1 // 判斷正負方向 this.itemArr.map((col, colIndex) => { if (colIndex === this.curCol) { col.map((item, index) => { this.movingItem.push(item) item.y = (gameAreaH + (index * gameOptions.iconH + vector.y) % gameAreaH) % gameAreaH // 銜接滑動 }) } }) this.offset = Math.abs(vector.y) % gameOptions.iconH this.moveNum = (Math.floor(Math.abs(vector.y) / gameOptions.iconH) + (this.offset < 50 ? 0 : 1)) % gameOptions.rows } else if (this.movingDir === gameOptions.movingX) { // x 方向移動 this.itemArr.map(item => { this.movingItem.push(item[this.curRow]) }) this.dir = vector.x > 0 ? 1 : -1 // 判斷正負方向 this.itemArr.map((col, colIndex) => { col.map((item, index) => { if (index === this.curRow) { item.x = (gameAreaW + (colIndex * gameOptions.iconW + vector.x) % gameAreaW) % gameAreaW // 銜接滑動 } }) }) this.offset = Math.abs(vector.x) % gameOptions.iconW this.moveNum = (Math.floor(Math.abs(vector.x) / gameOptions.iconW) + (this.offset < 50 ? 0 : 1)) % gameOptions.columns } } else { // 判斷移動方向 let angle = vector.angle() if (angle >= Math.PI / 4 && angle <= Math.PI * 3 / 4 || angle >= Math.PI * 5 / 4 && angle <= Math.PI * 7 / 4) { this.movingDir = gameOptions.movingY } else { this.movingDir = gameOptions.movingX } } } endHandler(pointer) { this.inArea = false if (this.movingDir === gameOptions.movingY) { this.tweens.add({ targets: [...this.movingItem], duration: 200, y: (target, name, value) => { let ret = value + (this.offset > 50 ? this.dir * (gameOptions.iconH - this.offset) : -(this.dir * this.offset)) if (Math.abs(ret) === gameAreaH) { this.overflowItem = target } return ret }, onComplete: () => { this.overflowItem && (this.overflowItem.y %= gameAreaH) } }) } else if (this.movingDir === gameOptions.movingX) { this.tweens.add({ targets: [...this.movingItem], duration: 200, x: (target, name, value) => { let ret = value + (this.offset > 50 ? this.dir * (gameOptions.iconW - this.offset) : -(this.dir * this.offset)) if (Math.abs(ret) === gameAreaW) { this.overflowItem = target } return ret }, onComplete: () => { this.overflowItem && (this.overflowItem.x %= gameAreaW) } }) } } }
3.3 頭/尾增長一個臨時小球,讓拖動看起來更順暢
class PlayGame extends Phaser.Scene { create() { this.tempItem = this.add.sprite(0, 0, 'sprite', 0).setAlpha(0) this.itemContainer.add(this.tempItem) } moveHandler(pointer) { if (this.movingDir) { if (this.movingDir === gameOptions.movingY) { this.itemArr.map((col, colIndex) => { if (colIndex === this.curCol) { if (this.dir > 0) { this.tempItem.setFrame(this.movingItem[this.movingItem.length - 1 - Math.floor(vector.y % gameAreaH / gameOptions.iconH)].type) this.tempItem.x = this.movingItem[this.movingItem.length - 1].x this.tempItem.y = vector.y % gameOptions.iconH - gameOptions.iconH this.tempItem.setAlpha(1) this.tempItem.setOrigin(0) } else if (this.dir < 0) { this.tempItem.setFrame(this.movingItem[Math.floor(Math.abs(vector.y % gameAreaH) / gameOptions.iconH)].type) this.tempItem.x = this.movingItem[0].x this.tempItem.y = vector.y % gameOptions.iconH this.tempItem.setAlpha(1) this.tempItem.setOrigin(0) } } }) } else if (this.movingDir === gameOptions.movingX) { this.itemArr.map((col, colIndex) => { col.map((item, index) => { if (index === this.curRow) { if (this.dir > 0) { this.tempItem.setFrame(this.movingItem[this.movingItem.length - 1 - Math.floor(vector.x % gameAreaW / gameOptions.iconW)].type) this.tempItem.y = this.movingItem[0].y this.tempItem.x = vector.x % gameOptions.iconW - gameOptions.iconW this.tempItem.setAlpha(1) this.tempItem.setOrigin(0) } else if (this.dir < 0) { this.tempItem.setFrame(this.movingItem[Math.floor(Math.abs(vector.x) % gameAreaW / gameOptions.iconW)].type) this.tempItem.y = this.movingItem[0].y this.tempItem.x = vector.x % gameOptions.iconW this.tempItem.setAlpha(1) this.tempItem.setOrigin(0) } } }) }) } } } endHandler(pointer) { if (this.movingDir === gameOptions.movingY) { this.tweens.add({ targets: [this.tempItem, ...this.movingItem], duration: 200, y: (target, name, value) => { let ret = value + (this.offset > 50 ? this.dir * (gameOptions.iconH - this.offset) : -(this.dir * this.offset)) if (Math.abs(ret) === gameAreaH) { this.overflowItem = target } return ret }, onComplete: () => { this.tempItem.setAlpha(0) this.overflowItem && (this.overflowItem.y %= gameAreaH) } }) } else if (this.movingDir === gameOptions.movingX) { this.tweens.add({ targets: [this.tempItem, ...this.movingItem], duration: 200, x: (target, name, value) => { let ret = value + (this.offset > 50 ? this.dir * (gameOptions.iconW - this.offset) : -(this.dir * this.offset)) if (Math.abs(ret) === gameAreaW) { this.overflowItem = target } return ret }, onComplete: () => { this.tempItem.setAlpha(0) this.overflowItem && (this.overflowItem.x %= gameAreaW) } }) } } }
3.4 移動後重置位置
class PlayGame extends Phaser.Scene { endHandler(pointer) { this.resetPos() } resetPos() { if (this.movingDir === gameOptions.movingY) { let ahaedNum = (gameOptions.rows + this.dir * this.moveNum) % gameOptions.rows this.movingItem.map((item, index) => { item.columns = this.curCol item.rows = (index + ahaedNum) % gameOptions.rows this.itemArr[this.curCol][(index + ahaedNum) % gameOptions.rows] = item }) } else if (this.movingDir === gameOptions.movingX) { let ahaedNum = (gameOptions.columns + this.dir * this.moveNum) % gameOptions.columns this.movingItem.map((item, index) => { item.columns = (index + ahaedNum) % gameOptions.columns item.rows = this.curRow this.itemArr[(index + ahaedNum) % gameOptions.columns][this.curRow] = item }) } } }
3.5 檢測圖案
class PlayGame extends Phaser.Scene { endHandler(pointer) { this.traversal() } traversal() { this.traversalCol() this.traversalRow() } traversalCol() { let matching = false // 和上一個小球類型是否相同 let lastType = -1 // 上一個小球的類型 let matchArr = [] let col let item // 判斷列 for (let colIndex in this.itemArr) { col = this.itemArr[colIndex] lastType = -1 matchArr = [] for (let index in col) { item = col[index] matchArr.push(item) if (lastType === item.type) { !matching && matchArr.unshift(col[index - 1]) matching = true if (+index === gameOptions.rows - 1) { if (matchArr.length >= 3) { this.checkMatchCol(matchArr, col, colIndex) return } } } else { matchArr.pop() matching = false if (matchArr.length >= 3) { this.checkMatchCol(matchArr, col, colIndex) return } matchArr = [] } lastType = item.type } } } traversalRow() { let lastType = -1 let matchArr = [] let matching = false let item for (let row = 0; row < gameOptions.rows; row++) { lastType = -1 for (let col = 0; col < gameOptions.columns; col++) { item = this.itemArr[col][row] matchArr.push(item) if (lastType === item.type) { !matching && matchArr.unshift(this.itemArr[col - 1][row]) matching = true if (row === gameOptions.rows - 1) { if (matchArr.length >= 3) { this.checkMatchRow(matchArr) return } } } else { matchArr.pop() matching = false if (matchArr.length >= 3) { this.checkMatchRow(matchArr) return } matchArr = [] } lastType = item.type } } } checkMatchCol(arr, col, colIndex) { this.matchCol = true this.movedItemCol = [] if (arr[0].rows !== 0) { let bottomRow = arr[arr.length - 1].rows for (let i = arr[0].rows; i > 0; i--) { // 下移的球 this.movedItemCol.push(col[i - 1]) this.itemArr[colIndex][bottomRow--] = col[i - 1] this.itemArr[colIndex][bottomRow + 1].columns = colIndex this.itemArr[colIndex][bottomRow + 1].rows = bottomRow + 1 } } let len = arr.length for (let i = 0; i < arr.length; i++) { // 消失的球 let iconX = parseInt(arr[0].columns) * 100 let iconY = -1 * gameOptions.iconH * (i + 1) let type = Phaser.Math.Between(0, gameOptions.iconNum - 1) let icon = this.add.sprite(iconX, iconY, 'sprite', type) this.itemContainer.add(icon) icon.setOrigin(0, 0) icon.rows = --len icon.columns = colIndex icon.type = type this.itemArr[colIndex][icon.rows] = icon this.movedItemCol.push(icon) icon.x = iconX icon.y = iconY } len = arr.length this.tweens.add({ targets: arr, duration: 400, alpha: 0, onComplete: () => { this.tweens.add({ targets: this.movedItemCol, duration: 300, y: (target, name, value) => { return value + (len * 100) }, onComplete: () => { this.traversal() } }) } }) } checkMatchRow(arr) { this.matchRow = true this.movedItemRow = [] let rowIndex = arr[0].rows let bottomRow = rowIndex let range = parseInt(arr[0].columns) + parseInt(arr.length) for (let col = arr[0].columns; col < range; col++) { bottomRow = rowIndex for (let j = 0; j < rowIndex; j++) { this.itemArr[col][bottomRow] = this.itemArr[col][--bottomRow] this.itemArr[col][bottomRow].rows += 1 this.movedItemRow.push(this.itemArr[col][bottomRow]) } let iconX = this.itemArr[col][1].x let iconY = -100 let type = Phaser.Math.Between(0, gameOptions.iconNum - 1) let icon = this.add.sprite(iconX, iconY, 'sprite', type) this.itemContainer.add(icon) icon.setOrigin(0, 0) icon.rows = 0 icon.columns = col icon.type = type this.itemArr[col][0] = icon this.movedItemRow.push(icon) icon.x = iconX icon.y = iconY } this.tweens.add({ targets: arr, duration: 400, alpha: 0, onComplete: () => { this.tweens.add({ targets: this.movedItemRow, duration: 300, y: (target, name, value) => { return value + 100 }, onComplete: () => { this.traversal() } }) } }) } }
3.6 每次只能消除一處
class PlayGame extends Phaser.Scene { checkMatchCol(arr, col, colIndex) { if (this.matchRow) return this.matchCol = true this.tweens.add({ targets: arr, duration: 400, alpha: 0, onComplete: () => { this.tweens.add({ targets: this.movedItemCol, duration: 300, y: (target, name, value) => { return value + (len * 100) }, onComplete: () => { this.matchCol = false this.traversal() } }) } }) } checkMatchRow(arr) { if (this.matchCol) return this.matchRow = true this.tweens.add({ targets: arr, duration: 400, alpha: 0, onComplete: () => { this.tweens.add({ targets: this.movedItemRow, duration: 300, y: (target, name, value) => { return value + 100 }, onComplete: () => { this.matchRow = false this.traversal() } }) } }) } }
3.7 消除時不能移動
class PlayGame extends Phaser.Scene { moveHandler(pointer) { if (this.matchRow || this.matchCol) return true } }
3.8 初始化檢測
class PlayGame extends Phaser.Scene { putSprite(pointer) { this.traversal() } }
至此,整個遊戲咱們就作完啦!???
前端phaser公衆號