Skip to content

Vue 集成

@tripo3d/engine/vue 提供三件套,把命令式的 engine 运行时接进 Vue 组件树:

  • <EngineCanvas> —— 把 engine 挂载到 DOM 的容器组件
  • useEngine() —— 在任意子组件里拿到 engine 实例
  • createPluginVue() —— 让 ECS 状态在模板里自动响应

前两者是容器 + 依赖注入,后者是响应式适配。三件套通常一起用。

<EngineCanvas>

组件负责 onMounted 时 engine.mount(...)、onBeforeUnmount 时 engine.unmount()engine.dispose(),并通过 Vue 的 provide/inject 把 engine 下传给整个子树。内部渲染三层结构:根容器 <div>、Three.js <canvas> 与一个同层 overlay <div>(用于 DOM 辅助层),默认 slot 会被渲染在 overlay 之后。

Props

名称 类型 默认值 说明
engine必填EnginecreateEngine() 返回的引擎实例
autoDisposebooleanfalse组件卸载时是否调用 engine.dispose() 彻底释放;false 则只 unmount,可再次挂载

基础用法

vue
<script setup lang="ts">
import { createEngine, EngineCanvas, createPluginVue } from '@tripo3d/engine/vue';
import { shallowRef } from 'vue';

const engine = shallowRef(createEngine().use(createPluginVue()));
</script>

<template>
  <EngineCanvas :engine="engine" auto-dispose />
</template>

useEngine()

<EngineCanvas> 的子组件里,随时用 useEngine() 拿到注入的 engine。没找到时会抛错,因此必须在 <EngineCanvas> 子树内使用。

ts
import { useEngine } from '@tripo3d/engine/vue';

const engine = useEngine();
// engine.world / engine.addSystem / engine.events ...

createPluginVue(options)

虽然插件本身在 插件系统 里有详细说明,但在 Vue 项目里必须再强调一次——不装这个插件,ECS 状态就不会驱动模板更新

原因是 engine 的 System / Component / Script 实例都是普通 JS 对象,Vue 不会自动跟踪属性变化。createPluginVue 在每个实例创建后用 shallowReactive 包一层,把对象写入变成可观测的响应式写入;同时对 Three.js 重型对象(Object3DMaterialTexture 等)调用 markRaw 避免被 Vue 深代理拖慢性能。

配置

名称 类型 默认值 说明
markEngineRawbooleantrue是否对 engine 本体及其 view / world / pipeline / events 等字段 markRaw;建议保持 true

完整示例

下面展示父组件持有 engine、子组件通过 useEngine 订阅某个 system 状态并双向绑定的典型写法。

父组件:

vue
<script setup lang="ts">
import { createEngine, EngineCanvas, createPluginVue } from '@tripo3d/engine/vue';
import { shallowRef } from 'vue';
import SceneControls from './SceneControls.vue';

const engine = shallowRef(
  createEngine({
    camera: { type: 'perspective', fov: 45 },
  }).use(createPluginVue()),
);
</script>

<template>
  <EngineCanvas :engine="engine" auto-dispose>
    <SceneControls />
  </EngineCanvas>
</template>

子组件 SceneControls.vue:

vue
<script setup lang="ts">
import { useEngine } from '@tripo3d/engine/vue';
import { CameraAddonSystem } from '../systems/camera-addon';

const engine = useEngine();
const cameraSystem = engine.addSystem(CameraAddonSystem);
// 因为 createPluginVue 已经把 system 变成 shallowReactive,
// 这里直接读写 cameraSystem.xxx 就能触发模板更新
</script>

<template>
  <div class="controls">
    <label>
      FOV
      <input v-model.number="cameraSystem.fov" type="range" min="10" max="120" />
    </label>
    <p>当前 FOV: {{ cameraSystem.fov }}</p>
  </div>
</template>

SSR / Nuxt 注意

engine 依赖 WebGL、window、DOM 测量,服务端渲染阶段不能构造,否则会抛错。两种常见做法:

  1. <ClientOnly> 包裹 <EngineCanvas>,让组件只在客户端渲染
  2. onMounted 里再 createEngine,并用 shallowRef 保存
vue
<script setup lang="ts">
import { shallowRef, onMounted } from 'vue';
import type { Engine } from '@tripo3d/engine';

const engine = shallowRef<Engine | null>(null);

onMounted(async () => {
  const { createEngine } = await import('@tripo3d/engine');
  const { createPluginVue } = await import('@tripo3d/engine/vue');
  engine.value = createEngine().use(createPluginVue());
});
</script>

<template>
  <ClientOnly>
    <EngineCanvas v-if="engine" :engine="engine" auto-dispose />
  </ClientOnly>
</template>

最佳实践

  • shallowRef 而不是 ref 保存 engine 实例。engine 内部字段众多,ref 会把整个对象做深响应,性能急剧下降;即便装了 createPluginVue({ markEngineRaw: true }),shallowRef 依然是更稳妥的组合
  • autoDispose 在大部分场景推荐设为 true。只有当组件会频繁 mount/unmount 且希望保留 world / history 状态时才设为 false,届时需要在更外层手动管理 engine.dispose()
  • use(createPluginVue())addSystem。插件只对安装之后创建的实例生效——先加的 System 不会被响应式包装
  • useEngine 必须在 <EngineCanvas> 子树内调用,否则注入失败会抛错。若需要在非子树组件里访问 engine,改用 Pinia / 共享 ref 存放
  • 不要把 Three.js 对象放进 ref。若确实需要,用 shallowRef 并手动 markRaw

下一步

基于 MIT 协议发布(内部使用)