设计一个 game class。负责了游戏的核心控制逻辑,包括游戏状态管理、方块和道具的生成与效果处理,以及游戏的重新开始和复活流程。通过这些方法,脚本实现了游戏的基本玩法和用户交互。
主要游戏控制方法
gameStart()
:开始游戏,恢复所有方块状态,初始化分数管理器,设置地图。
mapSet(num)
:初始化地图,生成随机的方块布局。
checkNeedFall()
:检查是否需要下落的防抖方法。
onFall()
:方块下落的逻辑。
gameOver()
:游戏结束的逻辑,添加复活页面。
askRevive()
:处理复活请求,显示复活页面。
onReviveButton()
:复活按钮的点击事件。
showReviveSuccess()
:显示复活成功的页面。
onReviveCertainBtn()
:处理确定复活的按钮点击事件。
restart()
:重新开始游戏。
道具相关方法
onUserTouched(iid, jid, itemType, color, warning, pos)
:存储用户点击的方块信息,用于生成道具。
generatePropItem(type)
:根据类型生成道具。
checkGenerateProp(chain)
:检查并生成连锁道具效果。
onItem(type, color, pos)
:处理道具效果,如分数翻倍、炸弹消除等。
预制体实例化方法
generatePool()
:生成对象池,用于方块的实例化和回收。
instantiateBlock(self, data, parent, itemType, pos)
:实例化单个方块,初始化方块组件。
recoveryAllBlocks()
:回收所有方块节点,用于游戏重新开始或结束。
gameStart
方法是游戏开始时调用的核心逻辑,用于初始化游戏环境并启动游戏循环。以下是 gameStart
方法的逻辑分析:
/**
* gameStart 方法用于启动游戏,初始化游戏环境和状态。
*/
gameStart() {
// 回收所有方块,准备重新开始游戏
this.recoveryAllBlocks().then(() => {
// 初始化分数管理器
this._score.init(this);
// 初始化游戏地图,设置方块布局
this.mapSet(this.rowNum).then((result) => {
// 游戏状态改变,更新游戏状态为开始状态(状态1)
this._status = 1;
});
});
},
详细步骤:
回收方块 (recoveryAllBlocks
): 将之前游戏中使用的所有方块回收到对象池,以便重新利用,减少实例化开销。
初始化分数 (_score.init
): 初始化或重置分数管理器,为新游戏开始做准备。
设置地图 (mapSet
): 创建一个新的地图,随机生成方块,并设置它们在地图上的位置。这个方法还负责设置特殊方块类型,如道具方块。
异步等待: 由于 mapSet
是异步的,使用 then
来确保在地图准备好之后再继续。
状态更新: 设置 _status
为 1,这通常会在游戏循环和用户界面中用来检查游戏是否已经开始。
mapSet 方法的目的是为游戏创建一个初始状态的地图,其中包括随机分布的方块和道具,为游戏开始做好准备。通过使用 Promise
和 setTimeout
,它能够处理异步的动画延迟,确保方块以动画形式逐一出现在游戏地图上。
// mapSet 方法用于初始化游戏地图,设置方块的布局和类型
mapSet(num) {
// 初始化地图为一个 num x num 的二维数组
this.map = new Array();
// 保存当前对象的引用
let self = this;
// 生成随机数来确定特殊方块的位置
let a = Math.floor(Math.random() * num);
let b = Math.floor(Math.random() * num);
let c = Math.floor(1 + Math.random() * (num - 1)) - 1;
// 确保 c 不等于 a,以便有区分的道具位置
if (a == c) c++;
let d = Math.floor(Math.random() * num);
// 返回一个 Promise 对象,以便在地图设置完毕后执行异步操作
return new Promise((resolve, reject) => {
// 遍历地图的每一行
for (let i = 0; i < num; i++) {
// 初始化当前行的数组
this.map[i] = new Array();
// 遍历地图的每一列
for (let j = 0; j < num; j++) {
// 根据随机坐标确定方块的类型(0为普通方块,1和2为不同类型的道具方块)
let itemType = (i == a && j == b) ? 1 : (i == c && j == d) ? 2 : 0;
// 调用 instantiateBlock 方法来创建方块实例
self.map[i][j] = self.instantiateBlock(self, {
x: j, // 列位置
y: i, // 行位置
width: self.blockWidth, // 根据地图宽度计算的方块宽度
startTime: (i + j + 1) * self._controller.config.json.startAnimationTime / num * 2 // 计算动画开始时间
}, self.blocksContainer, itemType);
}
}
// 初始化检查管理器,准备进行方块检查
this.checkMgr.init(this);
// 设置一个延迟,以便于所有方块动画完成后再执行检查
setTimeout(() => {
// 解析 Promise,表示地图设置完成
resolve('200 OK');
// 执行检查函数,检查方块布局和消除情况
self.checkMgr.check(self);
}, self._controller.config.json.startAnimationTime * num / 2);
});
}
初始化地图数组:创建一个二维数组 this.map
来存储游戏地图的布局。
定义局部变量:保存对当前类的引用,以便在匿名函数中使用。
随机生成位置:生成随机坐标点,用于确定特殊方块的初始位置。
返回 Promise:使 mapSet
方法支持异步操作,以便在地图初始化完成后执行其他任务。
遍历地图位置:通过两层嵌套循环遍历地图的每一行和每一列。
确定方块类型:根据随机生成的坐标点,为每个方块分配类型(普通或道具)。
实例化方块:为每个位置创建方块实例,设置其属性,并添加到游戏地图中。
初始化检查管理器:在所有方块生成后,准备进行方块的消除检查。
设置延迟执行:使用 setTimeout
延迟执行检查函数,确保方块动画完成后再进行检查。
解析 Promise:在延迟时间结束后,解析 Promise 并执行方块检查,完成地图的初始化。
checkNeedFall
方法实现了一个防抖机制,确保方块下落的逻辑不会过于频繁地被触发,从而提高游戏性能并优化用户体验
// checkNeedFall 方法用于检查是否需要执行方块的下落动作
checkNeedFall() {
// 如果 checkNeedFallTimer 已经存在,说明之前的检查尚未完成,需要清除定时器
if (this.checkNeedFallTimer) {
clearTimeout(this.checkNeedFallTimer);
}
// 设置一个新的定时器,以实现防抖功能
// 防抖逻辑:在一定时间间隔(例如300毫秒)内,即使多次触发也不会连续执行下落逻辑
this.checkNeedFallTimer = setTimeout(() => {
// 检查游戏状态是否允许方块下落(状态为5时允许)
if (this._status == 5) {
// 如果状态允许,将游戏状态更新为下落状态(状态4)
this._status = 4;
// 调用 onFall 方法执行方块下落的逻辑
this.onFall();
}
// 设置定时器的时间间隔为 300 毫秒,以实现防抖效果
}, 300 / 1
// (cc.game.getFrameRate() / 60) // 这里被注释掉的代码可能是为了适应不同的帧率
);
}
清除已有定时器:如果 checkNeedFallTimer
已经设置,使用 clearTimeout
清除它,避免之前的延迟操作影响当前逻辑。
定义局部变量:this
关键字引用当前对象,用于访问类的属性和方法。
设置新定时器:使用 setTimeout
创建一个新的定时器,延迟执行下落检查逻辑。
防抖逻辑:定时器的延迟时间设置为 300 毫秒,这意味着在这段时间内,无论触发多少次 checkNeedFall
方法,onFall
方法都只会执行一次。
检查游戏状态:在定时器的回调函数中,检查 _status
是否等于 5,即是否处于可以下落的状态。
更新状态为下落:如果条件满足,将 _status
更新为 4,表示方块即将开始下落。
执行下落逻辑:调用 onFall
方法,执行方块下落的逻辑。
定时器时间间隔:定时器的时间间隔可以根据游戏的帧率进行调整,以确保不同设备上都能正常工作。
onFall
方法实现了方块下落的逻辑,包括处理空格、生成新方块、执行下落动画,以及在适当的时候更新游戏状态
// onFall 方法用于处理方块下落的逻辑
onFall() {
// 检查是否可以生成道具,并在完成后继续执行下落逻辑
this.checkGenerateProp(this._score.chain).then(() => {
let self = this; // 保存当前对象的引用
let canFall = 0; // 用于记录连续空格的数量
// 从每一列的最下面开始往上判断,是否有空格
for (let j = this.rowNum - 1; j >= 0; j--) {
canFall = 0; // 重置连续空格计数器
// 从底部开始向上遍历每一列的方块
for (let i = this.rowNum - 1; i >= 0; i--) {
// 如果当前位置的方块状态为已消失(状态2),则进行下落处理
if (this.map[i][j].getComponent('cell')._status == 2) {
// 将消失的方块回收到对象池中
this.blockPool.put(this.map[i][j]);
// 更新地图数组,标记当前位置为空
this.map[i][j] = null;
canFall++; // 增加连续空格计数
} else {
// 如果上方有连续空格,则让当前方块下落
if (canFall != 0) {
// 交换当前方块与上方空格的方块
this.map[i + canFall][j] = this.map[i][j];
this.map[i][j] = null;
// 让上方的方块下落
this.map[i + canFall][j].getComponent('cell').playFallAction(canFall, {
x: j,
y: i + canFall,
});
}
}
}
// 如果当前列有连续空格,生成新方块并让它们下落
for (var k = 0; k < canFall; k++) {
// 在地图数组中放置新方块的位置
this.map[k][j] = this.instantiateBlock(this, {
x: j,
y: k,
width: this.blockWidth,
startTime: null
}, this.blocksContainer, '', {
x: j,
y: -canFall + k
});
// 让新生成的方块执行下落动作
this.map[k][j].getComponent('cell').playFallAction(canFall, null);
}
}
// 在所有下落动作完成后,设置一个延迟,再次检查游戏状态
setTimeout(() => {
// 初始化检查管理器,准备进行方块检查
this.checkMgr.init(this);
// 执行检查函数,检查方块布局和消除情况
this.checkMgr.check(this);
// 更新游戏状态为可玩
this._status = 1;
}, 250); // 设置延迟时间,以便于动画效果完成后再执行检查
});
}
检查生成道具:调用 checkGenerateProp
方法检查是否可以生成道具,并等待其完成后继续。
保存引用:保存当前对象的引用,以便在匿名函数中使用。
初始化空格计数器:canFall
用于记录连续的空格数量。
遍历列:从地图的最底部开始,向上遍历每一列。
重置空格计数器:在遍历每一列时重置 canFall
。
遍历行:在每一列内部,从底部向上遍历每一行。
方块下落处理:
如果当前方块已经消失(状态为2),则将其回收并标记地图数组中相应位置为空。
如果上方有空格,让当前方块下落。
生成新方块:如果有连续空格,生成新方块并让它们下落。
执行下落动作:调用 playFallAction
方法让方块执行下落动画。
设置延迟:在所有下落动作完成后,使用 setTimeout
设置延迟。
再次检查状态:延迟结束后,初始化检查管理器并执行检查函数,更新游戏状态。
更多详细代码逻辑请看:微信小游戏之三消(二)主要游戏控制方法代码逻辑分享 (qq.com)https://mp.weixin.qq.com/s?__biz=MzI0NTE3ODY5Mg==&mid=2247483770&idx=1&sn=721d6cb02baf8d2345870e924598e094&chksm=e953c951de24404768866f4c01a3e97f4cda185fe4f4a1cba84c8b98fc76303de832f66c3ebb&token=1281952290&lang=zh_CN#rd