@fps-games/vfx 0.3.13 → 0.3.15

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.
@@ -5,6 +5,8 @@ interface MagnetSuctionParams extends VfxParamValues {
5
5
  scale: number;
6
6
  initialHeight: number;
7
7
  color: VfxColorValue;
8
+ lineColor: VfxColorValue;
9
+ particleColor: VfxColorValue;
8
10
  opacity: number;
9
11
  intensity: number;
10
12
  forwardOffset: number;
@@ -26,6 +26,8 @@ const DEFAULT_PARAMS = {
26
26
  scale: 1.15,
27
27
  initialHeight: 0,
28
28
  color: { r: 0.82, g: 0.95, b: 1 },
29
+ lineColor: { r: 0.82, g: 0.95, b: 1 },
30
+ particleColor: { r: 0.82, g: 0.95, b: 1 },
29
31
  opacity: 1,
30
32
  intensity: 1,
31
33
  forwardOffset: 0,
@@ -43,12 +45,12 @@ const DEFAULT_PARAMS = {
43
45
  lineOpacity: 1,
44
46
  lineSpread: 1,
45
47
  lineTwist: 0,
46
- particleCount: 14,
47
- particleSize: 0.032,
48
+ particleCount: 22,
49
+ particleSize: 0.06,
48
50
  particleSpeed: 2.15,
49
51
  particleDrift: 0,
50
52
  particleOuterOffset: 0,
51
- particleOpacity: 0.55,
53
+ particleOpacity: 0.85,
52
54
  directionX: 0,
53
55
  directionZ: -1
54
56
  };
@@ -68,7 +70,8 @@ const magnetSuctionEffectPackage = {
68
70
  { key: "lifetime", label: "自定义时长", kind: "lifetime", min: 0.1, max: 30, step: 0.01 },
69
71
  { key: "scale", label: "整体缩放", kind: "scale", min: 0.05, max: 6, step: 0.01 },
70
72
  { key: "initialHeight", label: "局部高度", kind: "number", min: -2, max: 4, step: 0.01 },
71
- { key: "color", label: "吸入颜色", kind: "color" },
73
+ { key: "lineColor", label: "线条颜色", kind: "color" },
74
+ { key: "particleColor", label: "圆点颜色", kind: "color" },
72
75
  { key: "opacity", label: "整体透明度", kind: "opacity", min: 0, max: 1, step: 0.01 },
73
76
  { key: "intensity", label: "强度", kind: "opacity", min: 0, max: 1, step: 0.01 },
74
77
  { key: "forwardOffset", label: "入口前偏移", kind: "number", min: -2, max: 4, step: 0.01 },
@@ -193,10 +196,11 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
193
196
  function writeGeometry() {
194
197
  const basis = createSuctionBasis(params);
195
198
  const cameraBasis = createCameraBasis(scene, root, basis);
196
- const color = toColor3(params.color);
199
+ const lineColor = toColor3(params.lineColor ?? params.color);
200
+ const particleColor = toColor3(params.particleColor ?? params.color);
197
201
  const alphaScale = clamp01(params.opacity) * Math.max(0, params.intensity);
198
202
  let vertexIndex = 0;
199
- const putVertex = (position, alpha, brightness = 1) => {
203
+ const putVertex = (position, color, alpha, brightness = 1) => {
200
204
  const positionOffset = vertexIndex * 3;
201
205
  const colorOffset = vertexIndex * 4;
202
206
  positions[positionOffset] = position.x;
@@ -216,20 +220,21 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
216
220
  const tail = suctionPosition(tailD, line.u, line.v, basis);
217
221
  const shrink = Math.max(lateralScale(headD, basis), 0.18);
218
222
  const half = cameraBasis.ribbonSide.scale(line.width * shrink * 0.5);
219
- putVertex(tail.subtract(half), alpha, line.brightness);
220
- putVertex(tail.add(half), alpha * 0.92, line.brightness);
221
- putVertex(head, alpha * 0.22, line.brightness);
223
+ putVertex(tail.subtract(half), lineColor, alpha, line.brightness);
224
+ putVertex(tail.add(half), lineColor, alpha * 0.92, line.brightness);
225
+ putVertex(head, lineColor, alpha * 0.22, line.brightness);
222
226
  }
223
227
  for (const particle of particles) {
224
- const alpha = lineFade(particle.d, basis) * params.particleOpacity * particle.alphaMul;
228
+ const fade = lineFade(particle.d, basis);
229
+ const alpha = Math.sqrt(fade) * params.particleOpacity * particle.alphaMul;
225
230
  const center = suctionPosition(particle.d, particle.u, particle.v, basis);
226
- const shrink = Math.max(lateralScale(particle.d, basis), 0.22);
231
+ const shrink = Math.max(lateralScale(particle.d, basis), 0.48);
227
232
  const right = cameraBasis.particleRight.scale(particle.size * shrink);
228
233
  const up = cameraBasis.particleUp.scale(particle.size * shrink * 0.7);
229
- putVertex(center.subtract(right).subtractInPlace(up), alpha, particle.brightness);
230
- putVertex(center.add(right).subtractInPlace(up), alpha, particle.brightness);
231
- putVertex(center.add(right).addInPlace(up), alpha * 0.8, particle.brightness);
232
- putVertex(center.subtract(right).addInPlace(up), alpha * 0.8, particle.brightness);
234
+ putVertex(center.subtract(right).subtractInPlace(up), particleColor, alpha, particle.brightness);
235
+ putVertex(center.add(right).subtractInPlace(up), particleColor, alpha, particle.brightness);
236
+ putVertex(center.add(right).addInPlace(up), particleColor, alpha * 0.8, particle.brightness);
237
+ putVertex(center.subtract(right).addInPlace(up), particleColor, alpha * 0.8, particle.brightness);
233
238
  }
234
239
  mesh.updateVerticesData(VertexBuffer.PositionKind, positions, false, false);
235
240
  mesh.updateVerticesData(VertexBuffer.ColorKind, colors, false, false);
@@ -3,12 +3,10 @@ import { Vector3 } from "@babylonjs/core/Maths/math.vector";
3
3
  import { Constants } from "@babylonjs/core/Engines/constants";
4
4
  import { DynamicTexture } from "@babylonjs/core/Materials/Textures/dynamicTexture";
5
5
  import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
6
- import { CreateCylinder } from "@babylonjs/core/Meshes/Builders/cylinderBuilder";
7
6
  import { CreateDisc } from "@babylonjs/core/Meshes/Builders/discBuilder";
8
7
  import { CreatePlane } from "@babylonjs/core/Meshes/Builders/planeBuilder";
9
- import { CreateTorus } from "@babylonjs/core/Meshes/Builders/torusBuilder";
10
8
  import { ParticleSystem } from "@babylonjs/core/Particles/particleSystem";
11
- import { c as configureVfxParticleSystem, V as VFX_RENDERING_GROUP_ID } from "../../chunks/rendering-CV3nhygX.js";
9
+ import { c as configureVfxParticleSystem } from "../../chunks/rendering-CV3nhygX.js";
12
10
  const id = "player-upgrade";
13
11
  const nameZh = "人物升级光效";
14
12
  const source = "runtime-generated";
@@ -22,6 +20,7 @@ const manifest = {
22
20
  const strandTextureByScene = /* @__PURE__ */ new WeakMap();
23
21
  const glowTextureByScene = /* @__PURE__ */ new WeakMap();
24
22
  const haloTextureByScene = /* @__PURE__ */ new WeakMap();
23
+ const ringTextureByScene = /* @__PURE__ */ new WeakMap();
25
24
  const DEFAULT_PARAMS = {
26
25
  lifetime: 2.4,
27
26
  scale: 1,
@@ -73,13 +72,13 @@ const playerUpgradeEffectPackage = {
73
72
  { key: "glowHeight", label: "光柱高度", kind: "number", min: 0.2, max: 5, step: 0.01 },
74
73
  { key: "glowWidth", label: "光柱宽度", kind: "number", min: 0.1, max: 4, step: 0.01 }
75
74
  ],
76
- create({ scene, root, position, params }) {
75
+ create({ scene, root, position, params, renderingGroupId, scale }) {
77
76
  markVfxRoot(root);
78
77
  root.position.copyFrom(position);
79
- const strands = createStrandSystem(scene, root, params);
80
- const strandMeshes = createStrandMeshes(scene, root, params);
81
- const rings = createExpandingRings(scene, root, params);
82
- const glowAura = createGlowAura(scene, root, params);
78
+ const groupId = renderingGroupId ?? 0;
79
+ const strands = createStrandSystem(scene, root, params, groupId, scale);
80
+ const rings = createExpandingRings(scene, root, params, groupId);
81
+ const glowAura = createGlowAura(scene, root, params, groupId);
83
82
  let disposed = false;
84
83
  const disposeTimer = window.setTimeout(() => disposeAll(), getDisposeDelaySeconds(params) * 1e3);
85
84
  strands.start();
@@ -95,7 +94,6 @@ const playerUpgradeEffectPackage = {
95
94
  window.clearTimeout(disposeTimer);
96
95
  strands.stop();
97
96
  strands.dispose(false);
98
- strandMeshes.dispose();
99
97
  rings.dispose();
100
98
  glowAura.dispose();
101
99
  cleanupPlanarShadowArtifacts(scene);
@@ -133,7 +131,7 @@ function cleanupPlanarShadowArtifacts(scene) {
133
131
  }
134
132
  }
135
133
  }
136
- function createGlowAura(scene, root, params) {
134
+ function createGlowAura(scene, root, params, renderingGroupId) {
137
135
  const effectScale = Math.max(0.05, params.scale);
138
136
  const color = params.color;
139
137
  const glowHeight = Math.max(0.1, params.glowHeight) * effectScale;
@@ -148,7 +146,7 @@ function createGlowAura(scene, root, params) {
148
146
  mesh.position.y = glowHeight * 0.48;
149
147
  mesh.rotation.y = index * Math.PI / 3;
150
148
  mesh.scaling.set(glowWidth, glowHeight, 1);
151
- mesh.renderingGroupId = VFX_RENDERING_GROUP_ID;
149
+ mesh.renderingGroupId = renderingGroupId;
152
150
  const material = new StandardMaterial(`player-upgrade.soft-glow-column-material.${index}`, scene);
153
151
  configureTexturedGlowMaterial(material, color, getGlowTexture(scene), 2.8);
154
152
  material.alpha = 0;
@@ -162,7 +160,7 @@ function createGlowAura(scene, root, params) {
162
160
  halo.position.y = 0.025 * effectScale;
163
161
  halo.rotation.x = Math.PI / 2;
164
162
  halo.scaling.set(glowWidth * 1.12, glowWidth * 1.12, 1);
165
- halo.renderingGroupId = VFX_RENDERING_GROUP_ID;
163
+ halo.renderingGroupId = renderingGroupId;
166
164
  const haloMaterial = new StandardMaterial("player-upgrade.ground-halo-material", scene);
167
165
  configureTexturedGlowMaterial(haloMaterial, color, getHaloTexture(scene), 2.4);
168
166
  haloMaterial.alpha = 0;
@@ -198,19 +196,23 @@ function createGlowAura(scene, root, params) {
198
196
  }
199
197
  return { dispose };
200
198
  }
201
- function createStrandSystem(scene, root, params) {
199
+ function createStrandSystem(scene, root, params, renderingGroupId, spawnScale) {
202
200
  const effectScale = Math.max(0.05, params.scale);
201
+ const spawnHorizontalScale = getHorizontalScale(spawnScale);
202
+ const spawnVerticalScale = Math.max(0.01, Math.abs(spawnScale.y));
203
203
  const count = Math.max(0, Math.round(params.strandCount));
204
204
  const color = params.color;
205
205
  const bright = brighten(color, 0.72);
206
- const strandLength = Math.max(0.05, params.strandLength) * effectScale;
206
+ const strandLength = Math.max(0.05, params.strandLength) * effectScale * spawnVerticalScale;
207
207
  const strandRadius = Math.max(0, params.strandRadius) * effectScale;
208
- const strandWidth = Math.max(1e-3, params.strandWidth) * effectScale;
208
+ const strandWidth = Math.max(1e-3, params.strandWidth) * effectScale * spawnHorizontalScale;
209
209
  const system = new ParticleSystem("player-upgrade.gold-strands", Math.max(1, count), scene);
210
210
  configureVfxParticleSystem(system);
211
+ system.renderingGroupId = renderingGroupId;
211
212
  system.particleTexture = getStrandTexture(scene);
212
213
  system.particleTexture.hasAlpha = true;
213
214
  system.emitter = root;
215
+ system.isLocal = true;
214
216
  system.manualEmitCount = count;
215
217
  system.emitRate = count * 60;
216
218
  system.targetStopDuration = 0.05;
@@ -224,9 +226,9 @@ function createStrandSystem(scene, root, params) {
224
226
  system.maxScaleX = strandWidth * 1.8;
225
227
  system.minScaleY = 1;
226
228
  system.maxScaleY = 1.6;
227
- system.minEmitPower = Math.max(0.01, params.riseSpeed * effectScale * 0.55);
228
- system.maxEmitPower = Math.max(system.minEmitPower, params.riseSpeed * effectScale * 1.28);
229
- system.gravity = new Vector3(0, 0.34 * effectScale, 0);
229
+ system.minEmitPower = Math.max(0.01, params.riseSpeed * effectScale * spawnVerticalScale * 0.55);
230
+ system.maxEmitPower = Math.max(system.minEmitPower, params.riseSpeed * effectScale * spawnVerticalScale * 1.28);
231
+ system.gravity = new Vector3(0, 0.34 * effectScale * spawnVerticalScale, 0);
230
232
  system.updateSpeed = 1 / 60;
231
233
  system.color1 = toColor4(color, params.opacity * 0.72);
232
234
  system.color2 = toColor4(bright, params.opacity);
@@ -267,102 +269,28 @@ function createStrandSystem(scene, root, params) {
267
269
  };
268
270
  return system;
269
271
  }
270
- function createStrandMeshes(scene, root, params) {
271
- const effectScale = Math.max(0.05, params.scale);
272
- const count = Math.min(90, Math.max(0, Math.round(params.strandCount * 0.62)));
273
- const color = params.color;
274
- const radius = Math.max(0.04, params.strandRadius * effectScale);
275
- const strandLength = Math.max(0.05, params.strandLength * effectScale);
276
- const riseHeight = Math.max(0.1, params.strandHeight * effectScale);
277
- const width = Math.max(5e-3, params.strandWidth * effectScale * 1.25);
278
- const speedScale = Math.max(0.2, params.riseSpeed / DEFAULT_PARAMS.riseSpeed);
279
- const duration = Math.max(0.22, params.lifetime * 0.72 / speedScale);
280
- const states = [];
281
- for (let index = 0; index < count; index += 1) {
282
- const angle = sequence01(index, 0) * Math.PI * 2;
283
- const localRadius = radius * lerp(0.42, 1.08, sequence01(index, 1));
284
- const mesh = CreateCylinder(
285
- `player-upgrade.gold-strand-mesh.${index}`,
286
- { height: 1, diameter: width, tessellation: 6 },
287
- scene
288
- );
289
- prepareVfxMesh(mesh);
290
- mesh.parent = root;
291
- mesh.position.x = Math.cos(angle) * localRadius;
292
- mesh.position.z = Math.sin(angle) * localRadius;
293
- mesh.position.y = 0;
294
- mesh.renderingGroupId = VFX_RENDERING_GROUP_ID;
295
- mesh.isVisible = false;
296
- const material = new StandardMaterial(`player-upgrade.gold-strand-mesh-material.${index}`, scene);
297
- configureGlowMaterial(material, color, 2.8);
298
- material.alpha = 0;
299
- mesh.material = material;
300
- states.push({
301
- mesh,
302
- material,
303
- height: strandLength * lerp(0.68, 1.18, sequence01(index, 2)),
304
- startDelay: sequence01(index, 3) * Math.min(0.34, params.lifetime * 0.18),
305
- duration: duration * lerp(0.78, 1.12, sequence01(index, 4))
306
- });
307
- }
308
- let age = 0;
309
- let disposed = false;
310
- const observer = scene.onBeforeRenderObservable.add(() => {
311
- if (disposed) return;
312
- const deltaSeconds = Math.min(0.05, scene.getEngine().getDeltaTime() / 1e3);
313
- age += deltaSeconds;
314
- for (const state of states) {
315
- const localAge = age - state.startDelay;
316
- if (localAge < 0 || localAge > state.duration) {
317
- state.mesh.isVisible = false;
318
- state.material.alpha = 0;
319
- continue;
320
- }
321
- const t = Math.min(1, localAge / state.duration);
322
- const grow = Math.min(1, t / 0.32);
323
- const fadeOut = Math.max(0, 1 - Math.max(0, t - 0.66) / 0.34);
324
- const alpha = params.opacity * fadeOut * Math.min(1, t / 0.12);
325
- const currentHeight = state.height * grow;
326
- state.mesh.isVisible = alpha > 0.01;
327
- state.mesh.scaling.set(1, currentHeight, 1);
328
- state.mesh.position.y = currentHeight * 0.5 + riseHeight * easeOutCubic(t);
329
- state.material.alpha = Math.min(1, alpha * 1.35);
330
- }
331
- if (age >= params.lifetime + 0.45) dispose();
332
- });
333
- function dispose() {
334
- if (disposed) return;
335
- disposed = true;
336
- scene.onBeforeRenderObservable.remove(observer);
337
- for (const state of states) {
338
- state.mesh.dispose();
339
- state.material.dispose();
340
- }
341
- }
342
- return { dispose };
343
- }
344
- function createExpandingRings(scene, root, params) {
272
+ function createExpandingRings(scene, root, params, renderingGroupId) {
345
273
  const effectScale = Math.max(0.05, params.scale);
346
274
  const ringCount = Math.max(0, Math.round(params.ringCount));
347
275
  const ringDuration = Math.max(0.18, Math.min(0.9, params.lifetime * 0.38));
348
276
  const minRadius = 0.14 * effectScale;
349
277
  const maxRadius = Math.max(minRadius + 0.01, params.ringMaxRadius * effectScale);
350
- const thickness = Math.max(2e-3, params.ringThickness * effectScale);
351
278
  const color = params.color;
352
279
  const states = [];
353
280
  for (let index = 0; index < ringCount; index += 1) {
354
- const mesh = CreateTorus(
281
+ const mesh = CreatePlane(
355
282
  `player-upgrade.expanding-ring.${index}`,
356
- { diameter: 1, thickness, tessellation: 96 },
283
+ { size: 2 },
357
284
  scene
358
285
  );
359
286
  prepareVfxMesh(mesh);
360
287
  mesh.parent = root;
361
288
  mesh.position.y = 0.03 * effectScale + index * 0.012 * effectScale;
362
- mesh.renderingGroupId = VFX_RENDERING_GROUP_ID;
289
+ mesh.rotation.x = Math.PI / 2;
290
+ mesh.renderingGroupId = renderingGroupId;
363
291
  mesh.isVisible = false;
364
292
  const material = new StandardMaterial(`player-upgrade.ring-material.${index}`, scene);
365
- configureGlowMaterial(material, color, 2.4);
293
+ configureTexturedGlowMaterial(material, color, getRingTexture(scene), 2.4);
366
294
  material.alpha = 0;
367
295
  mesh.material = material;
368
296
  states.push({
@@ -390,7 +318,7 @@ function createExpandingRings(scene, root, params) {
390
318
  const radius = lerp(minRadius, maxRadius, eased);
391
319
  const alpha = params.ringOpacity * (1 - t) * Math.min(1, t / 0.12);
392
320
  state.mesh.isVisible = alpha > 0.01;
393
- state.mesh.scaling.set(radius, radius, radius);
321
+ state.mesh.scaling.set(radius, radius, 1);
394
322
  const ringColor = brighten(color, 0.1 + 0.08 * (1 - t));
395
323
  state.material.diffuseColor = toColor3(ringColor);
396
324
  state.material.emissiveColor = toGlowColor3(ringColor, 3.4);
@@ -477,6 +405,45 @@ function getHaloTexture(scene) {
477
405
  haloTextureByScene.set(scene, texture);
478
406
  return texture;
479
407
  }
408
+ function getRingTexture(scene) {
409
+ const existing = ringTextureByScene.get(scene);
410
+ if (existing) return existing;
411
+ const size = 256;
412
+ const texture = new DynamicTexture("player_upgrade_expanding_ring_texture", { width: size, height: size }, scene, true);
413
+ const context = texture.getContext();
414
+ const canvasContext = context;
415
+ context.clearRect(0, 0, size, size);
416
+ const center = size / 2;
417
+ const radius = size * 0.39;
418
+ const strokeWidth = size * 0.105;
419
+ canvasContext.lineCap = "round";
420
+ canvasContext.lineJoin = "round";
421
+ const outerGlow = context.createRadialGradient(center, center, radius - strokeWidth * 1.1, center, center, radius + strokeWidth * 1.8);
422
+ outerGlow.addColorStop(0, "rgba(255,255,255,0)");
423
+ outerGlow.addColorStop(0.38, "rgba(255,255,255,0.28)");
424
+ outerGlow.addColorStop(0.58, "rgba(255,255,255,0.34)");
425
+ outerGlow.addColorStop(1, "rgba(255,255,255,0)");
426
+ context.strokeStyle = outerGlow;
427
+ context.lineWidth = strokeWidth * 2.6;
428
+ context.beginPath();
429
+ context.arc(center, center, radius, 0, Math.PI * 2);
430
+ context.stroke();
431
+ const core = context.createRadialGradient(center, center, radius - strokeWidth * 0.85, center, center, radius + strokeWidth * 0.85);
432
+ core.addColorStop(0, "rgba(255,255,255,0)");
433
+ core.addColorStop(0.42, "rgba(255,255,255,0.72)");
434
+ core.addColorStop(0.5, "rgba(255,255,255,1)");
435
+ core.addColorStop(0.62, "rgba(255,255,255,0.62)");
436
+ core.addColorStop(1, "rgba(255,255,255,0)");
437
+ context.strokeStyle = core;
438
+ context.lineWidth = strokeWidth;
439
+ context.beginPath();
440
+ context.arc(center, center, radius, 0, Math.PI * 2);
441
+ context.stroke();
442
+ texture.hasAlpha = true;
443
+ texture.update(false);
444
+ ringTextureByScene.set(scene, texture);
445
+ return texture;
446
+ }
480
447
  function getDisposeDelaySeconds(params) {
481
448
  const ringDuration = Math.max(0.18, Math.min(0.9, params.lifetime * 0.38));
482
449
  return Math.max(0.2, params.lifetime) + getLastRingEndTime(params, ringDuration) + 0.35;
@@ -484,6 +451,9 @@ function getDisposeDelaySeconds(params) {
484
451
  function getLastRingEndTime(params, ringDuration) {
485
452
  return Math.max(0, Math.round(params.ringCount) - 1) * Math.max(0.02, params.ringInterval) + ringDuration;
486
453
  }
454
+ function getHorizontalScale(scale) {
455
+ return Math.max(0.01, (Math.abs(scale.x) + Math.abs(scale.z)) * 0.5);
456
+ }
487
457
  function toColor4(color, alpha) {
488
458
  return new Color4(clamp01(color.r), clamp01(color.g), clamp01(color.b), clamp01(alpha));
489
459
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fps-games/vfx",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
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": {