You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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; 
    }
}

根因分析

两个核心逻辑漏洞共同导致计分异常:

  1. 实体仅移除渲染节点,未清理数据源引用
    碰撞触发后仅调用this.pixi.stage.removeChild(mouse)将老鼠从渲染树移除,但this.mice数组中仍保留该老鼠对象的引用。PixiJS默认运行帧率为60FPS,即帧循环每秒执行60次,后续帧遍历this.mice时,已被移除渲染的老鼠对象仍会参与碰撞判定——只要猫的位置未发生偏移、与残留老鼠对象的碰撞盒仍重叠,就会重复触发加分逻辑。单次碰撞后0.5秒内就能触发约30次加分,和观察到的单次碰撞加30分的现象完全吻合。
  2. 失败场景清理不彻底,状态未锁死
    失败判定触发后,逻辑中同样仅将实体从渲染树移除,未清空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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 19:54:23