@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
@@ -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); }