LPAI 独立网页游戏 AI 社区指南

页面与交互

渲染布局与 Canvas

用只读响应式页面和完整 Canvas renderer API 构建场景。

页面与交互 · AI 路径

HTML 负责页面,Canvas 负责连续绘制

渲染布局同步返回一份响应式页面;Canvas renderer 增加精灵、镜头、粒子、碰撞和连续输入。两者只消费状态快照,玩家存档仍只能由 Worker 动作结算。

# 选择页面技术

需求 负责人
状态栏、卡片、列表、表单、弹窗、响应式排版 渲染布局 HTML/CSS
精灵、连续移动、命中检测、局部高频图形 Canvas renderer
三维模型、透视相机、灯光、3D 动画与拾取 type=3d Three.js Canvas renderer
点击、输入、付费或插件动作 已存在的控件 binding
属性、对象、存档、AI 或联网结算 Worker 控件/事件脚本
Canvas 外的临时覆盖层 客户端特效脚本

不要为了一个普通按钮使用 Canvas,也不要为了移动精灵逐帧重建整页 HTML。

# 渲染布局契约

脚本同步返回 HTML 字符串。它可以读取允许的 playerobjectstools 代理,但不能修改状态,也不能使用 asyncawait、Promise、DOM、网络或定时器。

return `
  <main lp-key="camp-root" class="min-h-dvh bg-slate-950 p-4 text-white">
    <h1>` + tools.tr("冒险营地") + `</h1>
    <p>` + player.getString("当前提示") + `</p>
    <button lp-control="explore-control-id" lp-key="explore-action">
      ` + tools.tr("开始探索") + `
    </button>
  </main>
`;

玩家输入、AI 输出和联网文本都是不可信展示数据,应转义或安全绑定,不能直接插入可执行 HTML。

# 绑定协议

属性 作用
lp-control 绑定已有控件及其 Worker 规则
lp-repeat-control 展开对象/插件重复行并保留实例上下文
lp-events 声明控件事件绑定
lp-model 绑定数值属性或可写动态字符串
lp-key 更新时保留稳定节点身份
lp-canvas 挂载已配置的 Canvas renderer

binding 使用稳定资源 ID,节点使用稳定业务 key。随机 key 会破坏输入、焦点和 renderer 连续性。

# 挂载 Canvas renderer

在场景 Canvas 配置中创建唯一 renderer ID,并给宿主明确的响应式尺寸:

<canvas
  lp-canvas="world"
  lp-key="world-canvas"
  class="block h-[70dvh] w-full"
></canvas>

renderer 脚本获得 playerobjectstools.canvas,并且必须同步返回 frame 函数或生命周期对象:

回调 执行时机 输入
mount(ctx) Pixi 和宿主准备完成后一次 初始快照
update(ctx) 运行时快照改变时 新的冻结快照
frame(ctx) 每个活动动画帧 deltaMselapsedMs
bindingResult(result) Canvas binding 请求结算后 request/binding/accepted/reason
dispose(ctx) renderer 移除、场景切换或替换 最终清理上下文

回调不能是 async,也不能返回 Promise。允许的资源 Promise 用 .then/.catch 启动;高级 API 还应维护 disposed 标记。

# 状态快照 API

生命周期 ctxtools.canvas 都提供冻结状态:

内容
ctx.revision / canvas.state.revision 当前快照修订号
ctx.state / canvas.state.snapshot { properties, dynamicStrings },按稳定 ID 索引
ctx.objects / canvas.objects.snapshot 按类/实例组织的对象快照
ctx.scene / canvas.scene id、逻辑 widthheightdpr
ctx.input / canvas.input 指针和键盘快照
ctx.deltaMs / ctx.elapsedMs 截断后的帧间隔与运行时间

快照只读。本地动画不能证明 Worker 动作成功,应等待下一份状态快照或 bindingResult

# 完整 canvas API

本节绘制/碰撞/资源示例为缺省 type=2d 的 Pixi 接口。type=3d 复用生命周期、输入、绑定和源码模块,原生场景及绘制接口见 Three.js 三维 Canvas。两种引擎分别按需导入;实际发布分包和网络下载仍需按目标构建验收。

# 绘制

方法 作用
canvas.draw.sprite(key, options) 创建/更新已配置图片、帧或动画
canvas.draw.rect(key, options) 创建/更新矩形或圆角矩形
canvas.draw.text(key, options) 创建/更新 Pixi 文本
canvas.draw.bounds(key) 返回 { x, y, width, height }undefined
canvas.draw.remove(key) 删除并销毁一个托管节点,返回 boolean
canvas.draw.clear() 删除全部托管节点,只在整体替换/销毁时使用

三个 draw 方法都是 upsert,必须复用稳定且唯一的 key。通用 options 包括 xywidthheightscalescaleXscaleYrotationalphavisiblezIndexbindingsobjectId 和可序列化 payload

  • Sprite 额外支持 assetframeanimationanchorXanchorY
  • Rect 额外支持 colorfillAlphastrokestrokeAlphalineWidthradius
  • Text 必须传 text;Pixi 字体字段放在 style,可传 anchorX / anchorY
const canvas = tools.canvas;
let x = 80;

return {
  frame(ctx) {
    if (canvas.input.isKeyDown("ArrowRight")) {
      x += 180 * ctx.deltaMs / 1000;
    }
    if (canvas.input.wasKeyPressed("Space")) {
      canvas.emitBinding("jump", { x });
    }
    canvas.draw.rect("player", {
      x, y: 120, width: 32, height: 32,
      radius: 16, color: "#22d3ee"
    });
  },
  dispose() {
    canvas.draw.clear();
  }
};

# 资源

方法 返回
canvas.assets.resolve(assetId, objectId?) { id, source, state }
canvas.assets.state(assetId) idleloadingreadyerror
canvas.assets.preload(assetId, objectId?) Promise<boolean>

资源必须先存在于 renderer 配置中,不能编造 URL。preload 是上下文中返回 Promise 的例外,但生命周期仍需同步返回:不要 await,通过 state/update 在资源就绪后绘制。

# 输入

canvas.input.pointer 包含 xydownpressedreleasedpointerTypebuttonbuttonsmovementXmovementYwheelXwheelY

canvas.input.keyscanvas.input.pressedKeyscanvas.input.releasedKeys 使用 KeyboardEvent.code。Canvas 获得焦点后才接收键盘输入。

方法 含义
canvas.input.isKeyDown(code) / canvas.input.wasKeyPressed(code) / canvas.input.wasKeyReleased(code) 持续按下、本帧按下、本帧释放
canvas.input.isButtonDown(button?) / canvas.input.wasButtonPressed(button?) / canvas.input.wasButtonReleased(button?) 指针按钮状态,默认 button 为 0

# 碰撞与边界

方法 返回
canvas.collision.intersects(leftKey, rightKey) 两个托管节点边界是否重叠
canvas.collision.contains(key, x, y) 点是否在托管节点内部
canvas.collision.clamp(key, bounds?) 把节点移入边界,返回是否成功

clamp 默认边界是 Canvas 逻辑区域。碰撞使用当前轴对齐渲染边界,不是自定义物理形状。

# 动作绑定

canvas.emitBinding(name, payload?, objectId?) 立即返回请求 ID。name 必须映射已配置的 action binding,其中使用真实控件 ID 和 js_gjs_pressjs_releasejs_move 之一。

托管节点可声明 bindings: { click, press, release, move },同时携带 payloadobjectId。move binding 应配置节流,不能逐帧向 Worker 发动作;Worker 脚本从 tools.kwargs.payload 读取可序列化参数。

bindingResult 表示传输/执行链是否接受请求,不表示业务一定成功。权威成功结果仍来自后续状态快照。

# 模块

canvas.modules.load(key, source) 同步返回作者源码导出的函数或对象。同一 key/source 每个 renderer session 只执行一次;源码变化时旧模块先清理再替换;session 销毁会调用模块导出的 dispose(ctx)

模块源码只能来自作者维护的只读动态字符串或静态表格。禁止执行玩家输入、动态表格、弱联网结果或运行时 AI 输出。

# 高级 PixiJS

canvas.pixi 开放 PixiJS 8 模块命名空间,canvas.app 开放宿主拥有的 Application。高级 renderer 可创建 Container、Graphics、Sprite、Text、滤镜、mask、shader、mesh、粒子和 ticker 回调。

不能创建/销毁宿主 Application、替换 renderer、销毁 app.ticker 或把 app.canvas 移到其他 DOM。canvas.draw 不管理自行创建的对象;必须在 dispose 中移除监听并销毁资源。

# 预算与失败

运行时会限制托管节点、纹理内存/并发、模块数量/深度/key 长度和模块源码总量。连续三次 frame 报错会暂停 frame 程序并显示 Canvas 诊断。复用稳定节点、预加载有限资源、避免每帧全量扫描,并优先修复第一条诊断。

# 纯 AI 任务合同

向编辑器 AI 提供场景、页面区域、数据 ID、控件/action binding、renderer ID、资源清单、目标宽度、视觉参考、清理规则和验收步骤。要求它保留 Worker 规则,只替换表现层。

验收首帧、状态更新、键盘/触摸焦点、尺寸/DPR、资源失败、binding 拒绝、重复输入、对象定位、场景清理和窄屏无重叠。

下一步:tools 运行时 API客户端视觉特效指针脚本