TypeScript+PixiJS游戏碰撞加分异常、清零后仍计数问题
PixiJS猫鼠碰撞计分异常问题排查
问题现象
- 猫与老鼠单次碰撞时,分数单次累加30左右,不符合预期的单次加1分逻辑
- 所有老鼠实体从舞台移除、分数手动置零后,分数仍然会持续自动增长
- 临时规避方案:增加「仅当猫角色存在于游戏场景中时才允许更新分数」的判断后,异常暂时消失
相关问题代码
碰撞检测逻辑
for (const mouse of this.mice) { mouse.update(delta); if (this.collision(this.cat, mouse)) { this.pixi.stage.removeChild(mouse); this.interface.addScore(1); } }
UI分数模块实现
export class ui extends PIXI.Container { scoreField:PIXI.Text score:number = 0 private game: Game; constructor(game: Game){ super(); this.game = game; const style = new PIXI.TextStyle({ fontFamily: 'ArcadeFont', fontSize: 40, fontWeight: 'bold', fill: ['#ffffff'] }) this.scoreField = new PIXI.Text(`Score : 0`, style) this.addChild(this.scoreField) this.scoreField.x = 10 this.scoreField.y = 10 } addScore(n:number) { this.score += n this.scoreField.text = `Score : ${this.score}` } }
游戏失败处理逻辑
// when the Dog is the only survivor if ( this.pixi.stage.children.filter((object) => object instanceof Dog) .length === 1 && this.pixi.stage.children.filter((object) => object instanceof Cat) .length === 0 ) { console.log("YOU LOSE"); let text = new PIXI.Text("You LOSE!!", { fill: ["#ffffff"] }); text.x = this.pixi.screen.width / 2; text.y = this.pixi.screen.height / 2; this.pixi.stage.addChild(text); for (const dog of this.dogs) { dog.update(delta); this.pixi.stage.removeChild(dog); for (const mouse of this.mice) { this.pixi.stage.removeChild(mouse); this.interface.score = 0; } this.interface.score = 0; } }
根因分析
两个核心逻辑漏洞共同导致计分异常:
- 实体仅移除渲染节点,未清理数据源引用
碰撞触发后仅调用this.pixi.stage.removeChild(mouse)将老鼠从渲染树移除,但this.mice数组中仍保留该老鼠对象的引用。PixiJS默认运行帧率为60FPS,即帧循环每秒执行60次,后续帧遍历this.mice时,已被移除渲染的老鼠对象仍会参与碰撞判定——只要猫的位置未发生偏移、与残留老鼠对象的碰撞盒仍重叠,就会重复触发加分逻辑。单次碰撞后0.5秒内就能触发约30次加分,和观察到的单次碰撞加30分的现象完全吻合。 - 失败场景清理不彻底,状态未锁死
失败判定触发后,逻辑中同样仅将实体从渲染树移除,未清空this.mice数组,帧循环仍会遍历残留的老鼠对象执行碰撞逻辑;同时直接给this.interface.score赋值为0的操作,既没有同步更新分数显示文本,也没有阻断后续碰撞、更新逻辑的执行,只要残留对象的碰撞判定返回真值,分数就会持续增长。
添加的「猫存在才计分」的判断只是刚好绕过了失败场景下的加分触发条件,内存中残留的实体对象、未停止的帧循环逻辑都没有被清理,属于临时规避方案,没有解决根本问题。
修复方案
按照以下步骤修改即可彻底解决问题:
- 碰撞触发移除老鼠时,同步将老鼠从
this.mice数组中删除,避免后续帧重复遍历到已销毁的实体,注意遍历数组删除元素时使用倒序遍历,避免正序splice导致元素漏遍历 - 失败逻辑触发时,直接清空
this.mice、this.dogs等实体数组,不要只移除渲染节点,可选调用PixiJS的destroy()方法释放实体占用的内存 - 给UI类增加统一的分数重置方法,重置分数时同步更新显示文本,不要直接外部修改score属性
- 增加游戏全局状态锁,游戏结束状态下直接跳出帧循环,不再执行实体更新、碰撞检测等逻辑
修复参考代码
改造碰撞逻辑
// 倒序遍历数组 安全删除元素 for (let i = this.mice.length - 1; i >= 0; i--) { const mouse = this.mice[i]; mouse.update(delta); if (this.collision(this.cat, mouse)) { this.pixi.stage.removeChild(mouse); this.mice.splice(i, 1); // 同步从数据源数组移除已碰撞老鼠 this.interface.addScore(1); mouse.destroy(); // 释放对象内存 } }
给UI类增加分数重置方法
resetScore() { this.score = 0; this.scoreField.text = `Score : ${this.score}`; }
改造失败处理逻辑
if ( this.pixi.stage.children.filter((object) => object instanceof Dog) .length === 1 && this.pixi.stage.children.filter((object) => object instanceof Cat) .length === 0 ) { console.log("YOU LOSE"); let text = new PIXI.Text("You LOSE!!", { fill: ["#ffffff"] }); text.x = this.pixi.screen.width / 2; text.y = this.pixi.screen.height / 2; this.pixi.stage.addChild(text); // 清理所有狗实体 this.dogs.forEach(dog => { this.pixi.stage.removeChild(dog); dog.destroy(); }); this.dogs = []; // 清理所有老鼠实体 this.mice.forEach(mouse => { this.pixi.stage.removeChild(mouse); mouse.destroy(); }); this.mice = []; // 重置分数 同步UI this.interface.resetScore(); // 锁死游戏状态 this.isGameOver = true; }
帧循环开头增加状态判断
update(delta) { // 游戏结束直接终止后续逻辑执行 if (this.isGameOver) return; // 原有实体更新、碰撞检测等逻辑 }
内容的提问来源于stack exchange,提问作者Mich VDW
相关产品推荐
相关产品推荐

