@fps-games/vfx 0.3.2 → 0.3.3

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 (231) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +5 -4
  3. package/tooling/vfx-cli.mjs +8 -1
  4. package/docs/AUTHORING_EFFECTS.md +0 -297
  5. package/docs/INTEGRATION.md +0 -194
  6. package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
  7. package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
  8. package/docs/VFX_LIBRARY_PLAN.md +0 -546
  9. package/docs/hit-flash-red-method.md +0 -155
  10. package/docs/treasure_attention_fx_babylonjs_guide.md +0 -901
  11. package/docs/vfx-ab/RESULTS.md +0 -35
  12. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
  13. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
  14. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
  15. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
  16. package/docs/vfx-integration_chatgpt.md +0 -462
  17. package/docs/vfx-placement-design.md +0 -164
  18. package/docs/weapon-trail-visualization.html +0 -241
  19. package/packages/EffectPackageService.ts +0 -346
  20. package/packages/SceneVfxService.ts +0 -96
  21. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
  22. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
  23. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
  24. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
  25. package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
  26. package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
  27. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
  28. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
  29. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
  30. package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
  31. package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
  32. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
  33. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
  34. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
  35. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
  36. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
  37. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
  38. package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
  39. package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
  40. package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
  41. package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
  42. package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
  43. package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
  44. package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
  45. package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
  46. package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
  47. package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
  48. package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  49. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
  50. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
  51. package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
  52. package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
  53. package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
  54. package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
  55. package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
  56. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
  57. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
  58. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
  59. package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
  60. package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
  61. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
  62. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  63. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
  64. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
  65. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
  66. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
  67. package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
  68. package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
  69. package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
  70. package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
  71. package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
  72. package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
  73. package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
  74. package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
  75. package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
  76. package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
  77. package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
  78. package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
  79. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
  80. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
  81. package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
  82. package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
  83. package/packages/effects/building-upgrade/effect.json +0 -6
  84. package/packages/effects/building-upgrade/index.ts +0 -387
  85. package/packages/effects/character-entrance/effect.json +0 -6
  86. package/packages/effects/character-entrance/index.ts +0 -308
  87. package/packages/effects/coin-burst/index.ts +0 -48
  88. package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
  89. package/packages/effects/comet-trail-flame/index.ts +0 -107
  90. package/packages/effects/conveyor-upgrade/effect.json +0 -6
  91. package/packages/effects/conveyor-upgrade/index.ts +0 -401
  92. package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
  93. package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
  94. package/packages/effects/debris-smoke/index.ts +0 -102
  95. package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
  96. package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
  97. package/packages/effects/door_open_gold_flakes/index.ts +0 -11
  98. package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
  99. package/packages/effects/electric-arc/index.ts +0 -63
  100. package/packages/effects/energy-shield/effect.json +0 -7
  101. package/packages/effects/energy-shield/index.ts +0 -432
  102. package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
  103. package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
  104. package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
  105. package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
  106. package/packages/effects/explosion/index.ts +0 -115
  107. package/packages/effects/ground-tile-upgrade/effect.json +0 -6
  108. package/packages/effects/ground-tile-upgrade/index.ts +0 -410
  109. package/packages/effects/hit-flash/index.ts +0 -77
  110. package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
  111. package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
  112. package/packages/effects/laser-beam/index.ts +0 -6
  113. package/packages/effects/laser-beam-factory.ts +0 -146
  114. package/packages/effects/lumber-cart-suction/effect.json +0 -6
  115. package/packages/effects/lumber-cart-suction/index.ts +0 -399
  116. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
  117. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
  118. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
  119. package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
  120. package/packages/effects/lumber-debris-scatter/effect.json +0 -228
  121. package/packages/effects/lumber-debris-scatter/index.ts +0 -780
  122. package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
  123. package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
  124. package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
  125. package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
  126. package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -13
  127. package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
  128. package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
  129. package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
  130. package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
  131. package/packages/effects/monster-hit/index.ts +0 -129
  132. package/packages/effects/player-upgrade/effect.json +0 -6
  133. package/packages/effects/player-upgrade/index.ts +0 -640
  134. package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
  135. package/packages/effects/pulse-laser-volley/index.ts +0 -215
  136. package/packages/effects/resource-collect-sparkles/effect.json +0 -6
  137. package/packages/effects/resource-collect-sparkles/index.ts +0 -254
  138. package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
  139. package/packages/effects/run-footstep-dust/effect.json +0 -183
  140. package/packages/effects/run-footstep-dust/index.ts +0 -377
  141. package/packages/effects/space-shared.ts +0 -120
  142. package/packages/effects/thruster-flame/index.ts +0 -306
  143. package/packages/effects/treasure-attention/effect.json +0 -7
  144. package/packages/effects/treasure-attention/index.ts +0 -630
  145. package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
  146. package/packages/effects/tree-chop-dust/effect.json +0 -8
  147. package/packages/effects/tree-chop-dust/index.ts +0 -202
  148. package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
  149. package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
  150. package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
  151. package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
  152. package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
  153. package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
  154. package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
  155. package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
  156. package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
  157. package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
  158. package/packages/effects/ufo-thread-flame/index.ts +0 -153
  159. package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
  160. package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
  161. package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
  162. package/packages/effects/vehicle-upgrade/effect.json +0 -6
  163. package/packages/effects/vehicle-upgrade/index.ts +0 -404
  164. package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
  165. package/packages/effects/weapon-trail/index.ts +0 -317
  166. package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  167. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  168. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  169. package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
  170. package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
  171. package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  172. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  173. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  174. package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
  175. package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
  176. package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
  177. package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  178. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  179. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  180. package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
  181. package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
  182. package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  183. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  184. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  185. package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
  186. package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
  187. package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
  188. package/packages/effects/wood_peel_material_test/index.ts +0 -11
  189. package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
  190. package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
  191. package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
  192. package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
  193. package/packages/env.particle.forest_edge_fog/effect.json +0 -854
  194. package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
  195. package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
  196. package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
  197. package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
  198. package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
  199. package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
  200. package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
  201. package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
  202. package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
  203. package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
  204. package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
  205. package/packages/generation.ts +0 -14
  206. package/packages/index.ts +0 -14
  207. package/packages/legacy-particle-runtime.ts +0 -113
  208. package/packages/manifestRuntimePackage.ts +0 -519
  209. package/packages/registry.ts +0 -85
  210. package/packages/rendering.ts +0 -13
  211. package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
  212. package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
  213. package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
  214. package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
  215. package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
  216. package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
  217. package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
  218. package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
  219. package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
  220. package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
  221. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
  222. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
  223. package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
  224. package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
  225. package/packages/types.ts +0 -195
  226. package/tooling/check-dist-runtime.mjs +0 -166
  227. package/tooling/generate-index.mjs +0 -55
  228. package/tooling/test-guardrails.mjs +0 -157
  229. package/tooling/validate-boundary.mjs +0 -92
  230. package/tooling/validate-effects.mjs +0 -304
  231. package/tooling/validate.mjs +0 -53
package/CHANGELOG.md CHANGED
@@ -12,6 +12,14 @@
12
12
 
13
13
  <!-- release.mjs 在此行下方插入新版本区块 -->
14
14
 
15
+ ## v0.3.3 — 2026-06-30
16
+
17
+ ### 改动
18
+ - fix(npm): exclude source and previews from package
19
+
20
+ ### 特效
21
+ - 无新增 / 删除(仍 37 个)。
22
+
15
23
  ## v0.3.2 — 2026-06-30
16
24
 
17
25
  ### 改动
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.3",
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
  ],
@@ -17,6 +17,9 @@ import { fileURLToPath } from 'node:url';
17
17
  const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
18
18
  const LIB_EFFECTS = join(LIB_ROOT, 'packages', 'effects');
19
19
  const LIB_VERSION = (() => { try { return JSON.parse(readFileSync(join(LIB_ROOT, 'package.json'), 'utf8')).version; } catch { return '0.0.0'; } })();
20
+ const LIB_INDEX = (() => {
21
+ try { return JSON.parse(readFileSync(join(LIB_ROOT, 'index.json'), 'utf8')); } catch { return null; }
22
+ })();
20
23
 
21
24
  const [, , cmd, ...rest] = process.argv;
22
25
  const VALUED_FLAGS = new Set(['--project', '--to']); // 这些 flag 后跟一个值,值不算 positional
@@ -33,10 +36,14 @@ const force = flags.has('--force');
33
36
 
34
37
  // ── 通用 ──────────────────────────────────────────────────────────────────────
35
38
  function libEffectIds() {
39
+ if (Array.isArray(LIB_INDEX?.effects)) return LIB_INDEX.effects.map((e) => e.id).filter(Boolean);
36
40
  return readdirSync(LIB_EFFECTS).filter((e) => {
37
41
  try { return statSync(join(LIB_EFFECTS, e)).isDirectory() && existsSync(join(LIB_EFFECTS, e, 'index.ts')); } catch { return false; }
38
42
  });
39
43
  }
44
+ function hasLibEffect(id) {
45
+ return libEffectIds().includes(id) || existsSync(join(LIB_EFFECTS, id, 'vfx-params.json'));
46
+ }
40
47
  function stub(id) {
41
48
  return `// @fps/vfx re-export 桩(代码/资源在包内;本地仅 vfx-params.json + 此桩)。\nexport * from '@fps/vfx/effects/${id}';\n`;
42
49
  }
@@ -111,7 +118,7 @@ function changelogRange(fromVersion, toVersion) {
111
118
 
112
119
  // ── add ───────────────────────────────────────────────────────────────────────
113
120
  function add(id) {
114
- if (!existsSync(join(LIB_EFFECTS, id))) { console.error(`✗ 库中无特效 "${id}"。可用:run "list"。`); process.exit(1); }
121
+ if (!hasLibEffect(id)) { console.error(`✗ 库中无特效 "${id}"。可用:run "list"。`); process.exit(1); }
115
122
  const destDir = join(effectsDestDir(), id);
116
123
  const stubPath = join(destDir, 'index.ts');
117
124
  const paramsPath = join(destDir, 'vfx-params.json');
@@ -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 面板里能选到、能调、能播、能存。
@@ -1,194 +0,0 @@
1
- # 新项目集成 @fps/vfx(集成指南)
2
-
3
- > 面向**新游戏项目**:如何消费 `@fps/vfx` 包、装配特效、在游戏逻辑里播放(含挂载/瞄准)。
4
- > 代码片段均摘自真实消费方 `pa_template`(`/Users/admin/Code/FPS/pa_template`)。
5
- > 写特效(作者侧)看 [`AUTHORING_EFFECTS.md`](./AUTHORING_EFFECTS.md);放置/inputs 设计看 [`vfx-placement-design.md`](./vfx-placement-design.md)。
6
-
7
- 消费模型 = **"模型 C"**:runtime + 类型来自 `@fps/vfx` 包;本地 effect 经 `import.meta.glob` 装配注册表。库走 **"做法A"** 发布:dist 已提交进 git 仓库并打 tag,消费端 `npm install` 直接 clone 拿到成品 dist,**无需 build/registry/postinstall**。
8
-
9
- ---
10
-
11
- ## 1. 消费 `@fps/vfx`(装包 + 构建配置)
12
-
13
- ### 1.1 加依赖(package.json)
14
- ```jsonc
15
- "dependencies": {
16
- "@babylonjs/core": "8.52.1",
17
- "@babylonjs/loaders": "8.52.1",
18
- "@fps/vfx": "git+ssh://git@github.com/forge-play-studio/fps-vfx-library.git#v0.3.0"
19
- }
20
- ```
21
- - `#vX.Y.Z` tag 即版本锁。`npm install` 把仓库 clone 进 `node_modules/@fps/vfx`,包的 `exports` 指向已提交的 `dist/index.js` + `dist/index.d.ts` + `dist/effects/*`。
22
- - **peerDependencies**:`@babylonjs/core ^8` + `@babylonjs/loaders ^8`,项目自带即可。
23
-
24
- ### 1.2 TypeScript(零配置)
25
- `tsconfig.json` 用 `moduleResolution: "bundler"` 即可解析包的 `exports`/`types`,**不用给 @fps/vfx 配 paths**。
26
-
27
- ### 1.3 Vite(三件事)
28
- ```ts
29
- // ① Babylon 去重为单实例(否则特效与场景用不同 Babylon → 渲染/类型错乱)
30
- resolve: { dedupe: ['@babylonjs/core', '@babylonjs/loaders', '@babylonjs/inspector'] },
31
-
32
- // ② ⭐必做:@fps/vfx 加进构建白名单,否则 `npm run build` 直接 throw
33
- // (pa_template 用了 build-only 的 thirdPartyWhitelistPlugin;dev/serve 不受影响 → 最易漏)
34
- const allowedThirdPartyPackages = ['@babylonjs/core', '@babylonjs/loaders', '@fps/vfx', /* ... */];
35
-
36
- // ③ 单文件打包:dist 含动态 import 的 runtime chunks,这两项把它们内联进单 HTML,无需 copy
37
- build: { rollupOptions: { output: { inlineDynamicImports: true } } }, // + viteSingleFile()
38
- ```
39
- **不需要**给 @fps/vfx 配 alias / optimizeDeps / copy —— 走 Vite 默认 node 解析到 dist,dev 与 prod 一致。
40
-
41
- ---
42
-
43
- ## 2. 装配注册表(模型 C)
44
-
45
- 新建 `src/assets/vfx/index.ts`(barrel,~12 行,新项目几乎一字不改):
46
- ```ts
47
- // VFX registry barrel —— 消费 @fps/vfx(模型 C)。glob 扫本地 effects 桩;runtime/类型来自包。
48
- import { buildRegistry, type VfxRegistry } from '@fps/vfx';
49
-
50
- export * from '@fps/vfx'; // 透传库的全部类型/runtime → 业务侧统一从 '../assets/vfx' 取,不直接 import 包
51
-
52
- const modules = import.meta.glob('./effects/*/index.ts', { eager: true }) as Record<string, Record<string, unknown>>;
53
- const params = import.meta.glob('./effects/*/vfx-params.json', { eager: true }) as Record<string, { default: unknown }>;
54
-
55
- export const VFX_REGISTRY: VfxRegistry = buildRegistry({ modules, params });
56
- ```
57
-
58
- ### 2.1 opt-in 一个特效 = 建一个本地桩
59
- > ⚠️ **glob 只扫本地 `src/assets/vfx/effects/*`;库 dist 里几十个预制特效不会自动进注册表。** 每个要用的特效都得建一个本地桩(哪怕只一行 re-export)。模板出厂 `effects/` 只有 `.gitkeep`,所以 **VFX_REGISTRY 默认是空的**。
60
-
61
- ```ts
62
- // src/assets/vfx/effects/thruster-flame/index.ts —— opt-in 库内预制特效(库 exports 子路径 ./effects/*)
63
- export { thrusterFlameEffectPackage } from '@fps/vfx/effects/thruster-flame';
64
- ```
65
- - (可选)同目录放 `vfx-params.json`(`{ schemaVersion:'vfx-params/1.0', effectId, updatedAt, params }`)作该特效的 `savedParams` 覆写。
66
- - 也可在本地桩里写**项目自有特效**(完整 `VfxEffectPackage`,见 AUTHORING_EFFECTS.md)。
67
- - 坑:两个桩 re-export 同一 `pkg.id` → buildRegistry **直接 throw**(duplicate id)。
68
-
69
- ---
70
-
71
- ## 3. 接线服务(唯一接线点)
72
-
73
- `src/services/SceneVfxService.ts`(项目自有薄封装,**不在库里**):
74
- ```ts
75
- import { EffectPackageService } from '@fps/vfx';
76
- import { VFX_REGISTRY, type VfxSpawnTransform, type VfxParamValues } from '../assets/vfx';
77
-
78
- export class SceneVfxService {
79
- private effectPackageService: EffectPackageService;
80
- constructor(scene: Scene, options: SceneVfxServiceOptions = {}) {
81
- this.effectPackageService = new EffectPackageService(scene, {
82
- registry: VFX_REGISTRY, // ← 注入本地装配的注册表
83
- getDebugAnchorPosition: options.getDebugAnchorPosition, // ← 调试锚点(可选;不传则锚点跟随不可用)
84
- });
85
- }
86
- getEffectPackageService() { return this.effectPackageService; }
87
- playEffectPackage(id: string, params, spawnTransform) { return this.effectPackageService.playEffectPackage(id, params, spawnTransform); }
88
- dispose() { this.effectPackageService.dispose(); }
89
- }
90
- ```
91
-
92
- `src/core/Game.ts`(创建 + 暴露门面):
93
- ```ts
94
- // init():在 entities/zone 之后、gameplay modules 之前创建(gameplay 工厂依赖它非空)
95
- this.sceneVfxService = new SceneVfxService(this.scene);
96
-
97
- // 对外播放门面(游戏逻辑/面板都只认这个,不直接碰 EffectPackageService)
98
- playEffectPackage(id: string, params: Partial<VfxParamValues> = {}, spawnTransform?: VfxSpawnTransform | Vector3) {
99
- return this.sceneVfxService?.playEffectPackage(id, params, spawnTransform) ?? null;
100
- }
101
- getEffectPackageService() { return this.sceneVfxService?.getEffectPackageService() ?? null; }
102
-
103
- // dispose():this.sceneVfxService?.dispose();
104
- ```
105
- gameplay 模块经 `createProjectGameplayRuntime({ ..., sceneVfxService })` 注入拿到入口。
106
-
107
- ---
108
-
109
- ## 4. 播放特效
110
-
111
- `playEffectPackage(effectId, params, spawnTransform)` 返回 `VfxEffectHandle = { root, ready?, dispose() }`。**生命周期由调用方管**。
112
-
113
- ### 4.1 一次性(one-shot)
114
- ```ts
115
- const h = game.playEffectPackage('explosion', {}, { position: hitPoint });
116
- // 自销型特效播完自己消;若要确保清理:
117
- await h?.ready; /* ...用完... */ h?.dispose();
118
- ```
119
-
120
- ### 4.2 持续 / 可重播(单例预览)
121
- ```ts
122
- previewHandle?.dispose(); // 重播前先 dispose 旧的,否则泄漏多份 root
123
- previewHandle = game.playEffectPackage(id, params);
124
- ```
125
-
126
- ### 4.3 放置(placement)——**需 @fps/vfx ≥ v0.2.0**
127
- > v0.1.1 的 `spawnTransform` 只有 `{ position?, rotationY?, renderingGroupId? }`(世界坐标老式范式)。
128
- > PR #12 合并发版后,`spawnTransform` 扩展为下面这套,并按特效的 `placement` 标记放置:
129
-
130
- ```ts
131
- type VfxSpawnTransform = {
132
- position?, rotationY?, rotation?, scale?, renderingGroupId?,
133
- parent?: TransformNode, // 挂载点:特效 root parent 到它 → 经场景图自动跟随
134
- offsetIsLocal?: boolean,
135
- inputs?: { // 运行时外部信息(瞄准点/法线/端点/目标);值可静态或 provider(动态每帧取)
136
- aim?, normal?, endpoints?, target?, scalars?
137
- },
138
- };
139
- ```
140
-
141
- 按特效的 `placement`(socket / world / link / target / screen,见 vfx-placement-design.md):
142
- ```ts
143
- // socket:挂到对象的命名挂点,随它走
144
- game.playEffectPackage('thruster-flame', {}, { parent: engineSocket, offsetIsLocal: true });
145
-
146
- // world:在运行时算出的世界点 spawn,不跟随(命中/爆炸/落点)
147
- game.playEffectPackage('monster-hit', {}, { position: hitPoint });
148
-
149
- // 定向武器(laser/pulse):socket 起点 + inputs.aim 终点(provider 追移动目标)
150
- game.playEffectPackage('laser-beam', {}, {
151
- parent: muzzle, offsetIsLocal: true,
152
- inputs: { aim: () => enemy.getAbsolutePosition() }, // 动态目标每帧取
153
- });
154
- ```
155
- > ⚠️ v0.1.1(及之前)**没有 parent 挂载**。要让特效跟随移动目标,只能自己持有 handle、每帧手写 `handle.root.position = target.position`。升级到 ≥v0.2.0 后改用 `parent`(场景图自动跟随),并用 `inputs.aim` 取代手动追瞄。
156
-
157
- ---
158
-
159
- ## 5. 调试面板(可选)
160
- ```ts
161
- // runtime-debug-bootstrap.ts
162
- runtimePanels.use(mountRuntimeVfxDebugPanel({ root, getGame }));
163
- ```
164
- - 面板经 `getGame().getEffectPackageService().getEffectPackages()` 拉特效列表;`getMergedParams(id)` 拉合并默认值填草稿。
165
- - 保存:POST dev server `/__vfx_debug_overrides {effectId, params}` → 落盘 `src/assets/vfx/effects/<id>/vfx-params.json` + `setDebugOverride(id, params)` 即时生效后 reload(生产无此路由)。
166
- - VFX_REGISTRY 为空时面板自动隐藏(列表为空)。
167
-
168
- ---
169
-
170
- ## 6. 升级 @fps/vfx 版本
171
- 1. **库侧(做法A)**:`pnpm release -- <patch|minor|major>` 一条龙(gate → bump → CHANGELOG → 提交 dist → tag → push)。
172
- 2. **消费侧**:改 `package.json` 的 `#vX.Y.Z` → `npm install`(git 依赖有时需先删 `node_modules/@fps/vfx` 再装才 clone 到新 tag)→ `npm run build` 验白名单与打包。
173
- 3. 详见 `vfx-library-maintenance` skill。
174
-
175
- ---
176
-
177
- ## 7. 常见坑(checklist)
178
- - [ ] **`@fps/vfx` 没进 vite 白名单** → dev 能跑、`npm run build` 直接 throw(最易漏)。
179
- - [ ] **Babylon 没 dedupe** → 特效与场景用不同 Babylon 实例,渲染/类型错乱。
180
- - [ ] **以为装了包就有特效** → 错,glob 只扫本地 `effects/*`,每个特效都要建本地桩。
181
- - [ ] **两个桩 re-export 同一 id** → buildRegistry throw。
182
- - [ ] **业务侧直接 import '@fps/vfx' 类型** → 应统一从 `'../assets/vfx'`(barrel `export *` 透传),保持单一出口。
183
- - [ ] **持续特效不 dispose 旧 handle 就重播** → 泄漏多份 root。
184
- - [ ] **想要挂载/瞄准却在旧版** → 升级到 ≥v0.2.0。
185
-
186
- ## 8. 参考真实文件(pa_template)
187
- | 切面 | 文件 |
188
- |---|---|
189
- | 依赖 | `package.json`(`@fps/vfx` git tag) |
190
- | vite | `vite.config.ts`(dedupe / 白名单 / inlineDynamicImports) |
191
- | 注册表 | `src/assets/vfx/index.ts` + `effects/<id>/index.ts` 桩 |
192
- | 服务接线 | `src/services/SceneVfxService.ts` + `src/core/Game.ts` |
193
- | 播放范式 | `SceneVfxService.warmupEffectPackage`(one-shot)、`runtime-vfx-debug-panel.ts playPreviewNow`(可重播) |
194
- | 调试面板 | `src/debug/runtime-vfx-debug-panel.ts` |