多实例自定义元素场景下JS文件非全局变量通信失败:poopengine未定义
多实例自定义元素场景下JS文件非全局变量通信失败:poopengine未定义
嘿,我完全懂你现在的困扰——你想让每个<poopengine-window>都能加载独立的游戏脚本,还不想用全局window变量来传递引擎实例,结果现在脚本里直接用poopengine就报错未定义对吧?咱们来一步步搞定这个问题,同时还能完美支持多实例。
问题根源分析
你现在的问题核心有两个:
- 游戏脚本
main.js默认依赖全局的poopengine变量,但你根本没把每个自定义元素对应的引擎实例暴露成全局变量(而且也不能这么做,多实例会冲突)。 - 原来用
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>
为什么这样能解决问题?
- 每个
<poopengine-window>实例都有自己独立的poopengine_class对象,完全隔离不会互相干扰。 - 游戏脚本通过
export init接收引擎实例,不需要依赖全局变量,彻底解决poopengine is not defined的错误。 - 用动态
import()加载脚本,比eval更安全、更符合模块化规范。
备注:内容来源于stack exchange,提问作者pui4
相关产品推荐
相关产品推荐

