@fps-games/vfx 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +5 -5
  3. package/dist/effects/treasure-attention/index.d.ts +1 -1
  4. package/package.json +6 -5
  5. package/tooling/vfx-cli.mjs +31 -22
  6. package/tooling/vfx-package-resolution.mjs +8 -8
  7. package/docs/AUTHORING_EFFECTS.md +0 -297
  8. package/docs/INTEGRATION.md +0 -194
  9. package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
  10. package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
  11. package/docs/VFX_LIBRARY_PLAN.md +0 -546
  12. package/docs/hit-flash-red-method.md +0 -155
  13. package/docs/treasure_attention_fx_babylonjs_guide.md +0 -901
  14. package/docs/vfx-ab/RESULTS.md +0 -35
  15. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
  16. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
  17. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
  18. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
  19. package/docs/vfx-integration_chatgpt.md +0 -462
  20. package/docs/vfx-placement-design.md +0 -164
  21. package/docs/weapon-trail-visualization.html +0 -241
  22. package/packages/EffectPackageService.ts +0 -346
  23. package/packages/SceneVfxService.ts +0 -96
  24. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
  25. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
  26. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
  27. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
  28. package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
  29. package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
  30. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
  31. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
  32. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
  33. package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
  34. package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
  35. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
  36. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
  37. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
  38. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
  39. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
  40. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
  41. package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
  42. package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
  43. package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
  44. package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
  45. package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
  46. package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
  47. package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
  48. package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
  49. package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
  50. package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
  51. package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  52. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
  53. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
  54. package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
  55. package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
  56. package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
  57. package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
  58. package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
  59. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
  60. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
  61. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
  62. package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
  63. package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
  64. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
  65. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  66. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
  67. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
  68. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
  69. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
  70. package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
  71. package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
  72. package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
  73. package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
  74. package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
  75. package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
  76. package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
  77. package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
  78. package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
  79. package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
  80. package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
  81. package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
  82. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
  83. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
  84. package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
  85. package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
  86. package/packages/effects/building-upgrade/effect.json +0 -6
  87. package/packages/effects/building-upgrade/index.ts +0 -387
  88. package/packages/effects/character-entrance/effect.json +0 -6
  89. package/packages/effects/character-entrance/index.ts +0 -308
  90. package/packages/effects/coin-burst/index.ts +0 -48
  91. package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
  92. package/packages/effects/comet-trail-flame/index.ts +0 -107
  93. package/packages/effects/conveyor-upgrade/effect.json +0 -6
  94. package/packages/effects/conveyor-upgrade/index.ts +0 -401
  95. package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
  96. package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
  97. package/packages/effects/debris-smoke/index.ts +0 -102
  98. package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
  99. package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
  100. package/packages/effects/door_open_gold_flakes/index.ts +0 -11
  101. package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
  102. package/packages/effects/electric-arc/index.ts +0 -63
  103. package/packages/effects/energy-shield/effect.json +0 -7
  104. package/packages/effects/energy-shield/index.ts +0 -432
  105. package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
  106. package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
  107. package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
  108. package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
  109. package/packages/effects/explosion/index.ts +0 -115
  110. package/packages/effects/ground-tile-upgrade/effect.json +0 -6
  111. package/packages/effects/ground-tile-upgrade/index.ts +0 -410
  112. package/packages/effects/hit-flash/index.ts +0 -77
  113. package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
  114. package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
  115. package/packages/effects/laser-beam/index.ts +0 -6
  116. package/packages/effects/laser-beam-factory.ts +0 -146
  117. package/packages/effects/lumber-cart-suction/effect.json +0 -6
  118. package/packages/effects/lumber-cart-suction/index.ts +0 -399
  119. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
  120. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
  121. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
  122. package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
  123. package/packages/effects/lumber-debris-scatter/effect.json +0 -228
  124. package/packages/effects/lumber-debris-scatter/index.ts +0 -780
  125. package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
  126. package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
  127. package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
  128. package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
  129. package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -13
  130. package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
  131. package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
  132. package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
  133. package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
  134. package/packages/effects/monster-hit/index.ts +0 -129
  135. package/packages/effects/player-upgrade/effect.json +0 -6
  136. package/packages/effects/player-upgrade/index.ts +0 -640
  137. package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
  138. package/packages/effects/pulse-laser-volley/index.ts +0 -215
  139. package/packages/effects/resource-collect-sparkles/effect.json +0 -6
  140. package/packages/effects/resource-collect-sparkles/index.ts +0 -254
  141. package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
  142. package/packages/effects/run-footstep-dust/effect.json +0 -183
  143. package/packages/effects/run-footstep-dust/index.ts +0 -377
  144. package/packages/effects/space-shared.ts +0 -120
  145. package/packages/effects/thruster-flame/index.ts +0 -306
  146. package/packages/effects/treasure-attention/effect.json +0 -7
  147. package/packages/effects/treasure-attention/index.ts +0 -630
  148. package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
  149. package/packages/effects/tree-chop-dust/effect.json +0 -8
  150. package/packages/effects/tree-chop-dust/index.ts +0 -202
  151. package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
  152. package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
  153. package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
  154. package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
  155. package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
  156. package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
  157. package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
  158. package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
  159. package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
  160. package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
  161. package/packages/effects/ufo-thread-flame/index.ts +0 -153
  162. package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
  163. package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
  164. package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
  165. package/packages/effects/vehicle-upgrade/effect.json +0 -6
  166. package/packages/effects/vehicle-upgrade/index.ts +0 -404
  167. package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
  168. package/packages/effects/weapon-trail/index.ts +0 -317
  169. package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  170. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  171. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  172. package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
  173. package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
  174. package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  175. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  176. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  177. package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
  178. package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
  179. package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
  180. package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  181. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  182. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  183. package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
  184. package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
  185. package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  186. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  187. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  188. package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
  189. package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
  190. package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
  191. package/packages/effects/wood_peel_material_test/index.ts +0 -11
  192. package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
  193. package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
  194. package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
  195. package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
  196. package/packages/env.particle.forest_edge_fog/effect.json +0 -854
  197. package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
  198. package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
  199. package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
  200. package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
  201. package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
  202. package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
  203. package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
  204. package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
  205. package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
  206. package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
  207. package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
  208. package/packages/generation.ts +0 -14
  209. package/packages/index.ts +0 -14
  210. package/packages/legacy-particle-runtime.ts +0 -113
  211. package/packages/manifestRuntimePackage.ts +0 -519
  212. package/packages/registry.ts +0 -85
  213. package/packages/rendering.ts +0 -13
  214. package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
  215. package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
  216. package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
  217. package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
  218. package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
  219. package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
  220. package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
  221. package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
  222. package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
  223. package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
  224. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
  225. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
  226. package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
  227. package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
  228. package/packages/types.ts +0 -195
  229. package/tooling/check-dist-runtime.mjs +0 -166
  230. package/tooling/generate-index.mjs +0 -55
  231. package/tooling/test-guardrails.mjs +0 -157
  232. package/tooling/validate-boundary.mjs +0 -92
  233. package/tooling/validate-effects.mjs +0 -304
  234. package/tooling/validate.mjs +0 -53
@@ -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); }