@fps-games/vfx 0.3.3 → 0.3.5

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 CHANGED
@@ -12,6 +12,26 @@
12
12
 
13
13
  <!-- release.mjs 在此行下方插入新版本区块 -->
14
14
 
15
+ ## v0.3.5 — 2026-07-01
16
+
17
+ ### 改动
18
+ - fix(weapon-trail): 拖尾按实际历史弧长映射,消除运动采样不足导致的截断
19
+ - feat(weapon-trail): refine layered trail controls
20
+ - feat(weapon-trail): 默认关闭刃头火花 (sparkRate 20→0)
21
+ - feat(weapon-trail): 软边加色外晕 ribbon 替代 GlowLayer 的柔光晕
22
+ - perf(weapon-trail): 去掉 per-instance GlowLayer,bloom 折进 additive 亮度 (#11 L-D)
23
+
24
+ ### 特效
25
+ - 无新增 / 删除(仍 37 个)。
26
+
27
+ ## v0.3.4 — 2026-06-30
28
+
29
+ ### 改动
30
+ - fix(tooling): use @fps-games package name
31
+
32
+ ### 特效
33
+ - 无新增 / 删除(仍 37 个)。
34
+
15
35
  ## v0.3.3 — 2026-06-30
16
36
 
17
37
  ### 改动
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": "git+ssh://git@github.com/forge-play-studio/fps-vfx-library.git#v0.3.0"
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
 
@@ -294,6 +294,14 @@ function buildDebugParams(manifest) {
294
294
  });
295
295
  continue;
296
296
  }
297
+ if (param.type === "boolean") {
298
+ result.push({
299
+ key: param.key,
300
+ label,
301
+ kind: "boolean"
302
+ });
303
+ continue;
304
+ }
297
305
  if (param.type === "enum" && Array.isArray(param.options)) {
298
306
  result.push({
299
307
  key: param.key,
@@ -311,6 +319,7 @@ function buildDebugParams(manifest) {
311
319
  function isVfxParamValue(value) {
312
320
  if (typeof value === "number") return Number.isFinite(value);
313
321
  if (typeof value === "string") return true;
322
+ if (typeof value === "boolean") return true;
314
323
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
315
324
  const color = value;
316
325
  return typeof color.r === "number" && Number.isFinite(color.r) && typeof color.g === "number" && Number.isFinite(color.g) && typeof color.b === "number" && Number.isFinite(color.b);
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.door_open_gold_flakes";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.lumber_vehicle_gear_spin";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.lumber_vehicle_upgrade_aura";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.lumber_vehicle_wood_chips";
4
4
  const name = {
@@ -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 {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.tree_chop_dust_burst";
4
4
  const name = {
@@ -2,7 +2,11 @@ import { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
2
2
  interface WeaponTrailParams extends VfxParamValues {
3
3
  lifetimeMode: string;
4
4
  lifetime: number;
5
+ playbackMode: string;
5
6
  orientation: string;
7
+ showGlow: boolean;
8
+ showOuter: boolean;
9
+ showCore: boolean;
6
10
  orbitRadius: number;
7
11
  orbitSpeed: number;
8
12
  height: number;
@@ -12,6 +16,7 @@ interface WeaponTrailParams extends VfxParamValues {
12
16
  coreWidth: number;
13
17
  tailFadeStrength: number;
14
18
  tailFadeExponent: number;
19
+ edgeSoftness: number;
15
20
  opacity: number;
16
21
  saturation: number;
17
22
  contrast: number;
@@ -19,7 +24,6 @@ interface WeaponTrailParams extends VfxParamValues {
19
24
  bloom: number;
20
25
  blendMode: string;
21
26
  flowSpeed: number;
22
- sparkRate: number;
23
27
  tint: VfxColorValue;
24
28
  }
25
29
  export declare const weaponTrailEffectPackage: VfxEffectPackage<WeaponTrailParams>;
@@ -4,26 +4,29 @@ import { VertexBuffer } from "@babylonjs/core/Buffers/buffer";
4
4
  import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
5
5
  import { Effect } from "@babylonjs/core/Materials/effect";
6
6
  import { Texture } from "@babylonjs/core/Materials/Textures/texture";
7
- import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
7
+ import { Color3 } from "@babylonjs/core/Maths/math.color";
8
8
  import { Vector3 } from "@babylonjs/core/Maths/math.vector";
9
9
  import { Constants } from "@babylonjs/core/Engines/constants";
10
- import { GlowLayer } from "@babylonjs/core/Layers/glowLayer";
11
- import { ParticleSystem } from "@babylonjs/core/Particles/particleSystem";
12
- import { L as LIFETIME_DEBUG_PARAMS, a as LIFETIME_DEFAULTS, b as makeFlareTexture, s as scheduleLifetime } from "../../chunks/space-shared-BXV1hc0k.js";
10
+ import { L as LIFETIME_DEBUG_PARAMS, a as LIFETIME_DEFAULTS, s as scheduleLifetime } from "../../chunks/space-shared-BXV1hc0k.js";
13
11
  const trailTextureUrl = "data:image/png;base64,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";
14
12
  const DEFAULT_PARAMS = {
15
13
  ...LIFETIME_DEFAULTS,
14
+ playbackMode: "follow",
16
15
  orientation: "horizontal",
16
+ showGlow: true,
17
+ showOuter: true,
18
+ showCore: true,
17
19
  orbitRadius: 0,
18
20
  orbitSpeed: 7,
19
21
  height: 0,
20
22
  // 默认 0:真实跟随挂点(orbit>0 是演示用的幻影圆周轨道)
21
- trailLength: 16,
23
+ trailLength: 32,
22
24
  worldLength: 4,
23
25
  width: 1.01,
24
26
  coreWidth: 0.4,
25
- tailFadeStrength: 0.58,
27
+ tailFadeStrength: 0,
26
28
  tailFadeExponent: 1.35,
29
+ edgeSoftness: 0.16,
27
30
  opacity: 1.35,
28
31
  saturation: 1.45,
29
32
  contrast: 1.32,
@@ -31,10 +34,12 @@ const DEFAULT_PARAMS = {
31
34
  bloom: 0.9,
32
35
  blendMode: "add",
33
36
  flowSpeed: 5,
34
- sparkRate: 20,
35
37
  tint: { r: 0.4, g: 0.85, b: 1 }
36
38
  // 青色能量,配新武器发光风
37
39
  };
40
+ const TEXTURE_U_MIN = 0;
41
+ const TEXTURE_U_MAX = 1;
42
+ const HISTORY_DISTANCE_MARGIN = 1.4;
38
43
  const TRAIL_VERTEX = `precision highp float;
39
44
  attribute vec3 position;
40
45
  attribute vec2 uv;
@@ -53,6 +58,7 @@ uniform float uContrast;
53
58
  uniform float uAlphaScale;
54
59
  uniform float uAlphaPower;
55
60
  uniform float uBright;
61
+ uniform float uEdgeSoftness;
56
62
  uniform float uTime;
57
63
  uniform float uFlowSpeed;
58
64
  void main(){
@@ -67,6 +73,8 @@ void main(){
67
73
  float band = smoothstep(0.0, 0.12, phase) * (1.0 - smoothstep(0.12, 0.4, phase));
68
74
  color *= 1.0 + band * 0.6 * step(0.0001, uFlowSpeed);
69
75
  float alpha = pow(clamp(base.a, 0.0, 1.0), uAlphaPower) * vColor.a * uAlphaScale;
76
+ float edge = min(vUv.y, 1.0 - vUv.y);
77
+ alpha *= mix(1.0, smoothstep(0.0, max(0.0001, uEdgeSoftness), edge), step(0.0001, uEdgeSoftness));
70
78
  gl_FragColor = vec4(color, clamp(alpha, 0.0, 1.0));
71
79
  }`;
72
80
  let shadersRegistered = false;
@@ -97,22 +105,26 @@ const weaponTrailEffectPackage = {
97
105
  defaultParams: DEFAULT_PARAMS,
98
106
  debugParams: [
99
107
  ...LIFETIME_DEBUG_PARAMS,
108
+ { key: "playbackMode", label: "播放方式", kind: "enum", options: [
109
+ { value: "follow", label: "跟随挂点" },
110
+ { value: "orbit", label: "环绕预览" }
111
+ ] },
100
112
  { key: "orientation", label: "拖尾朝向", kind: "enum", options: [
101
113
  { value: "horizontal", label: "水平面" },
102
114
  { value: "vertical", label: "竖直" }
103
115
  ] },
116
+ { key: "showGlow", label: "显示外晕层", kind: "boolean" },
117
+ { key: "showOuter", label: "显示主带层", kind: "boolean" },
118
+ { key: "showCore", label: "显示亮芯层", kind: "boolean" },
104
119
  { key: "orbitRadius", label: "环绕半径", kind: "scale", min: 0, max: 5, step: 0.05 },
105
120
  { key: "orbitSpeed", label: "环绕速度", kind: "number", min: 0, max: 20, step: 0.1 },
106
- { key: "height", label: "高度", kind: "number", min: -2, max: 3, step: 0.05 },
107
- { key: "trailLength", label: "拖尾段数", kind: "number", min: 4, max: 48, step: 1 },
121
+ { key: "trailLength", label: "拖尾段数", kind: "number", min: 4, max: 72, step: 1 },
108
122
  { key: "worldLength", label: "拖尾长度", kind: "scale", min: 0.5, max: 12, step: 0.1 },
109
123
  { key: "width", label: "拖尾宽度", kind: "scale", min: 0.1, max: 4, step: 0.01 },
110
124
  { key: "coreWidth", label: "亮芯宽度比", kind: "number", min: 0, max: 1, step: 0.01 },
111
125
  { key: "tailFadeStrength", label: "尾部淡出强度", kind: "number", min: 0, max: 1, step: 0.01 },
112
126
  { key: "tailFadeExponent", label: "尾部淡出曲线", kind: "number", min: 0.2, max: 4, step: 0.05 },
113
- { key: "opacity", label: "透明度", kind: "opacity", min: 0, max: 3, step: 0.01 },
114
- { key: "saturation", label: "饱和度", kind: "number", min: 0, max: 3, step: 0.01 },
115
- { key: "contrast", label: "对比度", kind: "number", min: 0, max: 3, step: 0.01 },
127
+ { key: "edgeSoftness", label: "横向软边", kind: "number", min: 0, max: 0.45, step: 0.01 },
116
128
  { key: "bright", label: "亮度(HDR)", kind: "number", min: 0.2, max: 4, step: 0.05 },
117
129
  { key: "bloom", label: "辉光强度", kind: "number", min: 0, max: 3, step: 0.05 },
118
130
  { key: "blendMode", label: "混合模式", kind: "enum", options: [
@@ -120,7 +132,6 @@ const weaponTrailEffectPackage = {
120
132
  { value: "combine", label: "正常(实心刃)" }
121
133
  ] },
122
134
  { key: "flowSpeed", label: "能量流动速度", kind: "number", min: 0, max: 4, step: 0.05 },
123
- { key: "sparkRate", label: "刃头火花/秒", kind: "number", min: 0, max: 80, step: 1 },
124
135
  { key: "tint", label: "颜色", kind: "color" }
125
136
  ],
126
137
  create({ scene, root, position, params, renderingGroupId }) {
@@ -135,7 +146,7 @@ const weaponTrailEffectPackage = {
135
146
  function makeMat(name, tint, bright) {
136
147
  const m = new ShaderMaterial(name, scene, { vertex: "vfxWeaponTrail", fragment: "vfxWeaponTrail" }, {
137
148
  attributes: ["position", "uv", "color"],
138
- uniforms: ["worldViewProjection", "uTint", "uSaturation", "uContrast", "uAlphaScale", "uAlphaPower", "uBright", "uTime", "uFlowSpeed"],
149
+ uniforms: ["worldViewProjection", "uTint", "uSaturation", "uContrast", "uAlphaScale", "uAlphaPower", "uBright", "uEdgeSoftness", "uTime", "uFlowSpeed"],
139
150
  samplers: ["mainTex"],
140
151
  needAlphaBlending: true
141
152
  });
@@ -146,6 +157,7 @@ const weaponTrailEffectPackage = {
146
157
  m.setFloat("uAlphaScale", params.opacity);
147
158
  m.setFloat("uAlphaPower", 0.82);
148
159
  m.setFloat("uBright", bright);
160
+ m.setFloat("uEdgeSoftness", params.edgeSoftness);
149
161
  m.setFloat("uTime", 0);
150
162
  m.setFloat("uFlowSpeed", params.flowSpeed);
151
163
  m.alphaMode = blend;
@@ -156,9 +168,14 @@ const weaponTrailEffectPackage = {
156
168
  }
157
169
  const tintC = new Color3(params.tint.r, params.tint.g, params.tint.b);
158
170
  const coreC = Color3.Lerp(tintC, new Color3(1, 1, 1), 0.75);
159
- const outerMat = makeMat("vfx_weaponTrail_outerMat", tintC, params.bright);
160
- const hasCore = params.coreWidth > 1e-3;
161
- const coreMat = hasCore ? makeMat("vfx_weaponTrail_coreMat", coreC, params.bright * 1.6) : null;
171
+ const bloomBoost = 1 + Math.max(0, params.bloom) * 0.25;
172
+ const hasOuter = params.showOuter !== false;
173
+ const hasCore = params.showCore !== false && params.coreWidth > 1e-3;
174
+ const hasGlow = params.showGlow !== false && params.bloom > 1e-3;
175
+ const outerMat = hasOuter ? makeMat("vfx_weaponTrail_outerMat", tintC, params.bright * bloomBoost) : null;
176
+ const coreMat = hasCore ? makeMat("vfx_weaponTrail_coreMat", coreC, params.bright * 1.6 * bloomBoost) : null;
177
+ const glowMat = hasGlow ? makeMat("vfx_weaponTrail_glowMat", tintC, params.bright) : null;
178
+ if (glowMat) glowMat.setFloat("uAlphaScale", params.opacity * 0.42 * Math.min(2.2, params.bloom));
162
179
  function makeMesh(name, mat, ai) {
163
180
  const m = new Mesh(name, scene);
164
181
  m.material = mat;
@@ -170,63 +187,89 @@ const weaponTrailEffectPackage = {
170
187
  m.alphaIndex = ai;
171
188
  return m;
172
189
  }
173
- const outer = makeMesh("vfx_weaponTrail_outer", outerMat, 10);
190
+ const glow = hasGlow ? makeMesh("vfx_weaponTrail_glow", glowMat, 9) : null;
191
+ const outer = hasOuter ? makeMesh("vfx_weaponTrail_outer", outerMat, 10) : null;
174
192
  const core = hasCore ? makeMesh("vfx_weaponTrail_core", coreMat, 11) : null;
175
- let glow = null;
176
- if (params.bloom > 0) {
177
- glow = new GlowLayer("vfx_weaponTrail_glow", scene, { blurKernelSize: 48 });
178
- glow.intensity = params.bloom;
179
- glow.addIncludedOnlyMesh(outer);
180
- glow.referenceMeshToUseItsOwnMaterial(outer);
181
- if (core) {
182
- glow.addIncludedOnlyMesh(core);
183
- glow.referenceMeshToUseItsOwnMaterial(core);
184
- }
185
- }
186
- let sparkPs = null;
187
- const sparkEmitter = new Vector3();
188
- if (params.sparkRate > 0) {
189
- const c255 = `${Math.round(params.tint.r * 255)},${Math.round(params.tint.g * 255)},${Math.round(params.tint.b * 255)}`;
190
- const flare = makeFlareTexture(scene, "vfx_weaponTrail_sparkTex", "255,255,255", c255);
191
- sparkPs = new ParticleSystem("vfx_weaponTrail_sparks", 160, scene);
192
- sparkPs.renderingGroupId = rgid;
193
- sparkPs.particleTexture = flare;
194
- sparkPs.emitter = sparkEmitter;
195
- sparkPs.minSize = 0.07;
196
- sparkPs.maxSize = 0.26;
197
- sparkPs.minLifeTime = 0.12;
198
- sparkPs.maxLifeTime = 0.4;
199
- sparkPs.emitRate = params.sparkRate;
200
- sparkPs.blendMode = ParticleSystem.BLENDMODE_ADD;
201
- sparkPs.minEmitPower = 0.6;
202
- sparkPs.maxEmitPower = 2.4;
203
- sparkPs.direction1 = new Vector3(-1, -0.6, -1);
204
- sparkPs.direction2 = new Vector3(1, 0.6, 1);
205
- sparkPs.gravity = new Vector3(0, -3, 0);
206
- sparkPs.color1 = new Color4(params.tint.r, params.tint.g, params.tint.b, 1);
207
- sparkPs.color2 = new Color4(1, 1, 1, 1);
208
- sparkPs.colorDead = new Color4(params.tint.r, params.tint.g, params.tint.b, 0);
209
- sparkPs.start();
210
- }
211
193
  const targetCount = Math.max(2, Math.round(params.trailLength));
212
194
  const raw = [];
213
195
  const points = [];
214
- for (let i = 0; i < 96; i++) raw.push(sample());
215
196
  for (let i = 0; i < targetCount; i++) points.push(sample());
197
+ const pointPathT = [];
216
198
  let rawCount = 0, angle = 0, time = 0;
217
- let outerInit = false, coreInit = false;
199
+ let outerInit = false, coreInit = false, glowInit = false;
218
200
  const cur = sample();
219
201
  const mountPos = new Vector3();
220
202
  const lastWidthAxis = new Vector3(0, 0, 1);
221
203
  const tmpDir = new Vector3();
204
+ const tailDir = new Vector3(1, 0, 0);
222
205
  const UP = new Vector3(0, 1, 0);
223
206
  const positions = [], uvs = [], colors = [], indices = [];
207
+ function resolvePlaybackMode() {
208
+ if (params.playbackMode === "follow" || params.playbackMode === "orbit") return params.playbackMode;
209
+ return params.orbitRadius > 0 ? "orbit" : "follow";
210
+ }
211
+ function sampleMinDistance() {
212
+ return Math.max(3e-3, Math.min(0.04, params.worldLength / Math.max(16, targetCount * 3)));
213
+ }
214
+ function ensureRawCapacity(length) {
215
+ while (raw.length < length) raw.push(sample());
216
+ }
217
+ function resolveTailDirection() {
218
+ if (rawCount >= 2) {
219
+ const tail = raw[rawCount - 1].position;
220
+ const newer = raw[rawCount - 2].position;
221
+ tailDir.set(tail.x - newer.x, tail.y - newer.y, tail.z - newer.z);
222
+ if (tailDir.lengthSquared() > 1e-8) {
223
+ tailDir.normalize();
224
+ return tailDir;
225
+ }
226
+ }
227
+ const axis = rawCount > 0 ? raw[rawCount - 1].widthAxis : lastWidthAxis;
228
+ Vector3.CrossToRef(UP, axis, tailDir);
229
+ if (tailDir.lengthSquared() > 1e-8) {
230
+ tailDir.normalize();
231
+ return tailDir;
232
+ }
233
+ tailDir.set(1, 0, 0);
234
+ return tailDir;
235
+ }
236
+ function extrapolateTailSample(targetDist, availableDist, out) {
237
+ const tail = raw[rawCount - 1];
238
+ copySample(tail, out);
239
+ const extra = Math.max(0, targetDist - availableDist);
240
+ const dir = resolveTailDirection();
241
+ out.position.x += dir.x * extra;
242
+ out.position.y += dir.y * extra;
243
+ out.position.z += dir.z * extra;
244
+ }
245
+ function trimRawHistory() {
246
+ const maxDistance = Math.max(1, params.worldLength * HISTORY_DISTANCE_MARGIN + params.width * 2);
247
+ let total = 0;
248
+ let keep = rawCount;
249
+ for (let i = 0; i < rawCount - 1; i++) {
250
+ total += Vector3.Distance(raw[i].position, raw[i + 1].position);
251
+ if (total >= maxDistance) {
252
+ keep = i + 2;
253
+ break;
254
+ }
255
+ }
256
+ rawCount = Math.min(rawCount, keep);
257
+ }
258
+ function insertRawSample(src) {
259
+ ensureRawCapacity(rawCount + 1);
260
+ for (let i = rawCount; i > 0; i--) copySample(raw[i - 1], raw[i]);
261
+ copySample(src, raw[0]);
262
+ rawCount += 1;
263
+ trimRawHistory();
264
+ }
224
265
  function sampleEmitter(out) {
266
+ const playbackMode = resolvePlaybackMode();
267
+ const orbitRadius = playbackMode === "orbit" ? params.orbitRadius : 0;
225
268
  const c = Math.cos(angle), s = Math.sin(angle);
226
269
  out.position.set(
227
- mountPos.x + c * params.orbitRadius,
270
+ mountPos.x + c * orbitRadius,
228
271
  mountPos.y + params.height,
229
- mountPos.z + s * params.orbitRadius
272
+ mountPos.z + s * orbitRadius
230
273
  );
231
274
  if (params.orientation === "vertical") {
232
275
  out.widthAxis.set(0, 1, 0);
@@ -245,17 +288,29 @@ const weaponTrailEffectPackage = {
245
288
  }
246
289
  out.widthAxis.copyFrom(lastWidthAxis);
247
290
  }
291
+ function historyArcLength() {
292
+ let total = 0;
293
+ for (let i = 0; i < rawCount - 1; i++) total += Vector3.Distance(raw[i].position, raw[i + 1].position);
294
+ return total;
295
+ }
248
296
  function resample() {
249
297
  if (rawCount <= 0) return;
250
- const target = params.worldLength;
298
+ const historyLen = historyArcLength();
299
+ const target = Math.max(1e-3, Math.min(params.worldLength, historyLen));
251
300
  let seg = 0, distBefore = 0;
252
301
  for (let i = 0; i < targetCount; i++) {
253
302
  const t = points[i];
254
- if (i === 0 || rawCount === 1) {
303
+ const normalizedDist = i / (targetCount - 1);
304
+ const targetDist = target * normalizedDist;
305
+ pointPathT[i] = normalizedDist;
306
+ if (i === 0) {
255
307
  copySample(raw[0], t);
256
308
  continue;
257
309
  }
258
- const targetDist = target * (i / (targetCount - 1));
310
+ if (rawCount === 1) {
311
+ extrapolateTailSample(targetDist, 0, t);
312
+ continue;
313
+ }
259
314
  let found = false;
260
315
  while (seg < rawCount - 1) {
261
316
  const l = raw[seg], r = raw[seg + 1];
@@ -272,7 +327,7 @@ const weaponTrailEffectPackage = {
272
327
  distBefore += segLen;
273
328
  seg++;
274
329
  }
275
- if (!found) copySample(raw[rawCount - 1], t);
330
+ if (!found) extrapolateTailSample(targetDist, distBefore, t);
276
331
  }
277
332
  }
278
333
  function buildRibbon(mesh, halfWidth, wasInit) {
@@ -283,8 +338,10 @@ const weaponTrailEffectPackage = {
283
338
  for (let i = 0; i < targetCount; i++) {
284
339
  const s = points[i];
285
340
  const t = i / (targetCount - 1);
286
- const u = t * 0.97 + 0.015;
287
- const alpha = Math.max(0, 1 - Math.pow(t, params.tailFadeExponent) * params.tailFadeStrength);
341
+ const pathT = pointPathT[i] ?? t;
342
+ const u = TEXTURE_U_MIN + (TEXTURE_U_MAX - TEXTURE_U_MIN) * pathT;
343
+ const tailAlpha = Math.max(0, 1 - Math.pow(pathT, params.tailFadeExponent) * params.tailFadeStrength);
344
+ const alpha = tailAlpha;
288
345
  const ox = s.widthAxis.x * halfWidth, oy = s.widthAxis.y * halfWidth, oz = s.widthAxis.z * halfWidth;
289
346
  positions.push(s.position.x + ox, s.position.y + oy, s.position.z + oz, s.position.x - ox, s.position.y - oy, s.position.z - oz);
290
347
  uvs.push(u, 0, u, 1);
@@ -313,22 +370,19 @@ const weaponTrailEffectPackage = {
313
370
  const observer = scene.onBeforeRenderObservable.add(() => {
314
371
  const dt = Math.min(scene.getEngine().getDeltaTime() / 1e3, 0.05);
315
372
  time += dt;
316
- angle += params.orbitSpeed * dt;
373
+ if (resolvePlaybackMode() === "orbit") angle += params.orbitSpeed * dt;
317
374
  root.computeWorldMatrix(true);
318
375
  mountPos.copyFrom(root.getAbsolutePosition());
319
376
  sampleEmitter(cur);
320
- if (rawCount === 0 || Vector3.Distance(cur.position, raw[0].position) > 5e-4) {
321
- for (let i = Math.min(rawCount, raw.length - 1); i > 0; i--) copySample(raw[i - 1], raw[i]);
322
- copySample(cur, raw[0]);
323
- rawCount = Math.min(rawCount + 1, raw.length);
324
- }
377
+ if (rawCount === 0 || Vector3.Distance(cur.position, raw[0].position) > sampleMinDistance()) insertRawSample(cur);
325
378
  resample();
326
379
  const halfW = params.width * 0.5;
327
- outerInit = buildRibbon(outer, halfW, outerInit);
380
+ if (glow) glowInit = buildRibbon(glow, halfW * 2.2, glowInit);
381
+ if (outer) outerInit = buildRibbon(outer, halfW, outerInit);
328
382
  if (core) coreInit = buildRibbon(core, halfW * params.coreWidth, coreInit);
329
- outerMat.setFloat("uTime", time);
383
+ outerMat?.setFloat("uTime", time);
330
384
  coreMat?.setFloat("uTime", time);
331
- if (sparkPs) sparkEmitter.copyFrom(points[0].position);
385
+ glowMat?.setFloat("uTime", time);
332
386
  });
333
387
  let disposed = false;
334
388
  let ltTimer = 0;
@@ -337,11 +391,10 @@ const weaponTrailEffectPackage = {
337
391
  disposed = true;
338
392
  if (ltTimer) window.clearTimeout(ltTimer);
339
393
  scene.onBeforeRenderObservable.remove(observer);
340
- sparkPs?.stop();
341
- sparkPs?.dispose();
342
394
  glow?.dispose();
343
- outer.dispose();
344
- outerMat.dispose();
395
+ glowMat?.dispose();
396
+ outer?.dispose();
397
+ outerMat?.dispose();
345
398
  core?.dispose();
346
399
  coreMat?.dispose();
347
400
  tex.dispose();
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.wood_crusher_sawdust";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.wood_cutter_sawdust";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.material.wood_peel_material_test";
4
4
  const name = {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { V, c, a } from "./chunks/rendering-CV3nhygX.js";
2
2
  import { Vector3 } from "@babylonjs/core/Maths/math.vector";
3
3
  import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
4
- import { c as c2, c as c3 } from "./chunks/manifestRuntimePackage-CXQFSTo2.js";
4
+ import { c as c2, c as c3 } from "./chunks/manifestRuntimePackage-Vgh-cH5X.js";
5
5
  import { c as c4, f, a as a2, s } from "./chunks/legacy-particle-runtime-CbJYVxdd.js";
6
6
  const VFX_GENERATION_PARAM_KEYS = {
7
7
  offsetX: "__generation.offsetX",
@@ -1,5 +1,5 @@
1
1
  import { VfxEffectPackage } from './types';
2
- type LegacyTunableParamType = 'number' | 'color' | 'enum' | string;
2
+ type LegacyTunableParamType = 'number' | 'boolean' | 'color' | 'enum' | string;
3
3
  interface LegacyTunableTarget {
4
4
  emitter: string;
5
5
  property: string;
package/dist/types.d.ts CHANGED
@@ -22,14 +22,14 @@ export interface VfxResolvedInputs {
22
22
  target?: AbstractMesh;
23
23
  scalars?: Record<string, () => number>;
24
24
  }
25
- export type VfxParamValue = number | string | VfxColorValue;
25
+ export type VfxParamValue = number | string | boolean | VfxColorValue;
26
26
  export type VfxParamValues = Record<string, VfxParamValue>;
27
27
  export interface VfxColorValue {
28
28
  r: number;
29
29
  g: number;
30
30
  b: number;
31
31
  }
32
- export type VfxParamKind = 'number' | 'color' | 'enum' | 'opacity' | 'lifetime' | 'scale';
32
+ export type VfxParamKind = 'number' | 'boolean' | 'color' | 'enum' | 'opacity' | 'lifetime' | 'scale';
33
33
  export interface VfxParamDefinitionBase {
34
34
  key: string;
35
35
  label: string;
@@ -45,6 +45,9 @@ export interface VfxNumberParamDefinition extends VfxParamDefinitionBase {
45
45
  export interface VfxColorParamDefinition extends VfxParamDefinitionBase {
46
46
  kind: 'color';
47
47
  }
48
+ export interface VfxBooleanParamDefinition extends VfxParamDefinitionBase {
49
+ kind: 'boolean';
50
+ }
48
51
  export interface VfxEnumParamOption {
49
52
  value: string;
50
53
  label: string;
@@ -53,7 +56,7 @@ export interface VfxEnumParamDefinition extends VfxParamDefinitionBase {
53
56
  kind: 'enum';
54
57
  options: VfxEnumParamOption[];
55
58
  }
56
- export type VfxParamDefinition = VfxNumberParamDefinition | VfxColorParamDefinition | VfxEnumParamDefinition;
59
+ export type VfxParamDefinition = VfxNumberParamDefinition | VfxColorParamDefinition | VfxBooleanParamDefinition | VfxEnumParamDefinition;
57
60
  export interface VfxEffectPackageContext<PParams extends VfxParamValues = VfxParamValues> {
58
61
  scene: Scene;
59
62
  root: TransformNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fps-games/vfx",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
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": {
@@ -45,7 +45,7 @@
45
45
  "demo:treasure-attention": "vite demo/treasure-attention --host 127.0.0.1",
46
46
  "release": "node scripts/release.mjs"
47
47
  },
48
- "//": "分发=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。",
49
49
  "devDependencies": {
50
50
  "@babylonjs/core": "^8.56.2",
51
51
  "@babylonjs/loaders": "^8.56.2",
@@ -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,6 +16,7 @@ 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'; } })();
20
21
  const LIB_INDEX = (() => {
21
22
  try { return JSON.parse(readFileSync(join(LIB_ROOT, 'index.json'), 'utf8')); } catch { return null; }
@@ -45,11 +46,11 @@ function hasLibEffect(id) {
45
46
  return libEffectIds().includes(id) || existsSync(join(LIB_EFFECTS, id, 'vfx-params.json'));
46
47
  }
47
48
  function stub(id) {
48
- return `// @fps/vfx re-export 桩(代码/资源在包内;本地仅 vfx-params.json + 此桩)。\nexport * from '@fps/vfx/effects/${id}';\n`;
49
+ return `// ${PACKAGE_NAME} re-export 桩(代码/资源在包内;本地仅 vfx-params.json + 此桩)。\nexport * from '${PACKAGE_NAME}/effects/${id}';\n`;
49
50
  }
50
51
  function effectsDestDir() { return join(projectRoot, 'src', 'assets', 'vfx', 'effects'); }
51
52
  function provenance(id, version) {
52
- return JSON.stringify({ id, source: '@fps/vfx', version }, null, 2) + '\n';
53
+ return JSON.stringify({ id, source: PACKAGE_NAME, version }, null, 2) + '\n';
53
54
  }
54
55
  function normVer(v) { return String(v ?? '').replace(/^v/, ''); }
55
56
  function semverCmp(a, b) {
@@ -58,17 +59,17 @@ function semverCmp(a, b) {
58
59
  return 0;
59
60
  }
60
61
 
61
- // 读项目 package.json 里的 @fps/vfx 依赖(原始文本 + 解析值)。
62
+ // 读项目 package.json 里的 VFX 依赖(原始文本 + 解析值)。
62
63
  function readProjectVfxDep() {
63
64
  const p = join(projectRoot, 'package.json');
64
65
  if (!existsSync(p)) return null;
65
66
  const raw = readFileSync(p, 'utf8');
66
67
  const json = JSON.parse(raw);
67
68
  for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
68
- const val = json[field]?.['@fps/vfx'];
69
- if (val) return { path: p, raw, json, field, value: val };
69
+ const deps = json[field] ?? {};
70
+ if (deps[PACKAGE_NAME]) return { path: p, raw, json, field, packageName: PACKAGE_NAME, value: deps[PACKAGE_NAME] };
70
71
  }
71
- return { path: p, raw, json, field: null, value: null };
72
+ return { path: p, raw, json, field: null, packageName: null, value: null };
72
73
  }
73
74
  function pinFromValue(value) { const m = String(value ?? '').match(/#v?(\d+\.\d+\.\d+)/); return m ? m[1] : null; }
74
75
  function repoUrlFromValue(value) { return String(value ?? '').replace(/#.*$/, ''); }
@@ -150,7 +151,7 @@ function outdated() {
150
151
  const latestSource = remote ? '远端最新 tag' : `本库版本(远端不可达,回退)`;
151
152
 
152
153
  console.log(`项目:${projectRoot}`);
153
- console.log(`@fps/vfx 钉版本:${pin ? `v${pin}` : '(未钉 / 非 git-tag)'} → 最新:v${latest}(${latestSource})`);
154
+ console.log(`${dep.packageName ?? PACKAGE_NAME} 钉版本:${pin ? `v${pin}` : '(未钉 / 非 git-tag)'} → 最新:v${latest}(${latestSource})`);
154
155
  const depBehind = pin && semverCmp(pin, latest) < 0;
155
156
  console.log(depBehind ? `⬆ 依赖落后:可升级到 v${latest}(\`vfx update --to v${latest}\` + pnpm install)` : `✓ 依赖已是最新(或无法判定)。`);
156
157
 
@@ -162,7 +163,7 @@ function outdated() {
162
163
  const tag = v == null ? '无 provenance(早期批量安装)' : (semverCmp(v, latest) < 0 ? `v${v} ⬆ 落后` : `v${v} ✓`);
163
164
  console.log(` - ${e.id} [${tag}]`);
164
165
  }
165
- console.log(`\n注:模型C 下特效代码/资源在 @fps/vfx 包内,真实版本由上面的依赖钉版本决定;`);
166
+ console.log(`\n注:模型C 下特效代码/资源在 ${PACKAGE_NAME} 包内,真实版本由上面的依赖钉版本决定;`);
166
167
  console.log(` .vfx-source.json 仅作 add 时的来源记录,落后不影响运行(随依赖升级即同步)。`);
167
168
  } else {
168
169
  console.log(`\n(项目 ${effectsDestDir()} 下暂无已装特效)`);
@@ -178,7 +179,7 @@ function update(onlyId) {
178
179
  const toFlag = flagVal('--to');
179
180
  const target = normVer(toFlag ?? latestRemoteTag(repoUrl) ?? LIB_VERSION);
180
181
 
181
- // 1) 改 package.json 的 @fps/vfx git-tag ref(保留原 url 与格式,仅换 #ref)。
182
+ // 1) 改 package.json 的 VFX git-tag ref(保留原 url 与格式,仅换 #ref)。
182
183
  const isGitDep = /(^git\+|:\/\/|github\.com|\.git)/.test(dep.value ?? '');
183
184
  if (dep.field && dep.value && (/#/.test(dep.value) || isGitDep)) {
184
185
  let newValue;
@@ -186,21 +187,22 @@ function update(onlyId) {
186
187
  else newValue = `${dep.value}#v${target}`;
187
188
  if (newValue !== dep.value) {
188
189
  // 原文本就地替换,避免重排整份 package.json;用替换函数避免 newValue 里的 $ 被当替换记号。
189
- const depValRe = new RegExp(`("@fps/vfx"\\s*:\\s*")${dep.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(")`);
190
+ const depName = dep.packageName ?? PACKAGE_NAME;
191
+ const depValRe = new RegExp(`("${depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:\\s*")${dep.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(")`);
190
192
  const newRaw = dep.raw.replace(depValRe, (_m, p1, p2) => `${p1}${newValue}${p2}`);
191
193
  if (newRaw !== dep.raw) {
192
194
  writeFileSync(dep.path, newRaw);
193
- console.log(`✓ package.json:@fps/vfx ${oldPin ? `#v${oldPin}` : dep.value} → #v${target}`);
195
+ console.log(`✓ package.json:${depName} ${oldPin ? `#v${oldPin}` : dep.value} → ${PACKAGE_NAME}#v${target}`);
194
196
  } else {
195
- console.warn(`! 未能就地替换 @fps/vfx 依赖文本,请手动把 ref 改成 #v${target}。`);
197
+ console.warn(`! 未能就地替换 ${depName} 依赖文本,请手动把 ref 改成 ${PACKAGE_NAME}#v${target}。`);
196
198
  }
197
199
  } else {
198
- console.log(`= package.json:@fps/vfx 已是 #v${target}。`);
200
+ console.log(`= package.json:${dep.packageName ?? PACKAGE_NAME} 已是 #v${target}。`);
199
201
  }
200
202
  } else if (dep.field && dep.value) {
201
- console.warn(`! @fps/vfx 依赖 "${dep.value}" 不是 git-tag 形式,跳过 ref 升级(仅重同步桩)。如需固定版本,请手动改成 git+ssh://…#v${target}。`);
203
+ console.warn(`! ${dep.packageName ?? PACKAGE_NAME} 依赖 "${dep.value}" 不是 git-tag 形式,跳过 ref 升级(仅重同步桩)。如需固定版本,请手动改成 git+ssh://…#v${target}。`);
202
204
  } else {
203
- console.warn(`! 项目 package.json 未声明 @fps/vfx 依赖,跳过 ref 升级(仅同步桩)。`);
205
+ console.warn(`! 项目 package.json 未声明 ${PACKAGE_NAME} 依赖,跳过 ref 升级(仅同步桩)。`);
204
206
  }
205
207
 
206
208
  // 2) 重同步桩 + bump .vfx-source.json(永不动 vfx-params.json)。
@@ -219,12 +221,12 @@ function update(onlyId) {
219
221
  const range = changelogRange(oldPin, target);
220
222
  if (range) { console.log(`\n── CHANGELOG ${oldPin ? `v${oldPin} → ` : ''}v${target} ──\n${range}\n`); }
221
223
 
222
- console.log(`\n下一步:\`pnpm install\`(拉取 @fps/vfx#v${target} 新代码/资源)。`);
224
+ console.log(`\n下一步:\`pnpm install\`(拉取 ${PACKAGE_NAME}#v${target} 新代码/资源)。`);
223
225
  }
224
226
 
225
227
  // ── dispatch ──────────────────────────────────────────────────────────────────
226
228
  if (cmd === 'list') {
227
- console.log(`@fps/vfx 可用特效(v${LIB_VERSION}):`);
229
+ console.log(`${PACKAGE_NAME} 可用特效(v${LIB_VERSION}):`);
228
230
  for (const id of libEffectIds().sort()) console.log(` - ${id}`);
229
231
  } else if (cmd === 'add') {
230
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
  }