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

多实例自定义元素场景下JS文件非全局变量通信失败:poopengine未定义

多实例自定义元素场景下JS文件非全局变量通信失败:poopengine未定义

嘿,我完全懂你现在的困扰——你想让每个<poopengine-window>都能加载独立的游戏脚本,还不想用全局window变量来传递引擎实例,结果现在脚本里直接用poopengine就报错未定义对吧?咱们来一步步搞定这个问题,同时还能完美支持多实例。

问题根源分析

你现在的问题核心有两个:

  1. 游戏脚本main.js默认依赖全局的poopengine变量,但你根本没把每个自定义元素对应的引擎实例暴露成全局变量(而且也不能这么做,多实例会冲突)。
  2. 原来用eval加载脚本的方式,没办法把当前元素的引擎实例传递给脚本,导致脚本找不到poopengine对象。

解决方案:用模块化导入传递实例

咱们改成动态导入脚本+传递实例的方式,既不用全局变量,又能让每个脚本绑定对应的引擎实例。

步骤1:改造自定义元素(poopengine.mjs)

先补全<poopengine-window>的自定义元素逻辑,让它创建自己的poopengine_class实例,然后动态导入脚本并把实例传递过去,同时修正引擎内部的全局引用问题:

class poopengine_class {
  constructor(canvas) {
    this.canvas = canvas;
    this.display = { width: 300, height: 200 };
    this.context = null;
    this.objects = [];
    // 修正input类的全局引用:将当前引擎实例传入内部类
    this.input = new class Input {
      constructor(parentEngine) {
        this.parent = parentEngine; // 保存父引擎实例
        this.keydown = null;
        this.keypressed = null;
        this.keyup = null;
        this.click = null;
        this.mousedown = null;
        this.mouseup = null;
        this.mouse_pos = { x: null, y: null };
        
        this.hovering = function (object) {
          if (this.mouse_pos.x != null) {
            // 改用this.parent访问当前引擎的objects,而非全局poopengine
            const index = this.parent.objects.indexOf(object);
            const array = this.parent.objects.slice(index + 1);

            for (let i = 0; i < array.length; i++) {
              if (
                this.mouse_pos.x >= array[i].x &&
                this.mouse_pos.x <= array[i].x + array[i].width &&
                this.mouse_pos.y >= array[i].y &&
                this.mouse_pos.y <= array[i].y + array[i].height
              ) {
                return false;
              }
            }

            if (
              this.mouse_pos.x >= object.x &&
              this.mouse_pos.x <= object.x + object.width &&
              this.mouse_pos.y >= object.y &&
              this.mouse_pos.y <= object.y + object.height
            ) {
              return true;
            } else {
              return false;
            }
          }
        }
      }
    }(this);

    this.start = function () {
      if (!this.canvas) {
        console.error("Canvas is not initialized yet.");
        return;
      }

      this.canvas.click();
      this.canvas.width = this.display.width;
      this.canvas.height = this.display.height;

      this.context = this.canvas.getContext("2d");

      // 事件监听逻辑保持不变,但所有全局poopengine替换为this
      window.onresize = () => {
        this.resize();
      };

      window.addEventListener("mousedown", () => {
        this.input.mousedown = true;
      });
      // ...其余事件监听代码
      
      window.requestAnimationFrame(() => this.update());
    };

    this.update = function () {
      this.context.reset()
      window.requestAnimationFrame(() => this.update());
      this.context.clearRect(0, 0, this.display.width, this.display.height);

      // FPS计算逻辑保持不变,替换全局引用为this
      const now = performance.now();
      if (this.times.length > 0) {
        this.deltaTime = now - this.times[this.times.length - 1];
      }
      while (this.times.length > 0 && this.times[0] <= now - 1000) {
        this.times.shift();
      }
      this.times.push(now);
      this.fps = this.times.length;

      for (let i = 0; i < this.objects.length; i++) {
        this.objects[i].update();
      }
    };

    this.object = function ({
      width,
      height,
      x,
      y,
      colour,
      image,
      tile,
      image_alpha,
      text,
      text_size,
      font,
      word_wrap,
      line_height,
      text_align,
    }) {
      this.width = width;
      this.height = height;
      this.x = x;
      this.y = y;
      this.colour = colour;

      let ctx = this.context; // 替换全局poopengine.context为this.context
      
      // 其余object逻辑保持不变,所有全局引用替换为this
    };
  }
}

// 定义自定义元素
class PoopengineWindow extends HTMLElement {
  constructor() {
    super();
    const canvas = document.createElement('canvas');
    this.appendChild(canvas);
    // 为当前元素创建独立的引擎实例
    this.engine = new poopengine_class(canvas);
  }

  async attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'script' && newValue && oldValue !== newValue) {
      try {
        // 动态导入游戏脚本
        const gameModule = await import(newValue);
        // 调用脚本导出的初始化方法,传递当前引擎实例
        if (typeof gameModule.init === 'function') {
          gameModule.init(this.engine);
        }
      } catch (err) {
        console.error('加载游戏脚本失败:', err);
      }
    }
  }

  static get observedAttributes() {
    return ['script'];
  }
}

customElements.define('poopengine-window', PoopengineWindow);

步骤2:修改游戏脚本(main.js)

把原来依赖全局poopengine的代码,改成通过参数接收引擎实例的模块化形式:

// 导出初始化函数,接收引擎实例作为参数
export function init(poopengine) {
  const originalStart = poopengine.start;
  poopengine.start = function () {
    originalStart.apply(this);
    const box = poopengine.object({
      width: 50,
      height: 50,
      x: 0,
      y: 0,
      colour: 'blue'
    });
  }

  const originalUpdate = poopengine.update;
  poopengine.update = function () {
    originalUpdate.apply(this);
    // 你的游戏逻辑写在这里
  }

  poopengine.resize = function () {
    // 窗口resize逻辑写在这里
  }

  poopengine.start();
}

步骤3:HTML保持不变

你的index.html不需要修改,还可以添加多个实例加载不同脚本:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="../poopengine.mjs" type="module"></script>
    <style>
        body {
            padding: 0;
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <poopengine-window script="./main.js"></poopengine-window>
    <!-- 支持多实例,加载不同脚本 -->
    <!-- <poopengine-window script="./another-game.js"></poopengine-window> -->
</body>
</html>

为什么这样能解决问题?

  1. 每个<poopengine-window>实例都有自己独立的poopengine_class对象,完全隔离不会互相干扰。
  2. 游戏脚本通过export init接收引擎实例,不需要依赖全局变量,彻底解决poopengine is not defined的错误。
  3. 用动态import()加载脚本,比eval更安全、更符合模块化规范。

备注:内容来源于stack exchange,提问作者pui4

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 15:02:59