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