@fps-games/vfx 0.1.1 → 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 (250) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +41 -0
  3. package/dist/effects/coin-burst/index.js +6 -2
  4. package/dist/effects/comet-trail-flame/index.js +3 -1
  5. package/dist/effects/debris-smoke/index.js +1 -0
  6. package/dist/effects/electric-arc/index.js +1 -0
  7. package/dist/effects/energy-shield/index.js +1 -0
  8. package/dist/effects/explosion/index.js +3 -2
  9. package/dist/effects/hit-flash/index.js +12 -4
  10. package/dist/effects/laser-beam/index.js +14 -2
  11. package/dist/effects/lumber_vehicle_upgrade_aura/index.js +1 -0
  12. package/dist/effects/monster-hit/index.js +2 -1
  13. package/dist/effects/player-upgrade/index.js +5 -3
  14. package/dist/effects/pulse-laser-volley/index.js +14 -4
  15. package/dist/effects/thruster-flame/index.js +3 -1
  16. package/dist/effects/treasure-attention/index.d.ts +54 -0
  17. package/dist/effects/treasure-attention/index.js +535 -0
  18. package/dist/effects/ufo-thread-flame/index.js +1 -0
  19. package/dist/effects/vehicle-upgrade/index.js +4 -1
  20. package/dist/effects/weapon-trail/index.js +8 -3
  21. package/dist/effects/wood_crusher_sawdust/index.d.ts +4 -0
  22. package/dist/effects/wood_cutter_sawdust/index.d.ts +4 -0
  23. package/dist/index.js +85 -17
  24. package/dist/types.d.ts +80 -0
  25. package/index.json +19 -1
  26. package/package.json +9 -4
  27. package/packages/effects/treasure-attention/vfx-params.json +49 -0
  28. package/tooling/vfx-cli.mjs +8 -1
  29. package/docs/AUTHORING_EFFECTS.md +0 -297
  30. package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
  31. package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
  32. package/docs/VFX_LIBRARY_PLAN.md +0 -546
  33. package/docs/vfx-ab/RESULTS.md +0 -35
  34. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
  35. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
  36. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
  37. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
  38. package/docs/vfx-integration_chatgpt.md +0 -462
  39. package/docs/weapon-trail-visualization.html +0 -241
  40. package/packages/EffectPackageService.ts +0 -240
  41. package/packages/SceneVfxService.ts +0 -96
  42. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
  43. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
  44. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
  45. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
  46. package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
  47. package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
  48. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
  49. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
  50. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
  51. package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
  52. package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
  53. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
  54. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
  55. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
  56. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
  57. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
  58. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
  59. package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
  60. package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
  61. package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
  62. package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
  63. package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
  64. package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
  65. package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
  66. package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
  67. package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
  68. package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
  69. package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  70. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
  71. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
  72. package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
  73. package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
  74. package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
  75. package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
  76. package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
  77. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
  78. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
  79. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
  80. package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
  81. package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
  82. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
  83. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  84. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
  85. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
  86. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
  87. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
  88. package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
  89. package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
  90. package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
  91. package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
  92. package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
  93. package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
  94. package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
  95. package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
  96. package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
  97. package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
  98. package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
  99. package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
  100. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
  101. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
  102. package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
  103. package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
  104. package/packages/effects/building-upgrade/effect.json +0 -6
  105. package/packages/effects/building-upgrade/index.ts +0 -387
  106. package/packages/effects/character-entrance/effect.json +0 -6
  107. package/packages/effects/character-entrance/index.ts +0 -308
  108. package/packages/effects/coin-burst/index.ts +0 -43
  109. package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
  110. package/packages/effects/comet-trail-flame/index.ts +0 -106
  111. package/packages/effects/conveyor-upgrade/effect.json +0 -6
  112. package/packages/effects/conveyor-upgrade/index.ts +0 -401
  113. package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
  114. package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
  115. package/packages/effects/debris-smoke/index.ts +0 -101
  116. package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
  117. package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
  118. package/packages/effects/door_open_gold_flakes/index.ts +0 -11
  119. package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
  120. package/packages/effects/electric-arc/index.ts +0 -62
  121. package/packages/effects/energy-shield/effect.json +0 -7
  122. package/packages/effects/energy-shield/index.ts +0 -431
  123. package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
  124. package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
  125. package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
  126. package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
  127. package/packages/effects/explosion/index.ts +0 -114
  128. package/packages/effects/ground-tile-upgrade/effect.json +0 -6
  129. package/packages/effects/ground-tile-upgrade/index.ts +0 -410
  130. package/packages/effects/hit-flash/index.ts +0 -69
  131. package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
  132. package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
  133. package/packages/effects/laser-beam/index.ts +0 -6
  134. package/packages/effects/laser-beam-factory.ts +0 -135
  135. package/packages/effects/lumber-cart-suction/effect.json +0 -6
  136. package/packages/effects/lumber-cart-suction/index.ts +0 -399
  137. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
  138. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
  139. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
  140. package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
  141. package/packages/effects/lumber-debris-scatter/effect.json +0 -228
  142. package/packages/effects/lumber-debris-scatter/index.ts +0 -780
  143. package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
  144. package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
  145. package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
  146. package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
  147. package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -11
  148. package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
  149. package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
  150. package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
  151. package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
  152. package/packages/effects/monster-hit/index.ts +0 -128
  153. package/packages/effects/player-upgrade/effect.json +0 -6
  154. package/packages/effects/player-upgrade/index.ts +0 -635
  155. package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
  156. package/packages/effects/pulse-laser-volley/index.ts +0 -204
  157. package/packages/effects/resource-collect-sparkles/effect.json +0 -6
  158. package/packages/effects/resource-collect-sparkles/index.ts +0 -254
  159. package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
  160. package/packages/effects/run-footstep-dust/effect.json +0 -183
  161. package/packages/effects/run-footstep-dust/index.ts +0 -377
  162. package/packages/effects/space-shared.ts +0 -120
  163. package/packages/effects/thruster-flame/index.ts +0 -305
  164. package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
  165. package/packages/effects/tree-chop-dust/effect.json +0 -8
  166. package/packages/effects/tree-chop-dust/index.ts +0 -202
  167. package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
  168. package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
  169. package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
  170. package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
  171. package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
  172. package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
  173. package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
  174. package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
  175. package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
  176. package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
  177. package/packages/effects/ufo-thread-flame/index.ts +0 -152
  178. package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
  179. package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
  180. package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
  181. package/packages/effects/vehicle-upgrade/effect.json +0 -6
  182. package/packages/effects/vehicle-upgrade/index.ts +0 -401
  183. package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
  184. package/packages/effects/weapon-trail/index.ts +0 -313
  185. package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  186. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  187. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  188. package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
  189. package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
  190. package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  191. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  192. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  193. package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
  194. package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
  195. package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
  196. package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  197. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  198. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  199. package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
  200. package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
  201. package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  202. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  203. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  204. package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
  205. package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
  206. package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
  207. package/packages/effects/wood_peel_material_test/index.ts +0 -11
  208. package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
  209. package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
  210. package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
  211. package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
  212. package/packages/env.particle.forest_edge_fog/effect.json +0 -854
  213. package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
  214. package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
  215. package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
  216. package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
  217. package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
  218. package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
  219. package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
  220. package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
  221. package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
  222. package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
  223. package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
  224. package/packages/generation.ts +0 -14
  225. package/packages/index.ts +0 -14
  226. package/packages/legacy-particle-runtime.ts +0 -113
  227. package/packages/manifestRuntimePackage.ts +0 -519
  228. package/packages/registry.ts +0 -85
  229. package/packages/rendering.ts +0 -13
  230. package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
  231. package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
  232. package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
  233. package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
  234. package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
  235. package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
  236. package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
  237. package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
  238. package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
  239. package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
  240. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
  241. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
  242. package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
  243. package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
  244. package/packages/types.ts +0 -107
  245. package/tooling/check-dist-runtime.mjs +0 -166
  246. package/tooling/generate-index.mjs +0 -55
  247. package/tooling/test-guardrails.mjs +0 -157
  248. package/tooling/validate-boundary.mjs +0 -92
  249. package/tooling/validate-effects.mjs +0 -304
  250. package/tooling/validate.mjs +0 -53
@@ -1,183 +0,0 @@
1
- {
2
- "schemaVersion": "build.particle/v1",
3
- "id": "build.particle.run_footstep_dust",
4
- "effectId": "run_footstep_dust",
5
- "displayName": {
6
- "en": "Run Footstep Dust",
7
- "zh": "跑动脚步烟尘"
8
- },
9
- "kind": "runFootstepDust",
10
- "runtime": {
11
- "runtimeId": "runFootstepDust/v1",
12
- "path": "assets/runtime/runFootstepDust.v1.js",
13
- "entry": "createRunFootstepDustRuntime"
14
- },
15
- "assets": {
16
- "runtime": [
17
- {
18
- "id": "run_footstep_dust_runtime",
19
- "packagePath": "assets/runtime/runFootstepDust.v1.js"
20
- }
21
- ],
22
- "source": [
23
- {
24
- "kind": "typescript",
25
- "id": "run_footstep_dust_source_ts",
26
- "packagePath": "source/src/effects/runFootstepDust.ts",
27
- "sourcePath": "D:/project/loy_babylon_particle_test/src/effects/runFootstepDust.ts"
28
- }
29
- ]
30
- },
31
- "emitters": [
32
- {
33
- "name": "run_footstep_dust",
34
- "enabled": true,
35
- "deterministic": true,
36
- "type": "ParticleSystem",
37
- "capacity": 64,
38
- "count": 6,
39
- "frequency": 4.5,
40
- "particleColor": "#ffffff",
41
- "spread": 0.3,
42
- "lifeTime": 0.42,
43
- "speed": 0.56,
44
- "particleSize": 0.18,
45
- "effectScale": 1,
46
- "trailOffset": 0.22,
47
- "emissionDirection": {
48
- "x": 1,
49
- "y": 0.2,
50
- "z": 0
51
- },
52
- "gravity": {
53
- "x": 0,
54
- "y": -0.08,
55
- "z": 0
56
- },
57
- "origin": {
58
- "x": 0,
59
- "y": 0,
60
- "z": 0
61
- },
62
- "runtime": {
63
- "continuous": true,
64
- "disposeOnComplete": false
65
- }
66
- }
67
- ],
68
- "parameters": [
69
- {
70
- "key": "count",
71
- "label": "Count",
72
- "type": "number",
73
- "default": 6,
74
- "min": 0,
75
- "max": 999,
76
- "step": 1,
77
- "mapsTo": "emitters[0].count",
78
- "note": "Particles emitted per dust burst behind the character."
79
- },
80
- {
81
- "key": "frequency",
82
- "label": "Frequency",
83
- "type": "number",
84
- "default": 4.5,
85
- "min": 0,
86
- "max": 30,
87
- "step": 0.01,
88
- "mapsTo": "emitters[0].frequency",
89
- "note": "Dust bursts per second for continuous playback."
90
- },
91
- {
92
- "key": "particleColor",
93
- "label": "Color",
94
- "type": "color",
95
- "default": "#ffffff",
96
- "mapsTo": "emitters[0].particleColor",
97
- "note": "Base tint for the dust particles."
98
- },
99
- {
100
- "key": "spread",
101
- "label": "Spread",
102
- "type": "number",
103
- "default": 0.3,
104
- "min": 0,
105
- "max": 20,
106
- "step": 0.01,
107
- "mapsTo": "emitters[0].spread"
108
- },
109
- {
110
- "key": "life",
111
- "label": "Life",
112
- "type": "number",
113
- "default": 0.42,
114
- "min": 0.01,
115
- "max": 20,
116
- "step": 0.01,
117
- "mapsTo": "emitters[0].lifeTime"
118
- },
119
- {
120
- "key": "speed",
121
- "label": "Speed",
122
- "type": "number",
123
- "default": 0.56,
124
- "min": 0,
125
- "max": 20,
126
- "step": 0.01,
127
- "mapsTo": "emitters[0].speed"
128
- },
129
- {
130
- "key": "size",
131
- "label": "Size",
132
- "type": "number",
133
- "default": 1,
134
- "min": 0,
135
- "max": 20,
136
- "step": 0.01,
137
- "mapsTo": "emitters[0].particleSize",
138
- "note": "Editor multiplies this by base particleSize 0.18."
139
- },
140
- {
141
- "key": "scale",
142
- "label": "Scale",
143
- "type": "number",
144
- "default": 1,
145
- "min": 0.01,
146
- "max": 20,
147
- "step": 0.01,
148
- "mapsTo": "emitters[0].effectScale"
149
- },
150
- {
151
- "key": "dirX",
152
- "label": "Dir X",
153
- "type": "range",
154
- "default": 100,
155
- "min": -100,
156
- "max": 100,
157
- "mapsTo": "emitters[0].emissionDirection.x"
158
- },
159
- {
160
- "key": "dirY",
161
- "label": "Dir Y",
162
- "type": "range",
163
- "default": 20,
164
- "min": 20,
165
- "max": 120,
166
- "mapsTo": "emitters[0].emissionDirection.y"
167
- },
168
- {
169
- "key": "dirZ",
170
- "label": "Dir Z",
171
- "type": "range",
172
- "default": 0,
173
- "min": -100,
174
- "max": 100,
175
- "mapsTo": "emitters[0].emissionDirection.z"
176
- }
177
- ],
178
- "notes": [
179
- "Continuous Unity-style particle playback: the runtime emits deterministic cartoon dust puffs behind the character at the configured frequency.",
180
- "Count controls particles per burst, not total lifetime particles.",
181
- "Dust is spawned behind the emitter based on the current direction controls."
182
- ]
183
- }
@@ -1,377 +0,0 @@
1
- import { Color4 } from '@babylonjs/core/Maths/math.color';
2
- import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector';
3
- import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
4
- import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem';
5
- import type { Particle } from '@babylonjs/core/Particles/particle';
6
- import type { Scene } from '@babylonjs/core/scene';
7
- import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
8
- import type { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
9
- import { VFX_RENDERING_GROUP_ID } from '../../rendering';
10
- import sourceManifest from './effect.json';
11
-
12
- const RUN_FOOTSTEP_DUST_RENDERING_GROUP_ID = VFX_RENDERING_GROUP_ID;
13
-
14
- interface RunFootstepDustParams extends VfxParamValues {
15
- emitMode: string;
16
- lifetime: number;
17
- scale: number;
18
- initialHeight: number;
19
- color: VfxColorValue;
20
- opacity: number;
21
- count: number;
22
- frequency: number;
23
- emitInterval: number;
24
- spread: number;
25
- particleLife: number;
26
- speed: number;
27
- size: number;
28
- trailOffset: number;
29
- directionX: number;
30
- directionZ: number;
31
- lift: number;
32
- gravity: number;
33
- }
34
-
35
- interface SourceEmitter {
36
- capacity?: unknown;
37
- count?: unknown;
38
- frequency?: unknown;
39
- particleColor?: unknown;
40
- spread?: unknown;
41
- lifeTime?: unknown;
42
- speed?: unknown;
43
- particleSize?: unknown;
44
- effectScale?: unknown;
45
- trailOffset?: unknown;
46
- emissionDirection?: { x?: unknown; y?: unknown; z?: unknown };
47
- gravity?: { y?: unknown };
48
- }
49
-
50
- const SOURCE_EMITTER = readSourceEmitter();
51
- const BASE_PARTICLE_SIZE = readNumber(SOURCE_EMITTER.particleSize, 0.18);
52
- const EFFECT_MANIFEST = {
53
- id: 'build.particle.run_footstep_dust',
54
- nameZh: '跑动脚步烟尘',
55
- assets: [
56
- 'assets/runtime/runFootstepDust.v1.js',
57
- ],
58
- source: 'run_footstep_dust.json',
59
- };
60
-
61
- const DEFAULT_PARAMS: RunFootstepDustParams = {
62
- emitMode: 'timed',
63
- lifetime: 1.35,
64
- scale: readNumber(SOURCE_EMITTER.effectScale, 1),
65
- initialHeight: 0.04,
66
- color: readColor(SOURCE_EMITTER.particleColor, { r: 1, g: 1, b: 1 }),
67
- opacity: 1,
68
- count: readNumber(SOURCE_EMITTER.count, 6),
69
- frequency: readNumber(SOURCE_EMITTER.frequency, 4.5),
70
- emitInterval: 1 / Math.max(0.01, readNumber(SOURCE_EMITTER.frequency, 4.5)),
71
- spread: readNumber(SOURCE_EMITTER.spread, 0.3),
72
- particleLife: readNumber(SOURCE_EMITTER.lifeTime, 0.42),
73
- speed: readNumber(SOURCE_EMITTER.speed, 0.56),
74
- size: 1,
75
- trailOffset: readNumber(SOURCE_EMITTER.trailOffset, 0.22),
76
- directionX: readNumber(SOURCE_EMITTER.emissionDirection?.x, 1),
77
- directionZ: readNumber(SOURCE_EMITTER.emissionDirection?.z, 0),
78
- lift: readNumber(SOURCE_EMITTER.emissionDirection?.y, 0.2),
79
- gravity: Math.abs(readNumber(SOURCE_EMITTER.gravity?.y, -0.08)),
80
- };
81
-
82
- export const runFootstepDustEffectPackage: VfxEffectPackage<RunFootstepDustParams> = {
83
- id: 'run-footstep-dust',
84
- nameZh: '跑动脚步烟尘',
85
- manifest: EFFECT_MANIFEST,
86
- defaultParams: DEFAULT_PARAMS,
87
- debugAnchor: 'player',
88
- debugParams: [
89
- {
90
- key: 'emitMode',
91
- label: '发射模式',
92
- kind: 'enum',
93
- options: [
94
- { value: 'loop', label: '持续发射' },
95
- { value: 'timed', label: '自定义时间' },
96
- ],
97
- },
98
- { key: 'lifetime', label: '生命周期', kind: 'lifetime', min: 0.08, max: 6, step: 0.01 },
99
- { key: 'emitInterval', label: '发射间隔', kind: 'number', min: 0.01, max: 5, step: 0.01 },
100
- { key: 'scale', label: '整体缩放', kind: 'scale', min: 0.05, max: 6, step: 0.01 },
101
- { key: 'initialHeight', label: '初始高度', kind: 'number', min: -0.5, max: 2, step: 0.01 },
102
- { key: 'color', label: '烟尘颜色', kind: 'color' },
103
- { key: 'opacity', label: '透明度', kind: 'opacity', min: 0, max: 1, step: 0.01 },
104
- { key: 'count', label: '每步粒子数', kind: 'number', min: 0, max: 80, step: 1 },
105
- { key: 'frequency', label: '脚步频率', kind: 'number', min: 0, max: 20, step: 0.01 },
106
- { key: 'spread', label: '散开范围', kind: 'number', min: 0, max: 4, step: 0.01 },
107
- { key: 'particleLife', label: '单粒生命周期', kind: 'number', min: 0.04, max: 3, step: 0.01 },
108
- { key: 'speed', label: '飘散速度', kind: 'number', min: 0, max: 5, step: 0.01 },
109
- { key: 'size', label: '烟尘大小', kind: 'number', min: 0.05, max: 6, step: 0.01 },
110
- { key: 'trailOffset', label: '身后偏移', kind: 'number', min: 0, max: 2, step: 0.01 },
111
- { key: 'directionX', label: '方向 X', kind: 'number', min: -1, max: 1, step: 0.01 },
112
- { key: 'directionZ', label: '方向 Z', kind: 'number', min: -1, max: 1, step: 0.01 },
113
- { key: 'lift', label: '上扬力度', kind: 'number', min: 0, max: 2, step: 0.01 },
114
- { key: 'gravity', label: '下沉重力', kind: 'number', min: 0, max: 3, step: 0.01 },
115
- ],
116
- create({ scene, root, position, renderingGroupId, params }) {
117
- root.position.copyFrom(position).addInPlace(new Vector3(0, params.initialHeight, 0));
118
- const system = createFootstepDustSystem(scene, root, params, renderingGroupId);
119
- const runner = createStepEmitter(scene, system, params);
120
- let disposed = false;
121
- let stopTimer: number | null = null;
122
- let disposeTimer: number | null = null;
123
-
124
- system.start();
125
- runner.start();
126
-
127
- if (readEmitMode(params.emitMode) === 'timed') {
128
- stopTimer = window.setTimeout(() => runner.stop(), Math.max(0.01, params.lifetime) * 1000);
129
- disposeTimer = window.setTimeout(
130
- () => disposeAll(),
131
- Math.max(0.05, params.lifetime + params.particleLife + 0.25) * 1000,
132
- );
133
- }
134
-
135
- return {
136
- root,
137
- dispose() {
138
- disposeAll();
139
- },
140
- };
141
-
142
- function disposeAll(): void {
143
- if (disposed) return;
144
- disposed = true;
145
- if (stopTimer !== null) window.clearTimeout(stopTimer);
146
- if (disposeTimer !== null) window.clearTimeout(disposeTimer);
147
- runner.dispose();
148
- system.dispose(false);
149
- root.dispose();
150
- }
151
- },
152
- };
153
-
154
- function createFootstepDustSystem(
155
- scene: Scene,
156
- root: TransformNode,
157
- params: RunFootstepDustParams,
158
- renderingGroupId?: number,
159
- ): ParticleSystem {
160
- const count = Math.max(0, Math.round(params.count));
161
- const capacity = Math.max(48, Math.round(readNumber(SOURCE_EMITTER.capacity, count * Math.max(6, params.frequency * params.particleLife * 3))));
162
- const system = new ParticleSystem('run-footstep-dust', capacity, scene);
163
- const effectScale = Math.max(0.01, params.scale);
164
- const particleSize = Math.max(0.001, BASE_PARTICLE_SIZE * params.size * effectScale);
165
- const dustColor = params.color;
166
- const movementDirection = new Vector3(params.directionX, 0, params.directionZ);
167
-
168
- if (movementDirection.lengthSquared() < 0.0001) movementDirection.set(1, 0, 0);
169
- movementDirection.normalize();
170
-
171
- const sideDirection = new Vector3(-movementDirection.z, 0, movementDirection.x);
172
- const backwardDirection = movementDirection.scale(-1);
173
- const spread = Math.max(0, params.spread) * effectScale;
174
- const speed = Math.max(0, params.speed) * effectScale;
175
- const trailOffset = Math.max(0, params.trailOffset) * effectScale;
176
-
177
- system.renderingGroupId = renderingGroupId ?? RUN_FOOTSTEP_DUST_RENDERING_GROUP_ID;
178
- system.particleTexture = getFootstepDustTexture(scene);
179
- system.emitter = root as any;
180
- system.emitRate = 0;
181
- system.manualEmitCount = 0;
182
- system.updateSpeed = 1 / 60;
183
- system.minLifeTime = Math.max(0.03, params.particleLife);
184
- system.maxLifeTime = Math.max(system.minLifeTime, params.particleLife);
185
- system.minSize = particleSize;
186
- system.maxSize = particleSize;
187
- system.minEmitPower = speed;
188
- system.maxEmitPower = speed;
189
- system.minAngularSpeed = 0;
190
- system.maxAngularSpeed = 0;
191
- system.gravity = new Vector3(0, -Math.max(0, params.gravity) * effectScale, 0);
192
- system.blendMode = ParticleSystem.BLENDMODE_STANDARD;
193
- system.color1 = tintColor(dustColor, 1.04, 0.58 * params.opacity);
194
- system.color2 = tintColor(dustColor, 0.78, 0.4 * params.opacity);
195
- system.colorDead = tintColor(dustColor, 0.52, 0);
196
- system.addColorGradient(0, tintColor(dustColor, 1.04, 0));
197
- system.addColorGradient(0.12, tintColor(dustColor, 1.04, 0.6 * params.opacity));
198
- system.addColorGradient(0.58, tintColor(dustColor, 0.86, 0.42 * params.opacity));
199
- system.addColorGradient(1, tintColor(dustColor, 0.58, 0));
200
- system.addSizeGradient(0, particleSize * 0.5);
201
- system.addSizeGradient(0.28, particleSize * 1.18);
202
- system.addSizeGradient(0.72, particleSize * 1.65);
203
- system.addSizeGradient(1, particleSize * 1.95);
204
- system.addVelocityGradient(0, 1);
205
- system.addVelocityGradient(0.35, 0.34);
206
- system.addVelocityGradient(1, 0.03);
207
- system.addDragGradient(0, 0.85);
208
- system.addDragGradient(1, 1.9);
209
-
210
- let positionIndex = 0;
211
- let directionIndex = 0;
212
- system.startPositionFunction = (worldMatrix: Matrix, positionToUpdate: Vector3, _particle: Particle, isLocal: boolean) => {
213
- const index = positionIndex;
214
- positionIndex += 1;
215
- const sideJitter = (sequence01(index, 1) - 0.5) * spread * 0.55;
216
- const backwardJitter = sequence01(index, 2) * spread * 0.75;
217
- const lift = sequence01(index, 3) * 0.012 * effectScale;
218
- const x = backwardDirection.x * (trailOffset + backwardJitter) + sideDirection.x * sideJitter;
219
- const z = backwardDirection.z * (trailOffset + backwardJitter) + sideDirection.z * sideJitter;
220
- if (isLocal) positionToUpdate.set(x, lift, z);
221
- else Vector3.TransformCoordinatesFromFloatsToRef(x, lift, z, worldMatrix, positionToUpdate);
222
- };
223
- system.startDirectionFunction = (worldMatrix: Matrix, directionToUpdate: Vector3, _particle: Particle, isLocal: boolean) => {
224
- const index = directionIndex;
225
- directionIndex += 1;
226
- const backward = backwardDirection.scale(0.28 + sequence01(index, 4) * 0.26);
227
- const sideways = sideDirection.scale((sequence01(index, 5) - 0.5) * 0.28);
228
- const x = backward.x + sideways.x;
229
- const y = Math.max(0, params.lift) * (0.4 + sequence01(index, 6) * 0.5);
230
- const z = backward.z + sideways.z;
231
- if (isLocal) directionToUpdate.set(x, y, z);
232
- else Vector3.TransformNormalFromFloatsToRef(x, y, z, worldMatrix, directionToUpdate);
233
- };
234
-
235
- return system;
236
- }
237
-
238
- function createStepEmitter(scene: Scene, system: ParticleSystem, params: RunFootstepDustParams): { start(): void; stop(): void; dispose(): void } {
239
- let observer: ReturnType<Scene['onBeforeRenderObservable']['add']> | null = null;
240
- let timeToNextStep = 0;
241
- const particlesPerStep = Math.max(0, Math.round(params.count));
242
- const emitMode = readEmitMode(params.emitMode);
243
- const interval = emitMode === 'loop'
244
- ? Math.max(0.01, readNumber(params.emitInterval, DEFAULT_PARAMS.emitInterval))
245
- : params.frequency > 0 ? 1 / params.frequency : Number.POSITIVE_INFINITY;
246
-
247
- return { start, stop, dispose: stop };
248
-
249
- function start(): void {
250
- if (observer) return;
251
- observer = scene.onBeforeRenderObservable.add(() => {
252
- if (particlesPerStep <= 0 || !Number.isFinite(interval)) return;
253
- timeToNextStep -= scene.getEngine().getDeltaTime() / 1000;
254
- while (timeToNextStep <= 0) {
255
- system.manualEmitCount += particlesPerStep;
256
- timeToNextStep += interval;
257
- }
258
- });
259
- }
260
-
261
- function stop(): void {
262
- if (observer) scene.onBeforeRenderObservable.remove(observer);
263
- observer = null;
264
- system.stop();
265
- }
266
- }
267
-
268
- function readEmitMode(value: string): 'loop' | 'timed' {
269
- return value === 'loop' || value === '持续发射' ? 'loop' : 'timed';
270
- }
271
-
272
- function getFootstepDustTexture(scene: Scene): DynamicTexture {
273
- const key = '__vfx_run_footstep_dust_texture';
274
- const metadata = scene.metadata && typeof scene.metadata === 'object' ? scene.metadata as Record<string, unknown> : {};
275
- scene.metadata = metadata;
276
- const existing = metadata[key] as DynamicTexture | undefined;
277
- if (existing && !isTextureDisposed(existing)) return existing;
278
- if (existing && isTextureDisposed(existing)) delete metadata[key];
279
-
280
- const texture = new DynamicTexture('run_footstep_dust_texture', { width: 128, height: 128 }, scene, true);
281
- const context = texture.getContext();
282
- context.clearRect(0, 0, 128, 128);
283
- for (const puff of [
284
- { x: 48, y: 69, radius: 34, alpha: 0.72 },
285
- { x: 70, y: 60, radius: 31, alpha: 0.64 },
286
- { x: 86, y: 76, radius: 27, alpha: 0.5 },
287
- ]) {
288
- const shadow = context.createRadialGradient(puff.x, puff.y + 2, puff.radius * 0.22, puff.x, puff.y + 2, puff.radius + 5);
289
- shadow.addColorStop(0, `rgba(86, 78, 64, ${puff.alpha * 0.22})`);
290
- shadow.addColorStop(1, 'rgba(86, 78, 64, 0)');
291
- context.fillStyle = shadow;
292
- context.beginPath();
293
- context.arc(puff.x, puff.y + 2, puff.radius + 5, 0, Math.PI * 2);
294
- context.fill();
295
- }
296
- for (const puff of [
297
- { x: 48, y: 69, radius: 34, alpha: 0.72 },
298
- { x: 70, y: 60, radius: 31, alpha: 0.64 },
299
- { x: 86, y: 76, radius: 27, alpha: 0.5 },
300
- ]) {
301
- const core = context.createRadialGradient(puff.x, puff.y, puff.radius * 0.12, puff.x, puff.y, puff.radius);
302
- core.addColorStop(0, `rgba(255, 255, 255, ${puff.alpha})`);
303
- core.addColorStop(0.58, `rgba(235, 229, 210, ${puff.alpha * 0.52})`);
304
- core.addColorStop(1, 'rgba(220, 212, 188, 0)');
305
- context.fillStyle = core;
306
- context.beginPath();
307
- context.arc(puff.x, puff.y, puff.radius, 0, Math.PI * 2);
308
- context.fill();
309
- }
310
- texture.hasAlpha = true;
311
- texture.update(false);
312
- metadata[key] = texture;
313
- texture.onDisposeObservable.addOnce(() => {
314
- if (metadata[key] === texture) delete metadata[key];
315
- });
316
- return texture;
317
- }
318
-
319
- function readSourceEmitter(): SourceEmitter {
320
- const source = sourceManifest as { emitters?: SourceEmitter[] };
321
- return source.emitters?.[0] ?? {};
322
- }
323
-
324
- function isTextureDisposed(texture: DynamicTexture): boolean {
325
- return Boolean((texture as { isDisposed?: () => boolean }).isDisposed?.());
326
- }
327
-
328
- function readColor(value: unknown, fallback: VfxColorValue): VfxColorValue {
329
- if (typeof value === 'string') {
330
- const match = /^#?([0-9a-f]{6})$/i.exec(value);
331
- if (match) {
332
- const hex = match[1];
333
- return {
334
- r: parseInt(hex.slice(0, 2), 16) / 255,
335
- g: parseInt(hex.slice(2, 4), 16) / 255,
336
- b: parseInt(hex.slice(4, 6), 16) / 255,
337
- };
338
- }
339
- }
340
- if (value && typeof value === 'object' && !Array.isArray(value)) {
341
- const color = value as Partial<VfxColorValue>;
342
- return {
343
- r: readNumber(color.r, fallback.r),
344
- g: readNumber(color.g, fallback.g),
345
- b: readNumber(color.b, fallback.b),
346
- };
347
- }
348
- return fallback;
349
- }
350
-
351
- function tintColor(color: VfxColorValue, brightness: number, alpha: number): Color4 {
352
- return new Color4(
353
- clamp01(color.r * brightness),
354
- clamp01(color.g * brightness),
355
- clamp01(color.b * brightness),
356
- clamp01(alpha),
357
- );
358
- }
359
-
360
- function readNumber(value: unknown, fallback: number): number {
361
- const next = Number(value);
362
- return Number.isFinite(next) ? next : fallback;
363
- }
364
-
365
- function clamp01(value: number): number {
366
- return Math.min(1, Math.max(0, value));
367
- }
368
-
369
- function fract(value: number): number {
370
- return value - Math.floor(value);
371
- }
372
-
373
- function sequence01(index: number, axis: number): number {
374
- if (index === 0) return 0.5;
375
- const strides = [0.618033988749895, 0.414213562373095, 0.732050807568877, 0.324717957244746, 0.754877666246693, 0.569840290998053];
376
- return fract(0.5 + index * strides[axis % strides.length] + axis * 0.173205080756888);
377
- }
@@ -1,120 +0,0 @@
1
- // babylon-laser 移植系列的共享工具(被拆分后的各 space 特效复用)。
2
- // 非 effect(无 index.ts,glob `*/index.ts` 不会当特效加载);各特效 index.ts 经 `../space-shared` 引用。
3
- import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem';
4
- import { Texture } from '@babylonjs/core/Materials/Textures/texture';
5
- import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
6
- import { Color4 } from '@babylonjs/core/Maths/math.color';
7
- import { Vector3 } from '@babylonjs/core/Maths/math.vector';
8
- import type { Mesh } from '@babylonjs/core/Meshes/mesh';
9
- import type { Scene } from '@babylonjs/core/scene';
10
- import type { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
11
- import type { Observer } from '@babylonjs/core/Misc/observable';
12
- import type { VfxColorValue, VfxParamDefinition } from '../types';
13
-
14
- // ── 通用「生命周期模式」(无限循环 / 自定义时长),对齐 lumber 系列的约定 ──────────
15
- export const LIFETIME_DEBUG_PARAMS: VfxParamDefinition[] = [
16
- { key: 'lifetimeMode', label: '生命周期模式', kind: 'enum', options: [
17
- { value: 'infinite', label: '无限' }, { value: 'duration', label: '自定义时间' },
18
- ] },
19
- { key: 'lifetime', label: '自定义时长', kind: 'lifetime', min: 0.1, max: 30, step: 0.01 },
20
- ];
21
- export const LIFETIME_DEFAULTS: { lifetimeMode: string; lifetime: number } = { lifetimeMode: 'infinite', lifetime: 2 };
22
- // mode='duration' 时 N 秒后调 dispose;返回定时器 id(0=未排程),由 dispose 里 clearTimeout。
23
- export function scheduleLifetime(params: { lifetimeMode?: string; lifetime?: number }, dispose: () => void): number {
24
- if (params.lifetimeMode === 'duration') return window.setTimeout(dispose, Math.max(0.05, params.lifetime ?? 2) * 1000);
25
- return 0;
26
- }
27
-
28
- // 把网格从场景里任何 GlowLayer 排除(ctx 不带 glow 引用,自查 scene.effectLayers)
29
- export function excludeFromGlow(scene: Scene, mesh: AbstractMesh): void {
30
- for (const layer of scene.effectLayers ?? []) {
31
- const add = (layer as unknown as { addExcludedMesh?: (m: AbstractMesh) => void }).addExcludedMesh;
32
- if (typeof add === 'function') add.call(layer, mesh);
33
- }
34
- }
35
-
36
- // 程序径向光斑(命中爆闪 / 激光两端光球用)
37
- export function makeFlareTexture(scene: Scene, name = 'vfx_flare', inner = '255,255,255', outer = '90,160,255'): DynamicTexture {
38
- const dt = new DynamicTexture(name, 128, scene, false);
39
- const ctx = dt.getContext();
40
- const g = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
41
- g.addColorStop(0, `rgba(${inner},1)`);
42
- g.addColorStop(0.35, `rgba(${outer},0.55)`);
43
- g.addColorStop(1, `rgba(${outer},0)`);
44
- ctx.clearRect(0, 0, 128, 128);
45
- ctx.fillStyle = g; ctx.fillRect(0, 0, 128, 128);
46
- dt.update(); dt.hasAlpha = true;
47
- return dt;
48
- }
49
-
50
- // 爆发型粒子工厂(emitRate=0 常驻,靠 manualEmitCount 单帧脉冲)
51
- export interface BurstPSOptions {
52
- cap: number; sheet?: [number, number, number];
53
- size: [number, number]; life: [number, number]; power: [number, number];
54
- gravity: Vector3; blend: number; c1: Color4; c2: Color4; cd: Color4;
55
- }
56
- export function makeBurstPS(name: string, scene: Scene, texUrl: string, opts: BurstPSOptions): ParticleSystem {
57
- const ps = new ParticleSystem(name, opts.cap, scene, null, !!opts.sheet);
58
- ps.particleTexture = new Texture(texUrl, scene);
59
- ps.particleTexture.hasAlpha = true;
60
- ps.emitter = new Vector3(0, 0, 0);
61
- ps.minEmitBox = new Vector3(0, 0, 0); ps.maxEmitBox = new Vector3(0, 0, 0);
62
- ps.direction1 = new Vector3(-1, -1, -1); ps.direction2 = new Vector3(1, 1, 1);
63
- ps.minInitialRotation = -Math.PI; ps.maxInitialRotation = Math.PI;
64
- ps.minSize = opts.size[0]; ps.maxSize = opts.size[1];
65
- ps.minLifeTime = opts.life[0]; ps.maxLifeTime = opts.life[1];
66
- ps.minEmitPower = opts.power[0]; ps.maxEmitPower = opts.power[1];
67
- ps.gravity = opts.gravity;
68
- ps.color1 = opts.c1; ps.color2 = opts.c2; ps.colorDead = opts.cd;
69
- ps.blendMode = opts.blend;
70
- ps.emitRate = 0;
71
- if (opts.sheet) {
72
- ps.spriteCellWidth = opts.sheet[0]; ps.spriteCellHeight = opts.sheet[1];
73
- ps.startSpriteCellID = 0; ps.endSpriteCellID = opts.sheet[2] - 1; ps.spriteCellChangeSpeed = 1;
74
- }
75
- ps.start();
76
- return ps;
77
- }
78
-
79
- // 抛物 flyer 池(碎石/金币):重力抛物 + 随机自转 + 末段缩小 + 寿命到回收。自维护 onBeforeRender。
80
- interface Flyer { mesh: Mesh; vel: Vector3; spin: Vector3; life: number; maxLife: number; gravity: number; base: number; }
81
- export class FlyerPool {
82
- private readonly flyers: Flyer[] = [];
83
- private readonly observer: Observer<Scene>;
84
- private disposed = false;
85
- constructor(private readonly scene: Scene) {
86
- this.observer = scene.onBeforeRenderObservable.add(() => this.update()) as Observer<Scene>;
87
- }
88
- spawn(mesh: Mesh, origin: Vector3, vel: Vector3, gravity: number, maxLife: number): void {
89
- mesh.position.copyFrom(origin);
90
- mesh.isPickable = false;
91
- this.flyers.push({
92
- mesh, vel, gravity, maxLife, life: 0, base: mesh.scaling.x || 1,
93
- spin: new Vector3((Math.random() - 0.5) * 8, (Math.random() - 0.5) * 8, (Math.random() - 0.5) * 8),
94
- });
95
- }
96
- get activeCount(): number { return this.flyers.length; }
97
- private update(): void {
98
- if (this.disposed) return;
99
- const dt = Math.min(this.scene.getEngine().getDeltaTime() / 1000, 0.05);
100
- for (let i = this.flyers.length - 1; i >= 0; i--) {
101
- const f = this.flyers[i]; f.life += dt;
102
- f.vel.y -= f.gravity * dt;
103
- f.mesh.position.addInPlace(f.vel.scale(dt));
104
- f.mesh.rotation.x += f.spin.x * dt; f.mesh.rotation.y += f.spin.y * dt; f.mesh.rotation.z += f.spin.z * dt;
105
- if (f.life > f.maxLife * 0.7) {
106
- const k = Math.max(0.001, 1 - (f.life - f.maxLife * 0.7) / (f.maxLife * 0.3));
107
- f.mesh.scaling.setAll(f.base * k);
108
- }
109
- if (f.life >= f.maxLife) { f.mesh.dispose(); this.flyers.splice(i, 1); }
110
- }
111
- }
112
- dispose(): void {
113
- if (this.disposed) return; this.disposed = true;
114
- this.scene.onBeforeRenderObservable.remove(this.observer);
115
- for (const f of this.flyers) f.mesh.dispose();
116
- this.flyers.length = 0;
117
- }
118
- }
119
-
120
- export function colorVec4(c: VfxColorValue, a = 1): Color4 { return new Color4(c.r, c.g, c.b, a); }