design.md 8.6 KB

背景

上游 flow-shield-effect 的护盾效果主要集中在 src/components/ForceShield 内部:一个球体 mesh、一套自定义 shader 材质、动画 uniform,以及一个用于渲染点击命中冲击波的小型命中缓冲。仓库其他部分大多是为了 demo 展示服务的,包括 React Three Fiber 包装、Leva 调参、Orbit 辅助、场景组合和可选后期处理。这意味着它非常适合做“核心能力直接迁移”,但迁移后必须符合当前仓库的实现约束:

  • 使用 Vue 3 + TresJS,而不是 React Three Fiber
  • 组件逻辑放在 src/components/
  • shader 文件外置到 src/shader/<component-name>/
  • 通过 src/dcsConfig.ts 完成注册和配置 schema
  • 不在 src/views/previewEntry.vue 或 src/views/templateComponet.vue 里加入组件专属分支逻辑

另外,上游点击冲击波来自 mesh 上的 onClick,使用本地 raycast 命中点并调用 stopPropagation()。这种写法在独立 demo 中没问题,但如果后续要放到编辑器里做场景级点选、选中或拾取,它会有明显冲突风险。

目标 / 非目标

目标:

  • 将护盾壳体效果本身迁移为一个可复用的 Vue 3 + TresJS 动态组件。
  • 保留这个组件最有辨识度的视觉特征:球形护盾、六边形纹理、菲涅耳边缘、动态噪声破碎效果和命中冲击波。
  • 将参数面收敛为护盾自身参数,避免配置面板变得臃肿。
  • 让命中冲击波与内部 click 捕获解耦,兼容后续编辑器点选接管事件链。
  • 通过 src/dcsConfig.ts 接入默认值和调参项,并把 shader 放到组件专属目录中。

非目标:

  • 不迁移上游的 React Three Fiber 场景搭建、辅助器、Leva UI 或 demo 布局。
  • 不迁移 transform 控制,也不把通用的 position/rotation/scale 作为组件自身配置契约的一部分。
  • 不迁移 reveal/debug 或 gameplay 风格的状态控制,例如手动 reveal、护盾生命值、伤害值等,除非它们是护盾表面效果本身不可缺少的一部分。
  • 不引入 bloom 或其他后期处理作为组件正确显示的前置条件。

关键决策

  1. 决策:不新增 radius 参数,也不引入 uRadius uniform;保持上游“固定基准球体 + shader 材质”的思路,组件整体尺寸交给宿主场景或编辑器外部 Object3D 缩放处理。
  2. 理由:回查上游实现后可以确认,ForceShield 本身就是用固定球体和材质组合出来的:外层 group 通过 scale 控整体大小,内部 sphereGeometry 使用固定参数 [1.8, 64, 64],index.tsx 中也是这么接的;shader 顶点阶段直接把 position 写入 vObjPos,片元阶段又直接使用 vObjPos.y / 1.8 和默认命中点 vec3(0, 1.8, 0) 这类固定半径假设来做计算,shaderMaterial.ts 里可以看到这一点。既然当前接入目标更接近“迁移材质表现”,就不应该平白扩展成一个额外的半径契约。
  3. 备选方案:新增 radius 参数并改造为 uRadius 驱动的自适配 shader。这个方案技术上可以做,但它已经超出“按原组件思路迁移”的边界,而且你明确说明后续尺寸会由你自己在外层调整,因此首版不应走这条路。

  4. 决策:将护盾 shader 迁移为 src/shader/superEnergyShieldComponent/ 下的外部顶点和片元文件。

  5. 理由:当前仓库已经要求 shader 代码按组件拆分存放。上游实现把 shader 写在 TypeScript 辅助文件里,迁移成 .vert 和 .frag 后更便于维护,也能与现有组件实现方式保持一致。

  6. 备选方案:继续把 shader 字符串内联在 Vue 组件里。技术上可行,但不符合仓库约定,也不利于后续调试和维护。

  7. 决策:参数只保留护盾自身相关项。

  8. 保留参数:

    • 表面:color、opacity、showHex、hexScale、hexOpacity、edgeWidth
    • 菲涅耳:fresnelPower、fresnelStrength、fadeStart
    • 流动与闪烁:flashSpeed、flashIntensity、flowScale、flowSpeed、flowIntensity
    • 噪声边缘:noiseScale、noiseEdgeColor、noiseEdgeWidth、noiseEdgeIntensity、noiseEdgeSmoothness
    • 冲击波:hitRingSpeed、hitRingWidth、hitMaxRadius、hitDuration、hitIntensity、hitImpactRadius
  9. 不保留参数:

    • 组件级尺寸参数,例如 radius
    • transform 类控制
    • 外部场景、辅助器、后期处理开关
    • demo 专属的 reveal/debug 控制
    • gameplay 相关值,例如 shield life、damage
  10. 理由:上游 demo 暴露的参数很多,是为了实验和展示,不适合作为动态组件服务里的正式参数面。这里应该只保留真正改变护盾本体表现的配置。

  11. 备选方案:一比一迁移所有上游参数。这个方案会让配置面板过于嘈杂,也会把 demo 状态和可复用组件参数混在一起。

  12. 决策:保留命中冲击波效果,但把“生成 impact”的逻辑抽成共享入口,而不是强绑定到永远开启的点击事件上。

  13. 理由:命中冲击波是护盾效果的重要组成部分,但上游 demo 的事件处理策略不适合作为长期集成契约。应通过 registerImpact(localPoint, localNormal) 一类统一入口来驱动固定大小的命中缓冲,这样既能支持可选的组件自点击预览模式,也能兼容未来编辑器自己的拾取逻辑。

  14. 集成规则:

    • 默认渲染不依赖组件内部 click 捕获
    • 可选自点击命中模式只在预览或 demo 场景下显式开启
    • 即便开启自点击命中,也不能把无条件 stopPropagation() 作为组件正常工作的前提
  15. 备选方案:彻底去掉命中冲击波。这样虽然能避开事件问题,但会丢掉上游组件最有辨识度的行为之一。

  16. 决策:组件接入统一通过 src/dcsConfig.ts 完成,不改通用预览视图文件。

  17. 理由:当前仓库已经把组件默认值和配置面板集中在 src/dcsConfig.ts 中管理。超级能量盾也应该遵循同样路径。

  18. 备选方案:在通用 preview 视图里为该组件加入特判逻辑。这个方案会让公共预览层和组件能力发生耦合。

  19. 决策:组件必须完全运行在 three.js canvas 渲染管线内,不依赖后期处理才能成立。

  20. 理由:上游效果本质上是 shader 驱动的,迁移后应当在不加 bloom 或 composer 的前提下也能保持有效表现,否则在动态组件服务中嵌入会变得不稳定。

  21. 备选方案:把后期处理视为组件契约的一部分。这样会增加集成复杂度,也违反你给出的迁移边界。

风险与权衡

  • [风险] 透明护盾材质在某些场景中可能出现深度排序问题。
    • 缓解:保持适合护盾的透明材质设置,例如合理控制 depth write 和 render order。
  • [风险] 上游 shader 使用了固定基准半径假设,如果后续有人改成“直接换几何体半径”而不是外部缩放,护盾表现可能失真。
    • 缓解:首版保持与上游一致的基准球体尺寸,不新增 radius/uRadius 契约;如果未来确实要支持几何体半径可配,再单独发起一次 shader 半径归一化改造。
  • [风险] 如果直接照搬上游 onClick,会和未来编辑器选中逻辑冲突。
    • 缓解:将自点击命中模式设为可选,并把冲击波生成逻辑抽象为可复用入口。
  • [风险] 暴露参数过多会让配置面板难以使用。
    • 缓解:首版只保留护盾自身参数,把 demo/debug/gameplay 项全部排除在外。

迁移计划

  • 第一步:先把组件边界、参数契约和交互约束在 OpenSpec 中明确下来。
  • 第二步:实现 Vue/Tres 组件和外置 shader 文件,保持与上游一致的基准球体尺寸假设,并确保外部缩放可以接管整体大小。
  • 第三步:在 src/dcsConfig.ts 中完成组件注册、默认值和配置面板定义。
  • 第四步:验证构建结果和预览表现,并单独验证“默认编辑器安全模式”和“可选自点击命中模式”。
  • 回滚策略:先移除 dcsConfig 注册,再回退组件和 shader 文件,最后清理相关预览资源。

待确认问题

  • 首版是否需要提供一个显式的外部触发 API 来驱动冲击波,还是先保留共享内部 helper + 可选自点击预览模式就足够? 不用。保留共享内部 helper就行,不做额外的适配。
  • 是否需要在同一次实现里补齐组件预览图,还是先临时复用占位资源,等效果稳定后再截图补齐? 不用,我会自行补齐