@fps-games/vfx 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +5 -5
  3. package/dist/effects/treasure-attention/index.d.ts +1 -1
  4. package/package.json +6 -5
  5. package/tooling/vfx-cli.mjs +31 -22
  6. package/tooling/vfx-package-resolution.mjs +8 -8
  7. package/docs/AUTHORING_EFFECTS.md +0 -297
  8. package/docs/INTEGRATION.md +0 -194
  9. package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
  10. package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
  11. package/docs/VFX_LIBRARY_PLAN.md +0 -546
  12. package/docs/hit-flash-red-method.md +0 -155
  13. package/docs/treasure_attention_fx_babylonjs_guide.md +0 -901
  14. package/docs/vfx-ab/RESULTS.md +0 -35
  15. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
  16. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
  17. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
  18. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
  19. package/docs/vfx-integration_chatgpt.md +0 -462
  20. package/docs/vfx-placement-design.md +0 -164
  21. package/docs/weapon-trail-visualization.html +0 -241
  22. package/packages/EffectPackageService.ts +0 -346
  23. package/packages/SceneVfxService.ts +0 -96
  24. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
  25. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
  26. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
  27. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
  28. package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
  29. package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
  30. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
  31. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
  32. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
  33. package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
  34. package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
  35. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
  36. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
  37. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
  38. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
  39. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
  40. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
  41. package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
  42. package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
  43. package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
  44. package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
  45. package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
  46. package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
  47. package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
  48. package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
  49. package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
  50. package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
  51. package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  52. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
  53. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
  54. package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
  55. package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
  56. package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
  57. package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
  58. package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
  59. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
  60. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
  61. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
  62. package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
  63. package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
  64. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
  65. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  66. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
  67. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
  68. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
  69. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
  70. package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
  71. package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
  72. package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
  73. package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
  74. package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
  75. package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
  76. package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
  77. package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
  78. package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
  79. package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
  80. package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
  81. package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
  82. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
  83. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
  84. package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
  85. package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
  86. package/packages/effects/building-upgrade/effect.json +0 -6
  87. package/packages/effects/building-upgrade/index.ts +0 -387
  88. package/packages/effects/character-entrance/effect.json +0 -6
  89. package/packages/effects/character-entrance/index.ts +0 -308
  90. package/packages/effects/coin-burst/index.ts +0 -48
  91. package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
  92. package/packages/effects/comet-trail-flame/index.ts +0 -107
  93. package/packages/effects/conveyor-upgrade/effect.json +0 -6
  94. package/packages/effects/conveyor-upgrade/index.ts +0 -401
  95. package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
  96. package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
  97. package/packages/effects/debris-smoke/index.ts +0 -102
  98. package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
  99. package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
  100. package/packages/effects/door_open_gold_flakes/index.ts +0 -11
  101. package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
  102. package/packages/effects/electric-arc/index.ts +0 -63
  103. package/packages/effects/energy-shield/effect.json +0 -7
  104. package/packages/effects/energy-shield/index.ts +0 -432
  105. package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
  106. package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
  107. package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
  108. package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
  109. package/packages/effects/explosion/index.ts +0 -115
  110. package/packages/effects/ground-tile-upgrade/effect.json +0 -6
  111. package/packages/effects/ground-tile-upgrade/index.ts +0 -410
  112. package/packages/effects/hit-flash/index.ts +0 -77
  113. package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
  114. package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
  115. package/packages/effects/laser-beam/index.ts +0 -6
  116. package/packages/effects/laser-beam-factory.ts +0 -146
  117. package/packages/effects/lumber-cart-suction/effect.json +0 -6
  118. package/packages/effects/lumber-cart-suction/index.ts +0 -399
  119. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
  120. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
  121. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
  122. package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
  123. package/packages/effects/lumber-debris-scatter/effect.json +0 -228
  124. package/packages/effects/lumber-debris-scatter/index.ts +0 -780
  125. package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
  126. package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
  127. package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
  128. package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
  129. package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -13
  130. package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
  131. package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
  132. package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
  133. package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
  134. package/packages/effects/monster-hit/index.ts +0 -129
  135. package/packages/effects/player-upgrade/effect.json +0 -6
  136. package/packages/effects/player-upgrade/index.ts +0 -640
  137. package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
  138. package/packages/effects/pulse-laser-volley/index.ts +0 -215
  139. package/packages/effects/resource-collect-sparkles/effect.json +0 -6
  140. package/packages/effects/resource-collect-sparkles/index.ts +0 -254
  141. package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
  142. package/packages/effects/run-footstep-dust/effect.json +0 -183
  143. package/packages/effects/run-footstep-dust/index.ts +0 -377
  144. package/packages/effects/space-shared.ts +0 -120
  145. package/packages/effects/thruster-flame/index.ts +0 -306
  146. package/packages/effects/treasure-attention/effect.json +0 -7
  147. package/packages/effects/treasure-attention/index.ts +0 -630
  148. package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
  149. package/packages/effects/tree-chop-dust/effect.json +0 -8
  150. package/packages/effects/tree-chop-dust/index.ts +0 -202
  151. package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
  152. package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
  153. package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
  154. package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
  155. package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
  156. package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
  157. package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
  158. package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
  159. package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
  160. package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
  161. package/packages/effects/ufo-thread-flame/index.ts +0 -153
  162. package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
  163. package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
  164. package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
  165. package/packages/effects/vehicle-upgrade/effect.json +0 -6
  166. package/packages/effects/vehicle-upgrade/index.ts +0 -404
  167. package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
  168. package/packages/effects/weapon-trail/index.ts +0 -317
  169. package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  170. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  171. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  172. package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
  173. package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
  174. package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  175. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  176. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  177. package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
  178. package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
  179. package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
  180. package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  181. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  182. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  183. package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
  184. package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
  185. package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  186. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  187. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  188. package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
  189. package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
  190. package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
  191. package/packages/effects/wood_peel_material_test/index.ts +0 -11
  192. package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
  193. package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
  194. package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
  195. package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
  196. package/packages/env.particle.forest_edge_fog/effect.json +0 -854
  197. package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
  198. package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
  199. package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
  200. package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
  201. package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
  202. package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
  203. package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
  204. package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
  205. package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
  206. package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
  207. package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
  208. package/packages/generation.ts +0 -14
  209. package/packages/index.ts +0 -14
  210. package/packages/legacy-particle-runtime.ts +0 -113
  211. package/packages/manifestRuntimePackage.ts +0 -519
  212. package/packages/registry.ts +0 -85
  213. package/packages/rendering.ts +0 -13
  214. package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
  215. package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
  216. package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
  217. package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
  218. package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
  219. package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
  220. package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
  221. package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
  222. package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
  223. package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
  224. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
  225. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
  226. package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
  227. package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
  228. package/packages/types.ts +0 -195
  229. package/tooling/check-dist-runtime.mjs +0 -166
  230. package/tooling/generate-index.mjs +0 -55
  231. package/tooling/test-guardrails.mjs +0 -157
  232. package/tooling/validate-boundary.mjs +0 -92
  233. package/tooling/validate-effects.mjs +0 -304
  234. package/tooling/validate.mjs +0 -53
package/CHANGELOG.md CHANGED
@@ -12,6 +12,22 @@
12
12
 
13
13
  <!-- release.mjs 在此行下方插入新版本区块 -->
14
14
 
15
+ ## v0.3.4 — 2026-06-30
16
+
17
+ ### 改动
18
+ - fix(tooling): use @fps-games package name
19
+
20
+ ### 特效
21
+ - 无新增 / 删除(仍 37 个)。
22
+
23
+ ## v0.3.3 — 2026-06-30
24
+
25
+ ### 改动
26
+ - fix(npm): exclude source and previews from package
27
+
28
+ ### 特效
29
+ - 无新增 / 删除(仍 37 个)。
30
+
15
31
  ## v0.3.2 — 2026-06-30
16
32
 
17
33
  ### 改动
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @fps/vfx
1
+ # @fps-games/vfx
2
2
 
3
3
  Babylon.js 共享 VFX 特效库。导入即用 + 项目内调试面板。消费模型 = **模型 C**(runtime/类型来自包,本地 `import.meta.glob` 装配注册表);发布 = **做法A**(dist 提交进 git + tag,消费端 clone 即用,无需 build)。
4
4
 
@@ -15,12 +15,12 @@ Babylon.js 共享 VFX 特效库。导入即用 + 项目内调试面板。消费
15
15
 
16
16
  ```jsonc
17
17
  // package.json —— git tag 即版本锁
18
- "@fps/vfx": "git+ssh://git@github.com/forge-play-studio/fps-vfx-library.git#v0.3.0"
18
+ "@fps-games/vfx": "^0.3.4"
19
19
  ```
20
20
  ```ts
21
21
  // src/assets/vfx/index.ts —— 装配注册表(模型 C)
22
- import { buildRegistry, type VfxRegistry } from '@fps/vfx';
23
- export * from '@fps/vfx';
22
+ import { buildRegistry, type VfxRegistry } from '@fps-games/vfx';
23
+ export * from '@fps-games/vfx';
24
24
  const modules = import.meta.glob('./effects/*/index.ts', { eager: true }) as Record<string, Record<string, unknown>>;
25
25
  const params = import.meta.glob('./effects/*/vfx-params.json', { eager: true }) as Record<string, { default: unknown }>;
26
26
  export const VFX_REGISTRY: VfxRegistry = buildRegistry({ modules, params });
@@ -31,7 +31,7 @@ const svc = new EffectPackageService(scene, { registry: VFX_REGISTRY });
31
31
  svc.playEffectPackage('thruster-flame', {}, { parent: engineSocket, offsetIsLocal: true });
32
32
  ```
33
33
 
34
- > ⚠️ 三个最易漏:① `@fps/vfx` 要加进 vite 构建白名单;② Babylon 要 `resolve.dedupe`;③ glob 只扫本地 `effects/*`,每个要用的特效都得建本地桩(re-export 库内预制即可)。详见 [INTEGRATION.md](./docs/INTEGRATION.md)。
34
+ > ⚠️ 三个最易漏:① `@fps-games/vfx` 要加进 vite 构建白名单;② Babylon 要 `resolve.dedupe`;③ glob 只扫本地 `effects/*`,每个要用的特效都得建本地桩(re-export 库内预制即可)。详见 [INTEGRATION.md](./docs/INTEGRATION.md)。
35
35
 
36
36
  ## 开发
37
37
 
@@ -38,7 +38,7 @@ export interface CreateTreasureAttentionOptions {
38
38
  renderingGroupId?: number;
39
39
  glowLayer?: GlowLayer | null;
40
40
  disposeRoot?: boolean;
41
- /** 经 @fps/vfx 包播放时为 true:root 的 transform 归 service 独占(placement/挂载由 service 管),特效不写 root.position(G6)。 */
41
+ /** 经 @fps-games/vfx 包播放时为 true:root 的 transform 归 service 独占(placement/挂载由 service 管),特效不写 root.position(G6)。 */
42
42
  serviceOwnedRoot?: boolean;
43
43
  }
44
44
  export interface TreasureAttentionHandle extends VfxEffectHandle {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fps-games/vfx",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "description": "Shared VFX library for Playable projects: engine-injected effect packages + runtime. Project-pure (no imports of host project code).",
6
6
  "publishConfig": {
@@ -21,9 +21,10 @@
21
21
  },
22
22
  "files": [
23
23
  "dist",
24
- "tooling",
25
- "packages",
26
- "docs",
24
+ "tooling/vfx-cli.mjs",
25
+ "tooling/vfx-package-resolution.mjs",
26
+ "packages/effects/*/vfx-params.json",
27
+ "README.md",
27
28
  "CHANGELOG.md",
28
29
  "index.json"
29
30
  ],
@@ -44,7 +45,7 @@
44
45
  "demo:treasure-attention": "vite demo/treasure-attention --host 127.0.0.1",
45
46
  "release": "node scripts/release.mjs"
46
47
  },
47
- "//": "分发=git tag + 提交 dist(做法A):发版前 pnpm build → 提交 dist/ → git tag。消费端 import '@fps/vfx' 解析到 dist;本地开发可用 FPS_VFX_LIBRARY_REPO 切回源码 alias。",
48
+ "//": "分发=git tag + 提交 dist(做法A):发版前 pnpm build → 提交 dist/ → git tag。消费端 import '@fps-games/vfx' 解析到 dist;本地开发可用 FPS_VFX_LIBRARY_REPO 切回源码 alias。",
48
49
  "devDependencies": {
49
50
  "@babylonjs/core": "^8.56.2",
50
51
  "@babylonjs/loaders": "^8.56.2",
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- // @fps/vfx CLI:把库特效以「re-export 桩 + 本地 vfx-params.json」装进项目(plan §6.2/§16 混合分发 模型C),
2
+ // @fps-games/vfx CLI:把库特效以「re-export 桩 + 本地 vfx-params.json」装进项目(plan §6.2/§16 混合分发 模型C),
3
3
  // 并管理版本升级(issue #4)。消费方典型调用:
4
- // node node_modules/@fps/vfx/tooling/vfx-cli.mjs <cmd> --project .
4
+ // node node_modules/@fps-games/vfx/tooling/vfx-cli.mjs <cmd> --project .
5
5
  //
6
6
  // list 列出库里可用特效
7
7
  // add <id> [--project <dir>] [--force] 装一个特效(桩 + vfx-params.json + .vfx-source.json)
8
- // outdated [--project <dir>] 对比项目钉的 @fps/vfx 版本 / 各特效 provenance vs 最新
8
+ // outdated [--project <dir>] 对比项目钉的 @fps-games/vfx 版本 / 各特效 provenance vs 最新
9
9
  // update [<id>] [--project <dir>] [--to vX.Y.Z]
10
- // 升级:改 package.json 的 @fps/vfx git-tag ref + 重同步桩
10
+ // 升级:改 package.json 的 @fps-games/vfx git-tag ref + 重同步桩
11
11
  // + 不动 vfx-params.json + 打印 CHANGELOG 区间(然后 pnpm install)
12
12
  import { readdirSync, statSync, existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
13
13
  import { execFileSync } from 'node:child_process';
@@ -16,7 +16,11 @@ import { fileURLToPath } from 'node:url';
16
16
 
17
17
  const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
18
18
  const LIB_EFFECTS = join(LIB_ROOT, 'packages', 'effects');
19
+ const PACKAGE_NAME = '@fps-games/vfx';
19
20
  const LIB_VERSION = (() => { try { return JSON.parse(readFileSync(join(LIB_ROOT, 'package.json'), 'utf8')).version; } catch { return '0.0.0'; } })();
21
+ const LIB_INDEX = (() => {
22
+ try { return JSON.parse(readFileSync(join(LIB_ROOT, 'index.json'), 'utf8')); } catch { return null; }
23
+ })();
20
24
 
21
25
  const [, , cmd, ...rest] = process.argv;
22
26
  const VALUED_FLAGS = new Set(['--project', '--to']); // 这些 flag 后跟一个值,值不算 positional
@@ -33,16 +37,20 @@ const force = flags.has('--force');
33
37
 
34
38
  // ── 通用 ──────────────────────────────────────────────────────────────────────
35
39
  function libEffectIds() {
40
+ if (Array.isArray(LIB_INDEX?.effects)) return LIB_INDEX.effects.map((e) => e.id).filter(Boolean);
36
41
  return readdirSync(LIB_EFFECTS).filter((e) => {
37
42
  try { return statSync(join(LIB_EFFECTS, e)).isDirectory() && existsSync(join(LIB_EFFECTS, e, 'index.ts')); } catch { return false; }
38
43
  });
39
44
  }
45
+ function hasLibEffect(id) {
46
+ return libEffectIds().includes(id) || existsSync(join(LIB_EFFECTS, id, 'vfx-params.json'));
47
+ }
40
48
  function stub(id) {
41
- return `// @fps/vfx re-export 桩(代码/资源在包内;本地仅 vfx-params.json + 此桩)。\nexport * from '@fps/vfx/effects/${id}';\n`;
49
+ return `// ${PACKAGE_NAME} re-export 桩(代码/资源在包内;本地仅 vfx-params.json + 此桩)。\nexport * from '${PACKAGE_NAME}/effects/${id}';\n`;
42
50
  }
43
51
  function effectsDestDir() { return join(projectRoot, 'src', 'assets', 'vfx', 'effects'); }
44
52
  function provenance(id, version) {
45
- return JSON.stringify({ id, source: '@fps/vfx', version }, null, 2) + '\n';
53
+ return JSON.stringify({ id, source: PACKAGE_NAME, version }, null, 2) + '\n';
46
54
  }
47
55
  function normVer(v) { return String(v ?? '').replace(/^v/, ''); }
48
56
  function semverCmp(a, b) {
@@ -51,17 +59,17 @@ function semverCmp(a, b) {
51
59
  return 0;
52
60
  }
53
61
 
54
- // 读项目 package.json 里的 @fps/vfx 依赖(原始文本 + 解析值)。
62
+ // 读项目 package.json 里的 VFX 依赖(原始文本 + 解析值)。
55
63
  function readProjectVfxDep() {
56
64
  const p = join(projectRoot, 'package.json');
57
65
  if (!existsSync(p)) return null;
58
66
  const raw = readFileSync(p, 'utf8');
59
67
  const json = JSON.parse(raw);
60
68
  for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
61
- const val = json[field]?.['@fps/vfx'];
62
- if (val) return { path: p, raw, json, field, value: val };
69
+ const deps = json[field] ?? {};
70
+ if (deps[PACKAGE_NAME]) return { path: p, raw, json, field, packageName: PACKAGE_NAME, value: deps[PACKAGE_NAME] };
63
71
  }
64
- return { path: p, raw, json, field: null, value: null };
72
+ return { path: p, raw, json, field: null, packageName: null, value: null };
65
73
  }
66
74
  function pinFromValue(value) { const m = String(value ?? '').match(/#v?(\d+\.\d+\.\d+)/); return m ? m[1] : null; }
67
75
  function repoUrlFromValue(value) { return String(value ?? '').replace(/#.*$/, ''); }
@@ -111,7 +119,7 @@ function changelogRange(fromVersion, toVersion) {
111
119
 
112
120
  // ── add ───────────────────────────────────────────────────────────────────────
113
121
  function add(id) {
114
- if (!existsSync(join(LIB_EFFECTS, id))) { console.error(`✗ 库中无特效 "${id}"。可用:run "list"。`); process.exit(1); }
122
+ if (!hasLibEffect(id)) { console.error(`✗ 库中无特效 "${id}"。可用:run "list"。`); process.exit(1); }
115
123
  const destDir = join(effectsDestDir(), id);
116
124
  const stubPath = join(destDir, 'index.ts');
117
125
  const paramsPath = join(destDir, 'vfx-params.json');
@@ -143,7 +151,7 @@ function outdated() {
143
151
  const latestSource = remote ? '远端最新 tag' : `本库版本(远端不可达,回退)`;
144
152
 
145
153
  console.log(`项目:${projectRoot}`);
146
- console.log(`@fps/vfx 钉版本:${pin ? `v${pin}` : '(未钉 / 非 git-tag)'} → 最新:v${latest}(${latestSource})`);
154
+ console.log(`${dep.packageName ?? PACKAGE_NAME} 钉版本:${pin ? `v${pin}` : '(未钉 / 非 git-tag)'} → 最新:v${latest}(${latestSource})`);
147
155
  const depBehind = pin && semverCmp(pin, latest) < 0;
148
156
  console.log(depBehind ? `⬆ 依赖落后:可升级到 v${latest}(\`vfx update --to v${latest}\` + pnpm install)` : `✓ 依赖已是最新(或无法判定)。`);
149
157
 
@@ -155,7 +163,7 @@ function outdated() {
155
163
  const tag = v == null ? '无 provenance(早期批量安装)' : (semverCmp(v, latest) < 0 ? `v${v} ⬆ 落后` : `v${v} ✓`);
156
164
  console.log(` - ${e.id} [${tag}]`);
157
165
  }
158
- console.log(`\n注:模型C 下特效代码/资源在 @fps/vfx 包内,真实版本由上面的依赖钉版本决定;`);
166
+ console.log(`\n注:模型C 下特效代码/资源在 ${PACKAGE_NAME} 包内,真实版本由上面的依赖钉版本决定;`);
159
167
  console.log(` .vfx-source.json 仅作 add 时的来源记录,落后不影响运行(随依赖升级即同步)。`);
160
168
  } else {
161
169
  console.log(`\n(项目 ${effectsDestDir()} 下暂无已装特效)`);
@@ -171,7 +179,7 @@ function update(onlyId) {
171
179
  const toFlag = flagVal('--to');
172
180
  const target = normVer(toFlag ?? latestRemoteTag(repoUrl) ?? LIB_VERSION);
173
181
 
174
- // 1) 改 package.json 的 @fps/vfx git-tag ref(保留原 url 与格式,仅换 #ref)。
182
+ // 1) 改 package.json 的 VFX git-tag ref(保留原 url 与格式,仅换 #ref)。
175
183
  const isGitDep = /(^git\+|:\/\/|github\.com|\.git)/.test(dep.value ?? '');
176
184
  if (dep.field && dep.value && (/#/.test(dep.value) || isGitDep)) {
177
185
  let newValue;
@@ -179,21 +187,22 @@ function update(onlyId) {
179
187
  else newValue = `${dep.value}#v${target}`;
180
188
  if (newValue !== dep.value) {
181
189
  // 原文本就地替换,避免重排整份 package.json;用替换函数避免 newValue 里的 $ 被当替换记号。
182
- const depValRe = new RegExp(`("@fps/vfx"\\s*:\\s*")${dep.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(")`);
190
+ const depName = dep.packageName ?? PACKAGE_NAME;
191
+ const depValRe = new RegExp(`("${depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:\\s*")${dep.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(")`);
183
192
  const newRaw = dep.raw.replace(depValRe, (_m, p1, p2) => `${p1}${newValue}${p2}`);
184
193
  if (newRaw !== dep.raw) {
185
194
  writeFileSync(dep.path, newRaw);
186
- console.log(`✓ package.json:@fps/vfx ${oldPin ? `#v${oldPin}` : dep.value} → #v${target}`);
195
+ console.log(`✓ package.json:${depName} ${oldPin ? `#v${oldPin}` : dep.value} → ${PACKAGE_NAME}#v${target}`);
187
196
  } else {
188
- console.warn(`! 未能就地替换 @fps/vfx 依赖文本,请手动把 ref 改成 #v${target}。`);
197
+ console.warn(`! 未能就地替换 ${depName} 依赖文本,请手动把 ref 改成 ${PACKAGE_NAME}#v${target}。`);
189
198
  }
190
199
  } else {
191
- console.log(`= package.json:@fps/vfx 已是 #v${target}。`);
200
+ console.log(`= package.json:${dep.packageName ?? PACKAGE_NAME} 已是 #v${target}。`);
192
201
  }
193
202
  } else if (dep.field && dep.value) {
194
- console.warn(`! @fps/vfx 依赖 "${dep.value}" 不是 git-tag 形式,跳过 ref 升级(仅重同步桩)。如需固定版本,请手动改成 git+ssh://…#v${target}。`);
203
+ console.warn(`! ${dep.packageName ?? PACKAGE_NAME} 依赖 "${dep.value}" 不是 git-tag 形式,跳过 ref 升级(仅重同步桩)。如需固定版本,请手动改成 git+ssh://…#v${target}。`);
195
204
  } else {
196
- console.warn(`! 项目 package.json 未声明 @fps/vfx 依赖,跳过 ref 升级(仅同步桩)。`);
205
+ console.warn(`! 项目 package.json 未声明 ${PACKAGE_NAME} 依赖,跳过 ref 升级(仅同步桩)。`);
197
206
  }
198
207
 
199
208
  // 2) 重同步桩 + bump .vfx-source.json(永不动 vfx-params.json)。
@@ -212,12 +221,12 @@ function update(onlyId) {
212
221
  const range = changelogRange(oldPin, target);
213
222
  if (range) { console.log(`\n── CHANGELOG ${oldPin ? `v${oldPin} → ` : ''}v${target} ──\n${range}\n`); }
214
223
 
215
- console.log(`\n下一步:\`pnpm install\`(拉取 @fps/vfx#v${target} 新代码/资源)。`);
224
+ console.log(`\n下一步:\`pnpm install\`(拉取 ${PACKAGE_NAME}#v${target} 新代码/资源)。`);
216
225
  }
217
226
 
218
227
  // ── dispatch ──────────────────────────────────────────────────────────────────
219
228
  if (cmd === 'list') {
220
- console.log(`@fps/vfx 可用特效(v${LIB_VERSION}):`);
229
+ console.log(`${PACKAGE_NAME} 可用特效(v${LIB_VERSION}):`);
221
230
  for (const id of libEffectIds().sort()) console.log(` - ${id}`);
222
231
  } else if (cmd === 'add') {
223
232
  if (!positional[0]) { console.error('用法: add <id> [--project <dir>] [--force]'); process.exit(1); }
@@ -1,6 +1,6 @@
1
- // @fps/vfx 的 Vite 接入助手(plan §18.3:克隆 editor 蓝图,简化为单包)。
1
+ // @fps-games/vfx 的 Vite 接入助手(plan §18.3:克隆 editor 蓝图,简化为单包)。
2
2
  // 消费方 vite.config:
3
- // import { createVfxLibrarySourceAlias, VFX_BABYLON_DEDUPE } from '@fps/vfx/vite'(或本文件路径)
3
+ // import { createVfxLibrarySourceAlias, VFX_BABYLON_DEDUPE } from '@fps-games/vfx/vite'(或本文件路径)
4
4
  // resolve: { alias: [...createVfxLibrarySourceAlias(process.env.FPS_VFX_LIBRARY_REPO)], dedupe: [...VFX_BABYLON_DEDUPE] }
5
5
  import { resolve } from 'node:path';
6
6
 
@@ -8,7 +8,7 @@ import { resolve } from 'node:path';
8
8
  export const VFX_BABYLON_DEDUPE = ['@babylonjs/core', '@babylonjs/loaders'];
9
9
 
10
10
  /**
11
- * 源码模式:把 `@fps/vfx` / `@fps/vfx/effects/<id>` 别名到本地库源(供 dev / SDK 联调)。
11
+ * 源码模式:把 `@fps-games/vfx` / `@fps-games/vfx/effects/<id>` 别名到本地库源(供 dev / SDK 联调)。
12
12
  * @param {string} libRepoRoot fps-vfx-library 仓库根(通常来自 process.env.FPS_VFX_LIBRARY_REPO)
13
13
  * @returns vite alias 数组(给 resolve.alias)
14
14
  */
@@ -18,11 +18,11 @@ export function createVfxLibrarySourceAlias(libRepoRoot) {
18
18
  }
19
19
  const pkgs = resolve(libRepoRoot, 'packages');
20
20
  return [
21
- // 子路径:@fps/vfx/effects/<id> → packages/effects/<id>/index.ts
22
- { find: /^@fps\/vfx\/effects\/(.+)$/, replacement: resolve(pkgs, 'effects') + '/$1/index.ts' },
23
- // 根入口:@fps/vfx → packages/index.ts
24
- { find: /^@fps\/vfx$/, replacement: resolve(pkgs, 'index.ts') },
21
+ // 子路径:@fps-games/vfx/effects/<id> → packages/effects/<id>/index.ts
22
+ { find: /^@fps-games\/vfx\/effects\/(.+)$/, replacement: resolve(pkgs, 'effects') + '/$1/index.ts' },
23
+ // 根入口:@fps-games/vfx → packages/index.ts
24
+ { find: /^@fps-games\/vfx$/, replacement: resolve(pkgs, 'index.ts') },
25
25
  // vite 助手子路径(本文件)
26
- { find: /^@fps\/vfx\/vite$/, replacement: resolve(libRepoRoot, 'tooling', 'vfx-package-resolution.mjs') },
26
+ { find: /^@fps-games\/vfx\/vite$/, replacement: resolve(libRepoRoot, 'tooling', 'vfx-package-resolution.mjs') },
27
27
  ];
28
28
  }
@@ -1,297 +0,0 @@
1
- # 如何制作一个 VFX 特效(@fps/vfx 作者指南)
2
-
3
- 面向在 `fps-vfx-library` 里**新增/修改特效**的人(human 或 coding agent)。读完你能从零写出一个能被项目导入、能在 DEV 调试面板里调参的特效。
4
-
5
- 配套:运行手册见 `.claude/skills/vfx-library-maintenance/`;库总设计见 issue #1 / `docs/VFX_LIBRARY_PLAN.md`;参数运转的代码细节见本文 §5。
6
-
7
- ---
8
-
9
- ## 0. 核心概念:一个特效 = 一个 `VfxEffectPackage`
10
-
11
- 每个特效是 `packages/effects/<dir>/index.ts` 导出的一个对象,实现 `VfxEffectPackage`(定义在 `packages/types.ts`):
12
-
13
- ```ts
14
- interface VfxEffectPackage<P> {
15
- id: string; // 全局唯一 id
16
- nameZh: string; // 中文名(面板下拉显示)
17
- defaultParams: P; // 参数默认值(运行时真正用的数据)
18
- debugParams: VfxParamDefinition[]; // 参数的 UI 描述(面板照它建控件)
19
- create(ctx): VfxEffectHandle; // 唯一入口:用 ctx + params 在场景里建出特效
20
- debugAnchor?: 'playerRight' | 'player'; // 面板里默认 spawn 锚点
21
- manifest?: VfxEffectManifest; // 可选:检索元数据
22
- spawnParams?, savedParams?; // 可选:见 §5
23
- }
24
- ```
25
-
26
- 宿主(项目)只通过 `playEffectPackage(id, params, transform)` 触发它;特效拿到 `ctx`(scene/root/position/params)自给自足地建粒子/网格。**特效绝不 import 项目代码**(纯净契约,§7)。
27
-
28
- **两类实现**:
29
- - **code 类**(逃生舱):`index.ts` 手写 `create()`,直接用 Babylon `ParticleSystem`/`Sprite`/`Mesh`。适合复杂、跟随、mesh/GLB 特效。**本指南主讲这类。**
30
- - **data 类**(数据驱动):`index.ts` 是薄包装,调工厂 `createManifestRuntimeEffectPackage` 读 manifest JSON。适合标准粒子、AI 可生成。见 §6。
31
-
32
- ---
33
-
34
- ## 1. 五分钟:一个最小 code 特效
35
-
36
- 目录:`packages/effects/ember-sparks/`
37
-
38
- ```
39
- ember-sparks/
40
- ├── index.ts # 特效本体
41
- ├── vfx-params.json # 调参种子(项目会拷走并各自调)
42
- └── assets/textures/ember_spark.png # 贴图(经 ?url 打包内联)
43
- ```
44
-
45
- `index.ts`:
46
-
47
- ```ts
48
- import { Color4 } from '@babylonjs/core/Maths/math.color';
49
- import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem';
50
- import { Texture } from '@babylonjs/core/Materials/Textures/texture';
51
- import type { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
52
- import { VFX_RENDERING_GROUP_ID } from '../../rendering';
53
- import sparkTextureUrl from './assets/textures/ember_spark.png?url'; // ✅ 资源用 ?url
54
-
55
- // 1) 参数类型 + 默认值
56
- interface EmberSparksParams extends VfxParamValues {
57
- lifetime: number; // 特效整体存活秒数
58
- count: number; // 粒子数
59
- size: number; // 粒子大小
60
- speed: number; // 飞散速度
61
- color: VfxColorValue; // 颜色 {r,g,b}(0..1)
62
- }
63
-
64
- const DEFAULT_PARAMS: EmberSparksParams = {
65
- lifetime: 1.2, count: 60, size: 0.12, speed: 3, color: { r: 1, g: 0.6, b: 0.15 },
66
- };
67
-
68
- export const emberSparksEffectPackage: VfxEffectPackage<EmberSparksParams> = {
69
- id: 'ember-sparks',
70
- nameZh: '火星迸射',
71
- defaultParams: DEFAULT_PARAMS,
72
- debugAnchor: 'playerRight',
73
-
74
- // 2) 面板控件:每条 = 一个旋钮。key 必须与 defaultParams / create() 读取的 key 一致(见 §4)
75
- debugParams: [
76
- { key: 'lifetime', label: '存活时长', kind: 'lifetime', min: 0.2, max: 5, step: 0.1 },
77
- { key: 'count', label: '粒子数', kind: 'number', min: 1, max: 300, step: 1 },
78
- { key: 'size', label: '粒子大小', kind: 'scale', min: 0.01,max: 2, step: 0.01 },
79
- { key: 'speed', label: '飞散速度', kind: 'number', min: 0, max: 20, step: 0.1 },
80
- { key: 'color', label: '颜色', kind: 'color' },
81
- ],
82
-
83
- // 3) create:用 ctx + params 建特效。每个 params.<key> 来自五层合并后的最终值
84
- create({ scene, root, position, renderingGroupId, params }) {
85
- root.position.copyFrom(position);
86
- const ps = new ParticleSystem('ember_sparks', Math.ceil(params.count), scene);
87
- ps.particleTexture = new Texture(sparkTextureUrl, scene);
88
- ps.emitter = root; // 挂在 root 上 → 宿主 reparent root 即可跟随物体
89
- ps.minSize = ps.maxSize = params.size;
90
- ps.minEmitPower = params.speed * 0.5;
91
- ps.maxEmitPower = params.speed;
92
- ps.color1 = new Color4(params.color.r, params.color.g, params.color.b, 1);
93
- ps.emitRate = params.count / params.lifetime;
94
- ps.renderingGroupId = renderingGroupId ?? VFX_RENDERING_GROUP_ID; // ✅ 用库常量,别写 magic number
95
- ps.start();
96
-
97
- const stopTimer = window.setTimeout(() => ps.stop(), params.lifetime * 1000);
98
- // 4) 必须返回 handle:root + dispose(清理一切,别漏 timer/observer/system)
99
- return {
100
- root,
101
- dispose() { window.clearTimeout(stopTimer); ps.dispose(); root.dispose(); },
102
- };
103
- },
104
- };
105
- ```
106
-
107
- `vfx-params.json`(种子,项目会拷走并各自调;`params` 里只放想覆盖默认值的项,空也行):
108
-
109
- ```json
110
- { "schemaVersion": "vfx-params/1.0", "effectId": "ember-sparks", "params": {} }
111
- ```
112
-
113
- 校验 + 自测:
114
-
115
- ```bash
116
- node tooling/validate-effects.mjs --effect ember-sparks # 单个特效快速 lint
117
- pnpm validate # 全量(含 build + dist 扫描)
118
- ```
119
-
120
- 装进一个项目试调:`node node_modules/@fps/vfx/tooling/vfx-cli.mjs add ember-sparks --project .` → `pnpm dev` → 右下 **VFX** 按钮 → 选「火星迸射」→ 调参 → 播放/保存。
121
-
122
- ---
123
-
124
- ## 2. `create(ctx)` 契约
125
-
126
- `ctx`(`VfxEffectPackageContext`)给你:
127
-
128
- | 字段 | 含义 |
129
- |---|---|
130
- | `scene` | Babylon `Scene` |
131
- | `root` | 已建好的 `TransformNode`,已放到 spawn 位置;**把特效挂在它下面**,宿主 reparent 它即可跟随 |
132
- | `position` | spawn 世界坐标(= `root.position` 的副本) |
133
- | `rotationY` | spawn 朝向 |
134
- | `renderingGroupId` | 渲染组(可选;没有就用 `VFX_RENDERING_GROUP_ID`) |
135
- | `params` | **五层合并后的最终参数**(见 §5),按 key 读 |
136
-
137
- 返回 `VfxEffectHandle`:
138
-
139
- ```ts
140
- interface VfxEffectHandle {
141
- root: TransformNode;
142
- ready?: Promise<void>; // 异步加载(GLB/贴图)时返回它;宿主可 await(warmup 用),别漏
143
- dispose(): void; // 清理一切:粒子系统 / observer / setTimeout / mesh / root.dispose()
144
- }
145
- ```
146
-
147
- 规则:
148
- - **跟随型特效用 code 类**,把发射器挂 `root`,宿主 reparent root 即跟随(`root.getWorldMatrix()`)。data 类一次性定位、不跟随。
149
- - **异步加载**(GLB、贴图)→ 把加载 promise 放 `ready`,且 `dispose` 要能在加载未完成时也安全清理(置 `disposed` 标志)。参考 `lumber-debris-scatter`。
150
- - `create()` 内部抛错不会崩场景(service 有 try/catch 兜底),但**别把资源泄漏在错误路径**。
151
-
152
- ---
153
-
154
- ## 3. `debugParams` 是什么、面板怎么用它
155
-
156
- `debugParams` 是**参数的 UI 描述**;DEV 调试面板**照它自动生成控件**(`runtime-vfx-debug-panel.ts` 读 `effectPackage.debugParams`,一条 = 一个旋钮)。你不用写任何面板代码——声明即出控件。
157
-
158
- 每条是一个 `VfxParamDefinition`:
159
-
160
- | `kind` | 面板控件 | 额外字段 | 值类型 |
161
- |---|---|---|---|
162
- | `number` | 数字输入框 | `min` / `max` / `step` | `number` |
163
- | `lifetime` | 数字框(语义=时长) | `min` / `max` / `step` | `number` |
164
- | `scale` | 数字框(语义=缩放) | `min` / `max` / `step` | `number` |
165
- | `opacity` | 数字框(语义=透明度) | `min` / `max` / `step` | `number` |
166
- | `color` | 取色器 | — | `{ r, g, b }`(0..1) |
167
- | `enum` | 下拉 | `options: [{value,label}]` | `string` |
168
-
169
- 公共字段:`key`(必填,见 §4)、`label`(中文标签)、`description?`。
170
-
171
- ```ts
172
- { key: 'scatterShape', label: '散落形态', kind: 'enum',
173
- options: [ { value: 'radial', label: '中心外扩' }, { value: 'vertical', label: '垂直下落' } ] }
174
- ```
175
-
176
- > 不想暴露到面板的内部参数:放 `defaultParams` 但**不放** `debugParams` 即可(常见且允许)。反过来不行——见 §4。
177
-
178
- ---
179
-
180
- ## 4. ⭐ key 一致性:面板↔参数靠 `key` 字符串对齐
181
-
182
- 三处用**同一个 key 串**起来:
183
-
184
- ```
185
- debugParams[i].key ←→ defaultParams[key] ←→ create() 里读的 params[key]
186
- ```
187
-
188
- - 面板把旋钮的值写进 `params[key]`;`create()` 用 `params[key]` 去设粒子属性。key 对不上 → **旋钮调了没反应**。
189
- - 硬规则(guardrail 强制):**每个 `debugParams[].key` 必须在 `defaultParams` 里有默认值**。`validate-effects.mjs` 会静态检查;拼错 key(如 `partcileCount`)会被拦下:
190
- ```
191
- [参数] debugParams 旋钮 [partcileCount] 在 defaultParams 里没有对应默认值 → 面板调它不生效…
192
- ```
193
- - 反向(`defaultParams` 有、`debugParams` 没有)= 内部参数,允许(不强制每个值都给旋钮)。
194
- - 你在 `create()` 里读哪些 `params.<key>`,就应该在 `defaultParams` 给默认值;想让人调的,再加进 `debugParams`。
195
-
196
- ---
197
-
198
- ## 5. 参数怎么运转(五层合并)
199
-
200
- `playEffectPackage(id, explicitParams, transform)`(`EffectPackageService.ts`)按顺序合并 5 层,**后面的盖前面的**,得到最终 `params` 传给 `create()`:
201
-
202
- ```
203
- 1. 生成默认(spawn 位置偏移 offsetX/Y/Z)
204
- 2. defaultParams —— 你在库里写的默认值
205
- 3. savedParams —— 项目的 vfx-params.json(经 buildRegistry 注入)
206
- 4. 运行时 override —— 面板调参时 setDebugOverride 实时写的
207
- 5. explicitParams —— 调用方临时传的(gameplay 触发时算的动态值)
208
- ```
209
-
210
- 含义:
211
- - **面板调参 = 第 4 层**;点「保存」→ 写回项目 `effects/<id>/vfx-params.json`(第 3 层)→ 下次加载即新默认。
212
- - **库代码不被改**(模型 C:代码在 `@fps/vfx` 包,调参在项目的 `vfx-params.json`)。
213
- - **面板调好的值,游戏正式跑也生效**——gameplay 的 system 触发也走同一条合并。
214
-
215
- 调参面板的存盘端点(DEV):面板「保存」→ `POST /__debug_panel_config?file=effects/<id>/vfx-params.json`(vite 中间件落盘,只写 `params`)。
216
-
217
- ---
218
-
219
- ## 6. data 类特效(manifest + 工厂)
220
-
221
- 标准粒子可以不手写 `create()`,改用工厂读 manifest:
222
-
223
- ```ts
224
- // packages/effects/spark_puff/index.ts
225
- import manifest from './spark_puff.json'; // vfx-effect-package 格式(emitters + tunableParams)
226
- import runtimeUrl from './assets/runtime/particleEmitter.v1.js?url'; // 引擎适配器,?url 注入
227
- import texUrl from './assets/textures/spark.png?url';
228
- import { createManifestRuntimeEffectPackage } from '../../manifestRuntimePackage';
229
-
230
- export const sparkPuffEffectPackage = createManifestRuntimeEffectPackage({
231
- id: 'spark_puff',
232
- manifest,
233
- assetUrls: {
234
- 'assets/runtime/particleEmitter.v1.js': runtimeUrl,
235
- 'assets/textures/spark.png': texUrl,
236
- },
237
- });
238
- ```
239
-
240
- - `defaultParams` / `debugParams` / `nameZh` 由**工厂从 manifest 的 `tunableParams` 生成**(所以 data 类 index.ts 里看不到它们字面量,key 检查也跳过)。
241
- - 双发射器(dust + 碎屑)有专用工厂 `createWoodSawdustEffectPackage`(见 `wood-crusher-sawdust`)。
242
- - data 类**一次性定位、不跟随**;要跟随用 code 类。
243
-
244
- ---
245
-
246
- ## 7. 硬约束:库纯净契约(违反会被 guardrail 拦)
247
-
248
- 1. **不 import 任何项目代码**。只允许 `@babylonjs/*`、库内相对路径(`../../types`/`../../rendering`)、自身资源。特效↔宿主唯一通道 = `create(ctx)`。
249
- 2. **资源自带**:贴图/GLB/runtime-js 放包内,`import x from './assets/…?url'`。**禁 `new URL(…, import.meta.url)`**(库 build 不内联它)。
250
- 3. **glTF 只用核心 loader**:`import '@babylonjs/loaders/glTF/2.0/glTFLoader'`。**禁全量 barrel**(`@babylonjs/loaders` / `…/glTF`)——会把 `MeshoptDecoder` 拉进 consumer 包,违反 Playable 策略(#2 v0.1.1)。GLB 必须 plain(零扩展)。见 `lumber-debris-scatter`。
251
- 4. **配置参数化**:任何"配置值"做成 `defaultParams`/`debugParams`,经 `params` 传入。
252
- 5. **渲染组用常量** `VFX_RENDERING_GROUP_ID`,别写 magic number。
253
-
254
- ---
255
-
256
- ## 8. 工作流:校验 → 安装 → 发版
257
-
258
- ```bash
259
- # 作者本地(改完特效):
260
- node tooling/validate-effects.mjs --effect <id> # 单特效快速 lint
261
- pnpm validate # 全量 guardrail(boundary+lint+typecheck+build+dist)
262
- pnpm test:guardrails # guardrail 回归
263
-
264
- # 在某项目里调试:
265
- node node_modules/@fps/vfx/tooling/vfx-cli.mjs add <id> --project . # 装桩+种子params
266
- pnpm dev # → VFX 面板调参/保存
267
-
268
- # 发版(让消费方拿到新特效):
269
- pnpm release -- minor --dry-run # 预演(新增特效=minor)
270
- pnpm release -- minor # gate→bump→index/CHANGELOG→提交dist→tag→push
271
- ```
272
-
273
- ---
274
-
275
- ## 9. 常见坑
276
-
277
- | 坑 | 现象 | 修 |
278
- |---|---|---|
279
- | 全量 glTF loader | prod 构建被 meshopt 检查卡住 | 用 `@babylonjs/loaders/glTF/2.0/glTFLoader` |
280
- | `new URL(...)` 引资源 | 库 build 后资源丢失 | 改 `import x from './…?url'` |
281
- | debugParams key 拼错 | 面板旋钮调了没反应 | key 对齐 defaultParams(guardrail 会拦) |
282
- | 想跟随却用了 data 类 | 特效不跟物体 | 用 code 类,挂 `root`,宿主 reparent |
283
- | `dispose` 漏清理 | 切场景/重复播泄漏 | dispose 清 system/observer/timer/mesh/root |
284
- | 异步加载没给 `ready` | warmup 时机错乱 | 返回 handle 时带 `ready: Promise` |
285
- | 引用项目模块 | boundary 报错 | 改成 ctx 注入 / 自带资源 |
286
-
287
- ---
288
-
289
- ## 10. 提交前 checklist
290
-
291
- - [ ] `packages/effects/<dir>/index.ts` 导出 `VfxEffectPackage`(`id`/`nameZh`/`defaultParams`/`debugParams`/`create`)。
292
- - [ ] 资源在 `assets/` 下,经 `?url` 引;GLB 用核心 loader。
293
- - [ ] `debugParams` 每个 `key` 都在 `defaultParams`(面板↔参数对齐)。
294
- - [ ] `create()` 用 `ctx` + `params`,不 import 项目;`dispose()` 清理彻底。
295
- - [ ] 有 `vfx-params.json` 种子(合法 JSON)。
296
- - [ ] `pnpm validate` + `pnpm test:guardrails` 全绿。
297
- - [ ] DEV 面板里能选到、能调、能播、能存。