import * as PIXI from "pixi.js"; import "pixi-plugin-bump"; const { Application, Sprite, Container } = PIXI; const { TextureCache } = PIXI.utils; import { loaderRes } from "./util"; import createGroundData from "./groudData"; const GAME_RESULT_STATE = { ON: "游戏开始", OVER: "游戏失败", SUCC: "通关完成" }; const ROLESTATE = { READY: "READY", RUN: "RUN", JUMP: "JUMP", DOWN: "DOWN" }; class Game { constructor(wrapper) { this.wrapper = wrapper; //外层element this.stageWidth = 750; this.stageHeight = 1334; this.app = null; this.bump = null; //碰撞检测实例 this.gameState = GAME_RESULT_STATE.ON; //游戏状态 this._roleState = ROLESTATE.READY; //人物状态 this.roleReadyStep = 0; //人物准备状态的当前帧数 this.roleReadyTimer = null; //人物准备动画的计时器 this.roleRunStep = 0; //人物跑动状态的当前帧数 this.roleRunTimer = null; // 人物跑动动画的计时器 this.fallingDown = false; //下坠状态 //地面 this.groundData = null; //地面构造数据 this.groundArr = []; //地面元素数组 this.totalWidth = 0; //总宽度,主要用于布局 //倒计时 this.uiCountdownTimes = 3; //倒计时次数 3 2 1 开始 this.uiCountdownTimer = null; //倒计时计时器 //ui this.uiRole = null; //人物sprite this.uiuiGroundContainer = null; //地面总容器 this.uiGameTopTip = null; //游戏顶部提示 this.uiCountdown = null; //游戏开始倒计时 this.uiFireworksContanier = null; //烟花容器 this.fireworkOpts = { green: { delay: 0, pos: { x: 588, y: 230 } }, orange: { delay: 300, pos: { x: 290, y: 375 } }, purple: { delay: 500, pos: { x: 400, y: 200 } }, red: { delay: 600, pos: { x: 450, y: 370 } } }; this.decibelArr = []; //声贝队列 this.decibel = 0; //声贝峰值 this.init(); this.initProxy(); } //底部小游戏 init() { this.app = new Application({ width: this.stageWidth, height: this.stageHeight, transparent: true }); this.wrapper.appendChild(this.app.view); this.bump = new PIXI.extras.Bump(); loaderRes(["/scene.json"], this.loadedSource, this); } //roleState代理 initProxy() { Object.defineProperty(this, "roleState", { configurable: true, enumerable: true, get: () => { return this._roleState; }, set: value => { if (this._roleState === value) return; this._roleState = value; this.clearRoleStateEffect(); switch (value) { case ROLESTATE.READY: this.roleReadyStep = 0; this.roleReadyLoop(); break; case ROLESTATE.RUN: this.roleRunStep = 0; this.roleRunLoop(); break; case ROLESTATE.JUMP: this.uiRole.texture = TextureCache[`p_jump_0.png`]; break; case ROLESTATE.DOWN: this.uiRole.texture = TextureCache[`p_jump_1.png`]; break; } } }); } clearRoleStateEffect() { clearTimeout(this.roleReadyTimer); clearTimeout(this.roleRunTimer); } roleReadyLoop() { this.roleReadyTimer = setTimeout(() => { this.uiRole.texture = TextureCache[`p_stand_${this.roleReadyStep % 2}.png`]; this.roleReadyStep++; this.roleReadyLoop(); }, 500); } roleRunLoop() { this.roleRunTimer = setTimeout(() => { this.uiRole.texture = TextureCache[`p_run_${this.roleRunStep % 2}.png`]; this.roleRunStep++; this.roleRunLoop(); }, 100); } loadedSource() { this.initGameReady(); this.initGround(); this.initRole(); this.initFireworks(); this.readyStart(); } //开始倒计时 initGameReady() { this.uiGameTopTip = Sprite.from("word_tips.png"); this.uiGameTopTip.anchor.x = 0.5; this.uiGameTopTip.x = 375; this.uiGameTopTip.y = 20; this.app.stage.addChild(this.uiGameTopTip); this.uiCountdown = Sprite.from("word_3.png"); this.uiCountdown.anchor.x = 0.5; this.uiCountdown.anchor.y = 0.5; this.uiCountdown.x = 375; this.uiCountdown.y = 400; this.app.stage.addChild(this.uiCountdown); } // 创建地面容器 initGround() { this.groundData = createGroundData(); this.groundArr = []; this.totalWidth = 0; this.uiGroundContainer = new Container(); this.uiGroundContainer._vx = 2.5; this.app.stage.addChild(this.uiGroundContainer); this.groundData.forEach(this.createGround.bind(this)); this.totalWidth = this.uiGroundContainer.width; } //生成地面迭代器函数 createGround(groundData, index, arr) { let { height, blockNum, gap } = groundData; this.totalWidth += gap; const groundUnitContainer = new Container(); groundData.x = groundUnitContainer.x = this.totalWidth; groundData.y = groundUnitContainer.y = this.stageHeight - height + 24; //草地高度除外 groundData.width = blockNum * 100; groundUnitContainer.cacheAsBitmap = true; let totalX = 0; for (let i = 0; i < blockNum; i++) { const ground = Sprite.fromFrame( `bg_${i === 0 ? "l" : i === blockNum - 1 ? "r" : "m"}.png` ); ground.x = totalX; ground.y = -24; //负值,刚好贴着地面。方便碰撞检测 groundUnitContainer.addChild(ground); totalX += ground.width; } this.totalWidth += groundData.width; this.groundArr.push(groundUnitContainer); this.uiGroundContainer.addChild(groundUnitContainer); //最后一个,需要添加通关门UI if (index === arr.length - 1) { let uiCastle = Sprite.from("castle.png"); uiCastle.anchor.x = 0.7; uiCastle.anchor.y = 1; uiCastle.x = totalX; groundUnitContainer.addChild(uiCastle); } } //创建角色 initRole() { this.uiRole = Sprite.fromFrame("people.png"); this.uiRole.x = this.stageWidth / 2; this.uiRole.y = this.stageHeight - this.groundData[0].height + 24; //初始化位置 this.uiRole._vy = 400 / 60; this.uiRole.anchor.x = 0.5; this.uiRole.anchor.y = 1; this.app.stage.addChild(this.uiRole); this.roleState = ROLESTATE.READY; } //生成烟花 initFireworks() { this.uiFireworksContanier = new Container(); Object.keys(this.fireworkOpts).forEach(key => { let firework = this.fireworkOpts[key]; firework.frameFirework = 0; //下标 firework.timer = null; //计时器 let uiFirework = Sprite.from( `fireworks_${key}_${firework.frameFirework}.png` ); //ui uiFirework.anchor.x = 0.5; uiFirework.anchor.y = 0.5; uiFirework.x = firework.pos.x; uiFirework.y = firework.pos.y; firework.uiFirework = uiFirework; this.uiFireworksContanier.addChild(uiFirework); }); this.uiFireworksContanier.visible = false; this.app.stage.addChild(this.uiFireworksContanier); } //开始倒计时 readyStart() { this.uiCountdownTimes = 3; this.uiCountdown.texture = TextureCache[`word_3.png`]; this.uiCountdown.visible = true; this.countDownLoop(); } countDownLoop() { this.uiCountdownTimer = setTimeout(() => { this.uiCountdownTimes--; if (this.uiCountdownTimes === 0) { this.uiCountdown.texture = TextureCache[`word_start.png`]; this.gameStart(); } else if (this.uiCountdownTimes < 0) { clearTimeout(this.uiCountdownTimer); this.uiCountdown.visible = false; } else { this.uiCountdown.texture = TextureCache[`word_${this.uiCountdownTimes}.png`]; } this.countDownLoop(); }, 1000); } gameStart() { this.roleState = ROLESTATE.RUN; this.groundWalk(); } restartGame() { console.log("restartGame"); this.gameState = GAME_RESULT_STATE.ON; this.uiGroundContainer.x = 0; this.uiRole.x = 375; this.uiRole.y = this.groundData[0].y; this.gameResultContainer.visible = false; this.decibel = 0; this.roleState = ROLESTATE.RUN; this.app.ticker.start(); } //每帧多要去判断人物状态 walkLoop() { this.uiGroundContainer.x -= this.uiGroundContainer._vx; let rolePosX = Math.abs(this.uiGroundContainer.x - 375); //人物所在位置 let rolePosY = this.uiRole.y; if (rolePosX >= this.totalWidth - 375) { console.log("完成游戏"); this.gameSucc(); return; } else if (rolePosY >= this.stageHeight) { console.log("掉坑里挂了"); this.gameOver(); return; } //寻找人物是否在地面上方。如果不在,就寻找下一个可能会碰撞的地面 let index = this.groundData.findIndex( ground => rolePosX >= ground.x && rolePosX <= ground.x + ground.width ); if (index === -1) { /** * 掉坑里了 * 如果不是跳起状态,默认下坠 * */ console.log("在坑里的范围内"); if ((this.roleState !== ROLESTATE.JUMP && this.roleState !== ROLESTATE.DOWN) && !this.fallingDown) { this.fallDown(); } //寻找下一个地面作为碰撞检测 index = this.groundData.findIndex(ground => rolePosX <= ground.x); } if (index >= 0) { /** * 人物下方的方块下标 * 判断是否和方块有碰撞 */ let ground = this.groundArr[index]; if (ground.y <= rolePosY) { let collision = this.bump.hit( this.uiRole, ground, true, true, true ); console.log("collision", collision); if (collision) { switch (collision) { case "bottom": console.log("站上了"); this.standGround(); break; default: console.log("撞上了"); this.gameOver(); break; } } } } } //站在地面上 standGround() { this.decibel = 0; this.decibelArr = []; this.roleState = ROLESTATE.RUN; this.fallingDown = false; this.app.ticker.remove(this.downLoop, this); } //最后即将通关时候,人物开始走动,而不是背景移动。 roleWalk() { this.app.ticker.add(this.roleWalkLoop, this); } //人物停止走动 roleStop() { this.app.ticker.remove(this.roleWalkLoop, this); } //人物走动帧函数 roleWalkLoop() { this.uiRole.x += 2.5; if (this.uiRole.x >= 750 + this.uiRole.width / 2) { console.log("人物通关"); this.roleStop(); this.startFireworks(); } } //背景移动,人物不动。 groundWalk() { this.app.ticker.add(this.walkLoop, this); } //背景停止移动 groundStop() { this.app.ticker.remove(this.walkLoop, this); } /** * * @param {number} decibel 声贝差值 * 取声贝峰值算法 * 1.如果声贝大于设置值,一旦呈下降趋势,则去当前峰值。 * 2.如果声贝一直增长,取第30帧的值。 */ jump(decibel) { if (this.gameState === GAME_RESULT_STATE.OVER) return; if ( this.roleState === ROLESTATE.JUMP || this.roleState === ROLESTATE.DOWN || this.roleState === ROLESTATE.READY ) { return; } console.log(this.decibelArr); if (this.decibelArr.length >= 1) { if (this.decibelArr.length >= 30) { this.decibel = decibel * 10; this.startJump(); } else { let isAscend = this.decibelArr.every(item => decibel >= item); if (isAscend) { this.decibelArr.push(decibel); } else { this.decibel = this.decibelArr.pop() * 10; this.startJump(); } } } else { this.decibelArr.push(decibel); } } startJump() { console.log("startJump"); this.app.ticker.add(this.jumpLoop, this); this.fallingDown = false; this.app.ticker.remove(this.fallDownLoop, this); this.app.ticker.remove(this.downLoop, this); } jumpLoop() { if (this.decibel > 0) { let distance = Math.min(this.uiRole._vy, this.decibel); this.uiRole.y -= distance; this.decibel -= distance; console.log("distance,decibel", distance, this.decibel); this.roleState = ROLESTATE.JUMP; if (this.decibel === 0) { this.app.ticker.remove(this.jumpLoop, this); this.app.ticker.add(this.downLoop, this); } } } downLoop() { this.roleState = ROLESTATE.DOWN; this.uiRole.y += this.uiRole._vy; } fallDownLoop() { this.uiRole.y += this.uiRole._vy; } fallDown() { this.fallingDown = true; this.roleState = ROLESTATE.DOWN; this.app.ticker.add(this.fallDownLoop, this); } startFireworks() { this.uiFireworksContanier.visible = true; Object.keys(this.fireworkOpts).forEach(key => { let firework = this.fireworkOpts[key]; firework.frameFirework = 0; clearInterval(firework.timer); setTimeout(() => { firework.timer = setInterval(() => { firework.uiFirework.visible = firework.frameFirework <= 6; if (firework.frameFirework <= 6) { firework.uiFirework.texture = TextureCache[ `fireworks_${key}_${firework.frameFirework}.png` ]; } firework.frameFirework = (firework.frameFirework + 1) % 12; }, 100); }, firework.delay); }); } gameOver() { this.roleState = ROLESTATE.READY; this.fallingDown = false; this.app.ticker.remove(this.fallDownLoop, this); this.gameState = GAME_RESULT_STATE.OVER; this.app.ticker.stop(); } gameSucc() { this.groundStop(); this.roleWalk(); } } export default Game;