/** @format */ import {Ball} from './Ball' import {ConstValue} from '../data/ConstValue' import {ZumaRole} from './ZumaRole' import {IZumaConfig, ZumaConfig} from '../config/ZumaConfig' import {ZumaUI} from '../ui/ZumaUI' import {Data} from '../GameControl' import {ccUtils} from '../utils/ccUtils' import {Bezier} from './Bezier' import {ZumamapConfig} from '../config/ZumamapConfig' import {GAME_PREFAB_TYPE, GAME_ROLE_TIP, GOODS} from '../enums/Enum' import {node, observer} from '../mobx/observer' import {ZumabuffConfig} from '../config/ZumabuffConfig' const {ccclass, property} = cc._decorator enum GAME_STATE { none, rollIn, run, rollOut, gamePass, } enum BORN_TYPE { random = 1, normal, } @ccclass @observer export default class ZumaCore extends cc.Component { @node('startImg') private startImgNode: cc.Node @node('endImg') private endImgNode: cc.Node @node('buff') private buffNode: cc.Node private ballPrefab: cc.Prefab = null //小球的节点池 private ballPool: cc.NodePool //小球的父节点 private ballParent: cc.Node //发射出来的小球 private ballShoot: any[] = [] //做小球的挤入效果时存储需要被检测碰撞的小球 private ballCrushed: Ball[][] = [] //被吸引回退的第一个球的数组 private ballAttracted: Ball[] = [] //最大连击数 private maxCombo: number = 0 //当前连击数 private curCombo: number = 0 //游戏是否结束 private canShoot: boolean = true //计算额外加分的时候要用到 private lastId: number /** 存储球链中所有的小球 */ private rollBalls: Ball[] = [] //开始滚入的小球的个数 private rollInCount: number = 35 //游戏中球的总数 private rollInTotalCount: number = 100 //球的大小 必须是 stepLength 的倍数 private ballSize: number = 64 //步长 private stepLength: number = 2 //一个球的坐标间距 private ballStep: number = 0 //起始下标 private startIndex: number = 0 //结束下标 越大实现快速缩进 private endIndex: number = 8 /** 角色 */ private role: ZumaRole //射速 private shootSpeed: number = 20 /** 关卡数据 */ public levelCfg: IZumaConfig //UI public zumaUI: ZumaUI //游戏状态 public gameSate: GAME_STATE //当前颜色数组 public curBallIDs: number[] buffPool: cc.NodePool protected onLoad() { this.node.on(cc.Node.EventType.TOUCH_START, this.onBattleAttack, this) this.ballParent = cc.find('balls', this.node) this.buffPool = new cc.NodePool() } protected update(dt: number) { switch (this.gameSate) { case GAME_STATE.rollIn: this.ballRollIn() break case GAME_STATE.run: //运行发射球 this.onShootBallMove() //检测发射球的碰撞 let isCrush = false isCrush = this.checkPushCollision() //吸附小球 let isAttract = false if (!isCrush) isAttract = this.attract() //小球向前滚动 if (!isCrush || !isAttract) this.ballRollComplete() break case GAME_STATE.rollOut: this.ballRollOut() break } } public init(role: ZumaRole, zumaUI: ZumaUI): void { this.ballStep = this.ballSize / this.stepLength this.role = role this.zumaUI = zumaUI this.ballPrefab = Data.game.gameAssetMap.get('prefab/zuma/ball') as cc.Prefab this.setBuffNum() } public startGame() { this.unscheduleAllCallbacks() if (this.levelCfg.bornType == BORN_TYPE.random) { this.curBallIDs = [...this.levelCfg.colorBall] this.curBallIDs.outOrder() } else if (this.levelCfg.bornType == BORN_TYPE.normal) { this.curBallIDs = [...this.levelCfg.colorBall] } this.gameSate = GAME_STATE.none this.ballShoot.length = 0 this.ballCrushed.length = 0 this.ballAttracted.length = 0 this.initBall() this.role.init(this) } public initData(): void { this.levelCfg = ZumaConfig[Data.game.stageRound] this.rollInCount = this.levelCfg.rollInCount this.rollInTotalCount = this.levelCfg.rollInTotal.length Data.zuma.mapData.length = 0 let mapCfg = ZumamapConfig[this.levelCfg.map] let mapData = mapCfg.mapData for (let i = 0; i < mapData.length; i++) { let tmp = mapData[i] let startX = i == 0 ? tmp[0] : i == 1 ? mapData[i - 1][4] : mapData[i - 1][2] let startY = i == 0 ? tmp[1] : i == 1 ? mapData[i - 1][5] : mapData[i - 1][3] let controlX = tmp[i == 0 ? 2 : 0] let controlY = tmp[i == 0 ? 3 : 1] let endX = tmp[i == 0 ? 4 : 2] let endY = tmp[i == 0 ? 5 : 3] let steps = Bezier.init( cc.v2(startX, startY), cc.v2(controlX, controlY), cc.v2(endX, endY), this.stepLength, ) for (let m = 1; m <= steps; ++m) { let data = Bezier.getAnchorPoint(m) Data.zuma.mapData.push(data) } } this.startImgNode.x = Data.zuma.mapData[0][0] this.startImgNode.y = Data.zuma.mapData[0][1] this.startImgNode.angle = Data.zuma.mapData[0][2] + 90 this.endImgNode.x = Data.zuma.mapData[Data.zuma.mapData.length - 1][0] this.endImgNode.y = Data.zuma.mapData[Data.zuma.mapData.length - 1][1] let nodes = ccUtils.instantChildren( cc.find('mapPoints/route_point', this.node), Math.floor(Data.zuma.mapData.length / this.ballStep) - 1, ) nodes.forEach((node, i) => { node.x = Data.zuma.mapData[(i + 1) * this.ballStep][0] node.y = Data.zuma.mapData[(i + 1) * this.ballStep][1] }) } /** 发射球 */ private onBattleAttack(e): void { if (!this.role.canShoot || !this.canShoot || this.gameSate != GAME_STATE.run || this.zumaUI.curBalls < 1) return //更新角色旋转角度 this.updateRoleAngle(e) this.shootBall() } /** 更新角色角度 */ private updateRoleAngle(e): void { let pos = ccUtils.convertWorldPosToNode(this.node, ccUtils.convertTouchPosToWorld(e)) let dir = pos.sub(cc.v2(this.role.node.position)).normalize() let angle = Math.atan2(dir.y, dir.x) this.role.node.angle = cc.misc.radiansToDegrees(angle) - 90 } /** 初始化第一个小球,并开始滚入小球 */ private initBall() { if (!this.ballPool) this.ballPool = new cc.NodePool() this.rollBalls.length = 0 let ball: Ball = this.getBall() ball.node.parent = this.ballParent this.rollBalls.unshift(ball) //将数组的第this.startIndex个位置设为起点 ball.posIndex = this.startIndex this.gameSate = GAME_STATE.rollIn } /** 开始滚入小球 */ private ballRollIn() { if (this.rollBalls.length < this.rollInCount) { for (let i: number = 0; i < this.rollBalls.length; ++i) { //所有小球移动rollSpeed个位置 this.rollBalls[i].posIndex += this.levelCfg.rollSpeed * 2 } //第一个球距起点this.startIndex个位置则补充小球进去 if (this.rollBalls[0].posIndex == this.ballStep + this.startIndex) { let tmpBall: Ball = this.getBall() this.rollBalls.unshift(tmpBall) tmpBall.node.parent = this.ballParent tmpBall.posIndex = this.startIndex } } else { //滚入小球完毕 //发射器初始化 this.role.initBall() //TODO 停止滚动声音 // soundMgr.stopRollingSound(); //开始正常游戏阶段 this.gameSate = GAME_STATE.run } } /** 滚入小球结束后,由该函数驱动小球向前滚动 */ private ballRollComplete() { //与第一个小球相连的小球都向前滚动一步 if (this.rollBalls.length != 0) { this.pushBallFrom(0, this.levelCfg.rollSpeed) //如果总球数不为0并且第一个球距第一个点位置相差startIndex则补充小球 if ( this.rollBalls[0] && this.rollBalls[0].posIndex == this.ballStep + this.startIndex && this.rollInTotalCount != 0 ) { let _ball: Ball = this.getBall() this.rollBalls.unshift(_ball) _ball.node.parent = this.ballParent _ball.posIndex = this.startIndex } } } /** 获取一个小球 */ public getBall(): Ball { if (this.rollInTotalCount <= 0) { return null console.error('rollInTotalCount is zero') } let ball = this.getBallFromPool() ball.init( this.curBallIDs[this.levelCfg.rollInTotal[this.levelCfg.rollInTotal.length - this.rollInTotalCount] - 1], this, ) --this.rollInTotalCount return ball } public getBallFromPool(): Ball { //node的parent赋值是异步的,要给对象池中的对象加标记,否则会出问题 let ballNode: cc.Node = this.ballPool.get() let ball = ballNode?.getComponent(Ball) if (!ballNode) { ballNode = cc.instantiate(this.ballPrefab) ball = ballNode.getComponent(Ball) } ballNode.stopAllActions() ballNode.scale = 1 return ball } public putBall(ball: Ball): void { if (this.ballPool.size() < 50) this.ballPool?.put(ball.node) } /** * 推进小球的函数,传入参数推进的起始位置,推进的步数(为正即为向前推进,为负即为向后回退) * 在使用回退道具时要传入负参数 */ public pushBallFrom(index: number, step: number): void { let temp: Ball[] = [] temp.push(this.rollBalls[index]) //算出所有与ballArray中索引为index的小球"相连"的小球 for (let i: number = index; i < this.rollBalls.length - 1; ++i) { if (this.rollBalls[i + 1].posIndex - this.rollBalls[i].posIndex <= this.ballStep) { //如小球位置有误差则纠正 if (this.rollBalls[i + 1].posIndex - this.rollBalls[i].posIndex < this.ballStep) { this.rollBalls[i + 1].posIndex = Math.min( this.rollBalls[i].posIndex + this.ballStep, Data.zuma.mapData.length - 1, ) } temp.push(this.rollBalls[i + 1]) } else { break } } //然后将其移动指定的步数 for (let j: number = temp.length - 1; j >= 0; j--) { if (temp[j].posIndex + step > Data.zuma.mapData.length - 1) { this.putBall(temp[j]) this.rollBalls.splice(this.rollBalls.indexOf(temp[j]), 1) //暂时不回收球 // if (this.rollBalls.indexOf(temp[j]) == this.rollBalls.length - 1) { // this.gameSate = GAME_STATE.rollOut // } } else { temp[j].posIndex += step } } //每次推动小球都需要判断是否结束游戏 if (this.gameSate == GAME_STATE.run) { if (this.rollBalls.length <= 0) { this.gamePass() } } } /** 游戏结束,小球进终点后消除 */ public ballRollOut(): void { //放入正在发射的小球 this.ballShoot.forEach(v => this.putBall(v[0])) for (let i: number = this.rollBalls.length - 1; i >= 0; --i) { if (this.rollBalls[i].posIndex > Data.zuma.mapData.length - this.endIndex - 1) { this.putBall(this.rollBalls[i]) this.rollBalls.splice(i, 1) if (this.rollBalls.length == 0) { //TODO 播放游戏胜利音效 // soundMgr.stopGameOverSound(); } } else { this.rollBalls[i].posIndex += this.endIndex } } } /** 开始射击 */ public shootBall(): void { //发射球时的弧度 let roleNode = this.role.node let radian: number = ccUtils.angle2radian(roleNode.angle + 90) let ball: Ball = this.role.getCurrentBall() ball.node.parent = this.ballParent ball.node.position = this.role.node.position /*因发射出来的小球可能不只一个球同时存在,所以建立数组来保存 小球以及相应发射该小球时的角度(经测试最多同时存在3个)*/ this.ballShoot.push([ball, radian]) if (this.zumaUI.curBalls > 1) this.zumaUI.curBalls -= 1 } public hasSpecialBall(): boolean { return this.rollBalls.every(v => v.ballCfg.association[0]) } private onShootBallMove(): void { if (this.ballShoot.length != 0) { //这个循环对发射出来的每个球都进行检测 for (let i: number = 0; i < this.ballShoot.length; ++i) { let ballNode: cc.Node = this.ballShoot[i][0].node let ball: Ball = this.ballShoot[i][0] let radian: number = this.ballShoot[i][1] //这里检测是否越界 if ( ballNode.x > -ConstValue.REAL_WIDTH / 2 && ballNode.x < ConstValue.REAL_WIDTH / 2 && ballNode.y > -ConstValue.REAL_HEIGHT / 2 && ballNode.y < ConstValue.REAL_HEIGHT / 2 ) { //检测碰撞 let flag: number = this.checkCollision(ballNode) if (flag == -1) { ballNode.x += Math.cos(radian) * this.shootSpeed ballNode.y += Math.sin(radian) * this.shootSpeed } else { // soundMgr.CollisionSound.play(100); let dis: number = Math.sqrt( (this.rollBalls[flag].node.x - ballNode.x) * (this.rollBalls[flag].node.x - ballNode.x) + (this.rollBalls[flag].node.y - ballNode.y) * (this.rollBalls[flag].node.y - ballNode.y), ) //调整下小球的位置使之与碰撞的小球刚好接触 ballNode.x -= (this.ballSize - dis) * Math.cos(radian) ballNode.y -= (this.ballSize - dis) * Math.sin(radian) /*碰到了则算出球应该在哪两个小球中挤入进去,采用检测相邻两个放球的位置与要 插入的球的距离的方法,哪个距离近则向哪边插入小球,这也是为什么要将起点跟终 点设为与实际路径的起点终点相差this.startIndex个位置的原因,防止+this.startIndex或者-this.startIndex之后造成数组的越界 */ //算出距前一个小球的位置的距离 let PrevX: number = Data.zuma.mapData[this.rollBalls[flag].posIndex - this.startIndex][0] let PrevY: number = Data.zuma.mapData[this.rollBalls[flag].posIndex - this.startIndex][1] let prevDis: number = Math.sqrt( (ballNode.x - PrevX) * (ballNode.x - PrevX) + (ballNode.y - PrevY) * (ballNode.y - PrevY), ) //算出距下一个小球的位置的距离 let NextX: number = Data.zuma.mapData[this.rollBalls[flag].posIndex + this.startIndex][0] let NextY: number = Data.zuma.mapData[this.rollBalls[flag].posIndex + this.startIndex][1] let nextDis: number = Math.sqrt( (ballNode.x - NextX) * (ballNode.x - NextX) + (ballNode.y - NextY) * (ballNode.y - NextY), ) //比较距离远近,从而得出应在被碰撞的球的前一个位置插入还是后一个位置插入 this.insertBall(ball, flag, prevDis > nextDis) this.ballShoot.splice(i, 1) } } else { //越界则从显示列表中remove掉,并从ballShoot中移除该小球 this.putBall(ball) this.ballShoot.splice(i, 1) } } } } //-----------检测发射出来的小球与球链中球的碰撞,碰撞了返回被碰撞球的索引,否则返回-1 private checkCollision(ballNode: cc.Node): number { //这里检测是否与球链self._rollBalls中的球碰撞,碰撞采用距离计算 for (let j: number = 0; j < this.rollBalls.length; ++j) { let targetNode = this.rollBalls[j].node let dis: number = Math.sqrt( (targetNode.x - ballNode.x) * (targetNode.x - ballNode.x) + (targetNode.y - ballNode.y) * (targetNode.y - ballNode.y), ) if (dis <= this.ballSize) { return j } } return -1 } //--------插入小球,第一个参数是要插入的小球,第二个是被碰撞的小球的索引,第三个是标志向前插入还是向后插入 private insertBall(ball: Ball, index: number, isNext: boolean): void { let posX: number let posY: number let insertPos: number //确定obj要运动到的位置 if (isNext) { insertPos = this.rollBalls[index].posIndex + this.ballStep //插入的位置后面还有小球并且在挤入的时候会发生碰撞就需要检查与这个小球的碰撞情况 if ( this.rollBalls[index + 1] && this.rollBalls[index + 1].posIndex - this.rollBalls[index].posIndex < this.ballStep * 2 ) { this.ballCrushed.push([ball, this.rollBalls[index + 1]]) } } else { //向前插入分两种情况,第一种就是向前插入会挤到前面的球,此时插入的球的位置应是被碰小球的 //前一个球的posIndex+this.ballStep位置,并推动后面的球空出位置出来 if ( this.rollBalls[index - 1] && this.rollBalls[index].posIndex - this.rollBalls[index - 1].posIndex < this.ballStep * 2 ) { insertPos = this.rollBalls[index - 1].posIndex + this.ballStep this.ballCrushed.push([ball, this.rollBalls[index]]) } //第二种情况就是向前插入不会挤到前面的球,此时插入的球的位置应是被碰小球的posIndex-this.ballStep个位置 else { insertPos = this.rollBalls[index].posIndex - this.ballStep } } if (insertPos < 0) { this.putBall(ball) return } posX = Data.zuma.mapData[insertPos][0] posY = Data.zuma.mapData[insertPos][1] //做一个简单的动画效果 cc.tween(ball.node) .to(this.ballStep / this.stepLength / 60 - 0.1, {x: posX, y: posY}, {easing: 'sineOut'}) .call(() => { if (this.node.activeInHierarchy) this.motionFinished(ball, insertPos) }) .start() } //------逐帧检测挤入的小球与后一个小球的碰撞情况,如果距离小于this.ballSize即为碰撞, //------此时要将球后移,直到没碰撞,形成一个挤入的效果------------------------- private checkPushCollision(): boolean { if (this.ballCrushed.length != 0) { for (let i: number = 0; i < this.ballCrushed.length; ++i) { let firstBall = this.ballCrushed[i][0] let secondBall = this.ballCrushed[i][1] let firstBallNode = this.ballCrushed[i][0].node let secondBallNode = this.ballCrushed[i][1].node let dis: number = Math.sqrt( (firstBallNode.x - secondBallNode.x) * (firstBallNode.x - secondBallNode.x) + (firstBallNode.y - secondBallNode.y) * (firstBallNode.y - secondBallNode.y), ) let isCollision: boolean = dis < this.ballSize ? true : false let moveStep: number = 0 while (isCollision) { //如果是碰撞的则假设小球向前移动一步在检测是否碰撞,如此下去直到不碰撞了 ++moveStep if (secondBall.posIndex + moveStep > Data.zuma.mapData.length - 1) { isCollision = false } else { dis = Math.sqrt( (firstBallNode.x - Data.zuma.mapData[secondBall.posIndex + moveStep][0]) * (firstBallNode.x - Data.zuma.mapData[secondBall.posIndex + moveStep][0]) + (firstBallNode.y - Data.zuma.mapData[secondBall.posIndex + moveStep][1]) * (firstBallNode.y - Data.zuma.mapData[secondBall.posIndex + moveStep][1]), ) isCollision = dis < this.ballSize ? true : false } } //执行推进小球的操作,指定moveStep为应推进的步数,执行前需检查被检测碰撞的球 //是否由于执行消除操作删除了,没有被删除才能执行推动小球,否则将引起数组操作越界 let index: number index = this.rollBalls.indexOf(secondBall) if (index != -1) { this.pushBallFrom(index, moveStep) } } } return this.ballCrushed.length != 0 } //-------动画效果完成后要将小球插入到数组中,并调整好位置,第一个参数是要插入的小球, //-------第二个是球插入到地图数组中的位置,第三个是标志向前插入还是向后插入---------------------------- private motionFinished(ball: Ball, insertPos: number): void { let index: number //算出应插入到self._rollBalls中位置的索引,这里采用传入insertPos参数的方式 //而不是传入被碰撞小球的引用的原因是为了防止消除操作删除掉了小球而算不出 //数组的插入位置 for (let i: number = 0; i < this.rollBalls.length; ++i) { if (this.rollBalls[i].posIndex > insertPos) { index = i break } if (i == this.rollBalls.length - 1) { index = i + 1 } } //删除对应ballCrushed中的元素 this.ballCrushed.splice( this.ballCrushed.findIndex(v => v[0] == ball), 1, ) //根据插入的位置在self._rollBalls中插入该小球 ball.posIndex = insertPos this.rollBalls.splice(index, 0, ball) //检查插入后是否有被吸引的小球 if ( this.rollBalls[index - 1] && this.rollBalls[index - 1].ballID == this.rollBalls[index].ballID && this.rollBalls[index].posIndex - this.rollBalls[index - 1].posIndex > this.ballStep + 1 ) { this.addToBallAttracted(this.rollBalls[index]) } if ( this.rollBalls[index + 1] && this.rollBalls[index + 1].ballID == this.rollBalls[index].ballID && this.rollBalls[index + 1].posIndex - this.rollBalls[index].posIndex > this.ballStep + 1 ) { this.addToBallAttracted(this.rollBalls[index + 1]) } //检查是有要清除的小球 this.clearCheck(index, true) } /** * 传入检查的起点向两端开始搜索,检查是否应消除小球传入一个参数clear * 表示是否执行消去,若为false则只返回搜索到颜色相同的球有几个 */ private clearCheck(index: number, clear: boolean): number { if (!this.rollBalls[index]) return let temp: any[] = [] temp.push(this.rollBalls[index]) let ballID: number = this.rollBalls[index].ballID //此循环向下搜索 //是否要清除的参数为false则将不相连的也算起(为判断是否应取消连击计数提供依据),否则不算,下同 for (let i: number = index + 1; i < this.rollBalls.length; i++) { if ( this.rollBalls[i] && (this.rollBalls[i].ballID == ballID || this.rollBalls[i].ballCfg.association.includes(ballID)) ) { //有一定的间隙也算作是连接,由于挤入的操作会使球插入后不一定绝对 //只相差this.ballStep个位置,所以这里判断是否连接的条件应当放宽一点,this.ballStep的一点5倍而不是this.ballStep if (this.rollBalls[i].posIndex - this.rollBalls[i - 1].posIndex <= this.ballStep * 1.5) { temp.push(this.rollBalls[i]) } else if (!clear) { temp.push(this.rollBalls[i]) } else { break } } else { break } } //此循环向上搜索 let j: number = index - 1 while (this.rollBalls[j]) { if (this.rollBalls[j].ballID == ballID || this.rollBalls[j].ballCfg.association.includes(ballID)) { if (this.rollBalls[j + 1].posIndex - this.rollBalls[j].posIndex <= this.ballStep + 1) { temp.push(this.rollBalls[j]) --j } else if (!clear) { temp.push(this.rollBalls[j]) --j } else break } else { break } } //将j加1后传给清除小球函数,作为删除小球的起点 ++j //temp长度大于三则执行消除小球,传入该消除的小球数组以及起始球索引 if (temp.length > 2 && clear) { this.clearBall(j, temp) } return temp.length } //------------------消除小球,并在消除后检测是否两端小球颜色相同,相同则应该吸引过去----------- private clearBall(f: number, arr: Ball[]): void { //当前连击数加1 ++this.curCombo //播放的音调 let id: number = this.curCombo > 5 ? 5 : this.curCombo if (this.curCombo > 1) { // soundMgr.BallExplosionSound.play(); } for (let i: number = 0; i < arr.length; ++i) { //小球爆炸 arr[i].explode() // soundMgr.playCollisionSound(id); } //从球链数组中删除这些小球前先判断删除后是否游戏过关,如删除后游戏过关就得先获取self._rollBalls中 //最后一个球的posIndex位置传给extraScore()函数,计算出额外的加分 if (this.rollBalls.length == arr.length) { this.gamePass() this.lastId = this.rollBalls[this.rollBalls.length - 1].posIndex } this.rollBalls.splice(f, arr.length) let clearID = arr.find(v => !v.ballCfg.association[0]).ballID //如果消除数量大于x,生产特殊球 let sameBalls = arr.filter(v => v.ballID == clearID) //S球产生的buff推入队列 let specials = arr.filter(v => v.ballCfg.association[0] > 0) specials.forEach(v => { v.ballCfg.activation.forEach((buff, index) => { if (v.ballCfg.association.indexOf(clearID) == index) { this.zumaUI.addZumaBuff(buff) this.flyZumaBuff(buff, v.node.position) } }) }) //特殊球生成 let ballCfg = sameBalls[0].ballCfg if (ballCfg.specific <= sameBalls.length) { let specialBall = this.getBallFromPool() specialBall.init(ballCfg.generate, this) specialBall.posIndex = arr[0].posIndex this.rollBalls.splice(f, -1, specialBall) specialBall.node.parent = this.ballParent } //大于一定数量buff生成 let selfBuff = ballCfg.trigger[ballCfg.eliminate.indexOf(sameBalls.length)] if (selfBuff) { this.zumaUI.addZumaBuff(selfBuff) this.flyZumaBuff(selfBuff, sameBalls[0].node.position) } //若全部球滚出来了,就需要检查发射器中球的颜色了 if (this.rollInTotalCount == 0) { this.checkColor(clearID) } //检测断开的球链两端颜色是否相同应该吸引过去,注意此处删除了小球,应对比颜色的小球索引有变化 if ( this.rollBalls[f - 1] && this.rollBalls[f] && (this.rollBalls[f - 1].ballID == this.rollBalls[f].ballID || this.rollBalls[f - 1].ballCfg.association.includes(this.rollBalls[f].ballID)) ) { //利用clearCheck的返回值来确定是否为连击,吸引结束不会引发消除则更新最大连击数 //并将当前连击数置为0 if (this.clearCheck(f, false) < 3) { if (this.curCombo > this.maxCombo) { this.maxCombo = this.curCombo } this.curCombo = 0 } //因在吸引过程中球链可能会有变动,比如爆炸、插入后,导致不能吸引,所以逐帧执行检测是否要吸引,延迟400毫秒执行 //将被吸引的小球加入检测数组 this.addToBallAttracted(this.rollBalls[f]) } else { //这里需要更新连击数 if (this.curCombo > this.maxCombo) { this.maxCombo = this.curCombo } this.curCombo = 0 } } /** 检查发射器的颜色,使发射器中球不会出现球链中没有的颜色 */ private checkColor(ballID: number): void { //检查球链中的球 for (let i: number = 0; i < this.rollBalls.length; ++i) { if (this.rollBalls[i].ballID == ballID) return } //检查发射出来的球但还未插入的 for (let j: number = 0; j < this.ballShoot.length; ++j) { if (this.ballShoot[j][0].ballID == ballID) return } this.role.colorCleared(ballID) } /** 将需要被检测吸引的小球加入ballAttracted数组 */ private addToBallAttracted(ball: Ball): void { this.ballAttracted.push(ball) } /** 传入球断开的球的两端的球的索引点,将球吸引闭合起来 */ private attract(): boolean { if (this.ballAttracted.length != 0) { for (let i: number = 0; i < this.ballAttracted.length; ++i) { let index: number = this.rollBalls.indexOf(this.ballAttracted[i]) if (index != -1 && this.rollBalls[index - 1]) { if ( this.ballAttracted[i].ballID == this.rollBalls[index - 1].ballID || this.rollBalls[index - 1].ballCfg.association.includes(this.ballAttracted[i].ballID) ) { //算出应移动多少步 let steps: number = this.ballAttracted[i].posIndex - this.rollBalls[index - 1].posIndex > this.ballStep + this.levelCfg.stableSpeed ? this.levelCfg.stableSpeed : this.ballAttracted[i].posIndex - this.rollBalls[index - 1].posIndex - this.ballStep this.pushBallFrom(index, -steps) //判断吸引是否结束了 if (this.ballAttracted[i].posIndex - this.rollBalls[index - 1].posIndex <= this.ballStep) { //TODO // soundMgr.CollisionSound.play(); //吸引结束从数组中删除 this.ballAttracted.splice(i, 1) this.clearCheck(index - 1, true) } } else { //此种情况针对由于消除、插入操作造成的不能继续吸引,并且连击条件被破坏 this.ballAttracted.splice(i, 1) if (this.curCombo > this.maxCombo) { this.maxCombo = this.curCombo } this.curCombo = 0 } } } } return this.ballAttracted.length == 0 } /** 游戏过关 */ private gamePass(): void { this.gameSate = GAME_STATE.gamePass this.extraScore() this.role.clearBalls() this.ballParent.children.forEach(v => this.putBall(v.getComponent(Ball))) this.zumaUI.endZuma() } /** 计算加分 */ private extraScore(): void { if (this.lastId + this.startIndex < Data.zuma.mapData.length - this.startIndex - 1) { this.lastId += this.startIndex //TODO 播放结算音效 // soundMgr.EndExplosionSound.play(); } } setBallNum(num: number) { ccUtils.setLabel(num.toString(), this.node, 'goods/lb') } setBuffNum() { ccUtils.setLabel(this.zumaUI.activeBuff.length.toString(), this.buffNode, 'lb') } /** 飞入祖玛buff */ flyZumaBuff(buffID: number, pos: cc.Vec3) { let origin = Data.game.gameAssetMap.get('prefab/zuma/zumabuff') as cc.Prefab let node: cc.Node = this.buffPool.get() if (!node) node = cc.instantiate(origin) this.zumaUI.initZumaBuffItem(buffID, node) node.stopAllActions() node.parent = cc.find('buffFlyParent', this.node) node.setPosition(pos) cc.tween(node) .then(cc.tween().to(1, {x: this.buffNode.x, y: this.buffNode.y}, {easing: 'quartOut'})) .call(() => { if (cc.isValid(node)) { this.buffPool.put(node) } }) .start() } }