如何在 Phaser 中正确为 DOM UI 元素添加事件监听器

在 phaser 中使用 `this.add.dom()` 加载 html ui 后,需在 `create` 阶段而非 `update` 阶段绑定事件监听器;否则每帧重复注册会导致性能问题、事件失效或无响应。

Phaser 的 DOM 对象(如通过 this.add.dom(0, 0).createFromCache('ui') 创建)本质上是将 HTML 元素挂载到 Canvas 旁的

容器中,其生命周期与 Phaser 场景一致。但 DOM 节点的可交互性依赖于正确的时机绑定事件

✅ 正确做法:在 create() 中一次性绑定

create() {
  // 加载并插入 UI HTML
  const domElement = this.add.dom(0, 0).createFromCache('ui');

  // 确保 DOM 已渲染完成后再查询元素(推荐加一层安全检查)
  this.time.delayedCall(0, () => {
    const button = document.querySelector('#dirt');
    if (button) {
      button.addEventListener('click', () => {
        console.log('add dirt');
        // ? 可在此触发 Phaser 游戏逻辑,例如:
        // this.scene.get('GameScene').addDirt();
      });
    } else {
      console.warn('DOM element #dirt not found in cache');
    }
  });
}
? 提示:delayedCall(0, ...) 是一种轻量级保障 DOM 渲染就绪的方式(尤其当 createFromCache 异步性或渲染时序存在微小延迟时)。也可改用 domElement.node.onload 或 MutationObserver,但对简单 UI,delayedCall(0) 已足够可靠。

❌ 常见错误:在 update() 中重复绑定(务必避免!)

// ⚠️ 危险!不要这样做:
update() {
  document.querySelector('#dirt').addEventListener('click', () => {
    console.log('add dirt'); // 每秒执行约 60 次 → 内存泄漏 + 多重触发
  });
}

该写法会在每一帧(默认 60 FPS)重复添加监听器,不仅造成严重性能下降,还会导致点击一次触发数十次回调,且旧监听器无法被自动清理,最终 UI 表现“无响应”或行为异常。

? 补充建议

  • 确保资源已预加载:在 preload() 中调用 this.load.html('ui', 'assets/ui.html'),否则 createFromCache('ui') 将失败;
  • 检查作用域与上下文:若使用箭头函数,this 指向场景实例,适合调用 this.sound.play() 等 Phaser API;
  • 调试技巧:在绑定前 console.log(document.querySelector('#dirt')),确认元素存在且非 null;
  • 替代方案(更 Phaser 原生):对于简单交互,也可直接监听 DOM 对象的 node 属性:
const domElement = this.add.dom(0, 0).createFromCache('ui');
domElement.node.querySelector('#dirt').addEventListener('click', () => { /* ... */ });

这种方式更明确地限定作用域,避免全局 document 查询可能受其他脚本干扰的风险。

总之,时机决定成败——把事件绑定逻辑放在 create()(或 domElement.once('addedtoscene', ...)),是让 Phas

er DOM UI 真正响应用户操作的关键。