@fps-games/vfx 0.3.7 → 0.3.8

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,14 @@
12
12
 
13
13
  <!-- release.mjs 在此行下方插入新版本区块 -->
14
14
 
15
+ ## v0.3.8 — 2026-07-02
16
+
17
+ ### 改动
18
+ - fix(building-upgrade): treat offset scale as footprint scale
19
+
20
+ ### 特效
21
+ - 无新增 / 删除(仍 37 个)。
22
+
15
23
  ## v0.3.7 — 2026-07-02
16
24
 
17
25
  ### 改动
@@ -38,6 +38,7 @@ const buildingUpgradeEffectPackage = {
38
38
  manifest,
39
39
  defaultParams: DEFAULT_PARAMS,
40
40
  debugAnchor: "playerRight",
41
+ applyRootScale: false,
41
42
  debugParams: [
42
43
  { key: "lifetime", label: "生命周期", kind: "lifetime", min: 0.2, max: 6, step: 0.01 },
43
44
  { key: "color", label: "金光颜色", kind: "color" },
@@ -56,13 +57,13 @@ const buildingUpgradeEffectPackage = {
56
57
  ],
57
58
  create({ scene, root, position, params, scale }) {
58
59
  root.position.copyFrom(position);
59
- const visualScale = normalizeBuildingUpgradeScale(scale);
60
- const centerStrands = createStrandSystem(scene, root, params, visualScale, "center");
61
- const outlineStrands = createStrandSystem(scene, root, params, visualScale, "outline");
62
- const glow = createOutlineGlow(scene, root, params, visualScale);
60
+ const footprintScale = normalizeBuildingUpgradeScale(scale);
61
+ const centerStrands = createStrandSystem(scene, root, params, footprintScale, "center");
62
+ const outlineStrands = createStrandSystem(scene, root, params, footprintScale, "outline");
63
+ const glow = createOutlineGlow(scene, root, params, footprintScale);
63
64
  const disposeTimer = window.setTimeout(
64
65
  () => disposeAll(),
65
- Math.max(0.05, params.lifetime + getMaxStrandHeight(visualScale) / Math.max(0.1, params.riseSpeed) * 0.18 + 0.45) * 1e3
66
+ Math.max(0.05, params.lifetime + getMaxStrandHeight() / Math.max(0.1, params.riseSpeed) * 0.18 + 0.45) * 1e3
66
67
  );
67
68
  let disposed = false;
68
69
  centerStrands.start();
@@ -85,12 +86,12 @@ const buildingUpgradeEffectPackage = {
85
86
  }
86
87
  }
87
88
  };
88
- function createStrandSystem(scene, root, params, visualScale, mode) {
89
+ function createStrandSystem(scene, root, params, footprintScale, mode) {
89
90
  const count = Math.max(0, Math.round(mode === "center" ? params.centerCount : params.outlineCount));
90
91
  const system = new ParticleSystem(`building-upgrade.${mode}-strands`, Math.max(1, count), scene);
91
92
  const color = params.color;
92
- const height = mode === "center" ? getCenterHeight(visualScale) : getOutlineHeight(visualScale);
93
- const width = params.strandWidth * visualScale.horizontal * (mode === "center" ? 1.35 : 0.92);
93
+ const height = mode === "center" ? getCenterHeight() : getOutlineHeight();
94
+ const width = params.strandWidth * (mode === "center" ? 1.35 : 0.92);
94
95
  configureVfxParticleSystem(system);
95
96
  system.particleTexture = getGoldStrandTexture(scene);
96
97
  system.emitter = root;
@@ -126,7 +127,7 @@ function createStrandSystem(scene, root, params, visualScale, mode) {
126
127
  system.startPositionFunction = (worldMatrix, positionToUpdate, particle, isLocal) => {
127
128
  const index = particle.id + positionIndex;
128
129
  positionIndex += 1;
129
- const local = mode === "center" ? sampleCenterPosition(index, params) : sampleOutlinePosition(index, params);
130
+ const local = mode === "center" ? sampleCenterPosition(index, params, footprintScale) : sampleOutlinePosition(index, params, footprintScale);
130
131
  if (isLocal) positionToUpdate.copyFrom(local);
131
132
  else Vector3.TransformCoordinatesToRef(local, worldMatrix, positionToUpdate);
132
133
  };
@@ -144,11 +145,11 @@ function createStrandSystem(scene, root, params, visualScale, mode) {
144
145
  };
145
146
  return system;
146
147
  }
147
- function createOutlineGlow(scene, root, params, visualScale) {
148
+ function createOutlineGlow(scene, root, params, footprintScale) {
148
149
  const count = Math.max(1, Math.round(params.outlineCount * 0.82));
149
150
  const system = new ParticleSystem("building-upgrade.outline-glow-rays", count, scene);
150
- const height = getOutlineHeight(visualScale) * 1.06;
151
- const glowWidth = Math.max(0.04, BASE_GLOW_THICKNESS * visualScale.horizontal);
151
+ const height = getOutlineHeight() * 1.06;
152
+ const glowWidth = Math.max(0.04, BASE_GLOW_THICKNESS);
152
153
  const alpha = params.glowOpacity * params.opacity;
153
154
  configureVfxParticleSystem(system);
154
155
  system.particleTexture = getOutlineGlowTexture(scene);
@@ -185,7 +186,7 @@ function createOutlineGlow(scene, root, params, visualScale) {
185
186
  system.startPositionFunction = (worldMatrix, positionToUpdate, particle, isLocal) => {
186
187
  const index = particle.id + positionIndex + 31;
187
188
  positionIndex += 1;
188
- const local = sampleOutlinePosition(index, params);
189
+ const local = sampleOutlinePosition(index, params, footprintScale);
189
190
  if (isLocal) positionToUpdate.copyFrom(local);
190
191
  else Vector3.TransformCoordinatesToRef(local, worldMatrix, positionToUpdate);
191
192
  };
@@ -203,13 +204,13 @@ function createOutlineGlow(scene, root, params, visualScale) {
203
204
  };
204
205
  return system;
205
206
  }
206
- function sampleCenterPosition(index, params) {
207
+ function sampleCenterPosition(index, params, footprintScale) {
207
208
  const shape = getFootprintShape(params);
208
- if (shape === "circle") return sampleCircleCenterPosition(index, params);
209
+ if (shape === "circle") return sampleCircleCenterPosition(index, params, footprintScale);
209
210
  const angle = sequence01(index, 0) * Math.PI * 2;
210
211
  const radius = Math.sqrt(0.16 + sequence01(index, 1) * 0.84);
211
- const xRadius = getFootprintX() * 0.5 * params.centerSpread;
212
- const zRadius = getFootprintZ() * 0.5 * params.centerSpread;
212
+ const xRadius = getFootprintX(footprintScale) * 0.5 * params.centerSpread;
213
+ const zRadius = getFootprintZ(footprintScale) * 0.5 * params.centerSpread;
213
214
  const jitterX = (sequence01(index, 7) - 0.5) * xRadius * 0.18;
214
215
  const jitterZ = (sequence01(index, 8) - 0.5) * zRadius * 0.18;
215
216
  const x = Math.cos(angle) * radius * xRadius + jitterX;
@@ -217,23 +218,24 @@ function sampleCenterPosition(index, params) {
217
218
  const y = sequence01(index, 2) * 0.12;
218
219
  return new Vector3(x, y, z);
219
220
  }
220
- function sampleOutlinePosition(index, params) {
221
+ function sampleOutlinePosition(index, params, footprintScale) {
221
222
  const shape = getFootprintShape(params);
222
- if (shape === "circle") return sampleCircleOutlinePosition(index, params);
223
- return sampleRectOutlinePosition(index, getFootprintX(), getFootprintZ(), params.outlineThickness);
223
+ if (shape === "circle") return sampleCircleOutlinePosition(index, params, footprintScale);
224
+ return sampleRectOutlinePosition(index, getFootprintX(footprintScale), getFootprintZ(footprintScale), params.outlineThickness);
224
225
  }
225
- function sampleCircleCenterPosition(index, params) {
226
+ function sampleCircleCenterPosition(index, params, footprintScale) {
226
227
  const angle = sequence01(index, 0) * Math.PI * 2;
227
- const radius = Math.sqrt(sequence01(index, 1)) * BASE_CIRCLE_RADIUS * params.centerSpread;
228
- const jitterRadius = (sequence01(index, 7) - 0.5) * BASE_CIRCLE_RADIUS * 0.08;
228
+ const baseRadius = BASE_CIRCLE_RADIUS * footprintScale.horizontal;
229
+ const radius = Math.sqrt(sequence01(index, 1)) * baseRadius * params.centerSpread;
230
+ const jitterRadius = (sequence01(index, 7) - 0.5) * baseRadius * 0.08;
229
231
  const r = Math.max(0, radius + jitterRadius);
230
232
  const y = sequence01(index, 2) * 0.12;
231
233
  return new Vector3(Math.cos(angle) * r, y, Math.sin(angle) * r);
232
234
  }
233
- function sampleCircleOutlinePosition(index, params) {
235
+ function sampleCircleOutlinePosition(index, params, footprintScale) {
234
236
  const angle = sequence01(index, 0) * Math.PI * 2;
235
237
  const jitter = (sequence01(index, 1) - 0.5) * params.outlineThickness;
236
- const radius = Math.max(1e-3, BASE_CIRCLE_RADIUS + jitter);
238
+ const radius = Math.max(1e-3, BASE_CIRCLE_RADIUS * footprintScale.horizontal + jitter);
237
239
  const y = sequence01(index, 2) * 0.16;
238
240
  return new Vector3(Math.cos(angle) * radius, y, Math.sin(angle) * radius);
239
241
  }
@@ -251,28 +253,26 @@ function getFootprintShape(params) {
251
253
  if (params.footprintShape === "circle") return params.footprintShape;
252
254
  return "rectangle";
253
255
  }
254
- function getFootprintX() {
255
- return BASE_FOOTPRINT_X;
256
+ function getFootprintX(footprintScale) {
257
+ return BASE_FOOTPRINT_X * footprintScale.x;
256
258
  }
257
- function getFootprintZ() {
258
- return BASE_FOOTPRINT_Z;
259
+ function getFootprintZ(footprintScale) {
260
+ return BASE_FOOTPRINT_Z * footprintScale.z;
259
261
  }
260
- function getCenterHeight(visualScale) {
261
- return BASE_CENTER_HEIGHT * visualScale.y;
262
+ function getCenterHeight() {
263
+ return BASE_CENTER_HEIGHT;
262
264
  }
263
- function getOutlineHeight(visualScale) {
264
- return BASE_OUTLINE_HEIGHT * visualScale.y;
265
+ function getOutlineHeight() {
266
+ return BASE_OUTLINE_HEIGHT;
265
267
  }
266
- function getMaxStrandHeight(visualScale) {
267
- return Math.max(getCenterHeight(visualScale), getOutlineHeight(visualScale));
268
+ function getMaxStrandHeight() {
269
+ return Math.max(getCenterHeight(), getOutlineHeight());
268
270
  }
269
271
  function normalizeBuildingUpgradeScale(scale) {
270
272
  const x = Math.max(0.05, Math.abs(scale.x));
271
- const y = Math.max(0.05, Math.abs(scale.y));
272
273
  const z = Math.max(0.05, Math.abs(scale.z));
273
274
  return {
274
275
  x,
275
- y,
276
276
  z,
277
277
  horizontal: Math.max(0.05, (x + z) * 0.5)
278
278
  };
@@ -16,5 +16,6 @@ export declare const woodCrusherSawdustEffectPackage: {
16
16
  requiredInputs?: import('../..').VfxInputKey[];
17
17
  optionalInputs?: import('../..').VfxInputKey[];
18
18
  geometrySpace?: "local" | "world";
19
+ applyRootScale?: boolean;
19
20
  create(context: import('../..').VfxEffectPackageContext<import('../..').VfxParamValues>): import('../..').VfxEffectHandle;
20
21
  };
@@ -16,5 +16,6 @@ export declare const woodCutterSawdustEffectPackage: {
16
16
  requiredInputs?: import('../..').VfxInputKey[];
17
17
  optionalInputs?: import('../..').VfxInputKey[];
18
18
  geometrySpace?: "local" | "world";
19
+ applyRootScale?: boolean;
19
20
  create(context: import('../..').VfxEffectPackageContext<import('../..').VfxParamValues>): import('../..').VfxEffectHandle;
20
21
  };
package/dist/index.js CHANGED
@@ -114,7 +114,8 @@ class EffectPackageService {
114
114
  warnMissingRequiredInputs(effectId, effectPackage.requiredInputs, spawn.inputs);
115
115
  const runtimeParams = stripSpawnParams(params, effectPackage.spawnParams);
116
116
  const root = new TransformNode(`vfx_package_${effectId}`, this.scene);
117
- applySpawnToRoot(root, spawn);
117
+ const applyRootScale = effectPackage.applyRootScale !== false;
118
+ applySpawnToRoot(root, spawn, applyRootScale);
118
119
  const worldPosition = root.getAbsolutePosition().clone();
119
120
  let handle;
120
121
  try {
@@ -128,7 +129,7 @@ class EffectPackageService {
128
129
  scale: spawn.scale,
129
130
  inputs: spawn.inputs
130
131
  });
131
- applySpawnToRoot(root, spawn);
132
+ applySpawnToRoot(root, spawn, applyRootScale);
132
133
  } catch (error) {
133
134
  console.warn(`[EffectPackageService] effect "${effectId}" create() failed; skipped.`, error);
134
135
  try {
@@ -212,10 +213,11 @@ function warnMissingRequiredInputs(effectId, required, inputs) {
212
213
  console.warn(`[EffectPackageService] effect "${effectId}" 声明 requiredInputs [${missing.join(", ")}] 但未提供 → 可能静默降级。`);
213
214
  }
214
215
  }
215
- function applySpawnToRoot(root, spawn) {
216
+ function applySpawnToRoot(root, spawn, applyRootScale = true) {
216
217
  if (spawn.parent) root.parent = spawn.parent;
217
218
  root.rotation.copyFrom(spawn.rotation);
218
- root.scaling.copyFrom(spawn.scale);
219
+ if (applyRootScale) root.scaling.copyFrom(spawn.scale);
220
+ else root.scaling.set(1, 1, 1);
219
221
  if (spawn.parent && !spawn.offsetIsLocal) {
220
222
  root.computeWorldMatrix(true);
221
223
  root.setAbsolutePosition(spawn.position);
package/dist/types.d.ts CHANGED
@@ -157,6 +157,12 @@ export interface VfxEffectPackage<PParams extends VfxParamValues = VfxParamValue
157
157
  * 默认随 placement 推断('socket'→local,其余→world)。
158
158
  */
159
159
  geometrySpace?: 'local' | 'world';
160
+ /**
161
+ * 是否把 spawnTransform.scale 应用到 root.scaling。默认 true。
162
+ * 对需要自行解释 scale 的特效(例如只把 scale 当作发射 footprint 尺寸)可设为 false,
163
+ * 此时 service 仍会把 scale 传入 create(context.scale),但 root 保持单位缩放。
164
+ */
165
+ applyRootScale?: boolean;
160
166
  /**
161
167
  * 构建特效。**约定(G6):root 的 transform(position/rotation/scaling/parent)归 service 独占** ——
162
168
  * service 在 create() 后会二次 `applySpawnToRoot` 覆盖,create 内写 `root.position/rotation/scaling` 会被静默清掉。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fps-games/vfx",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
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": {