@fps-games/vfx 0.3.15 → 0.3.17
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 +8 -0
- package/dist/EffectPackageService.d.ts +3 -1
- package/dist/SceneVfxService.d.ts +2 -1
- package/dist/effects/energy-shield/index.d.ts +5 -0
- package/dist/effects/energy-shield/index.js +62 -2
- package/dist/effects/pulse-laser-volley-sequence/index.d.ts +14 -0
- package/dist/effects/pulse-laser-volley-sequence/index.js +142 -0
- package/dist/effects/treasure-attention/index.js +9 -2
- package/dist/index.js +5 -2
- package/dist/types.d.ts +8 -0
- package/index.json +11 -1
- package/package.json +1 -1
- package/packages/effects/energy-shield/vfx-params.json +2 -0
- package/packages/effects/pulse-laser-volley-sequence/vfx-params.json +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
|
|
13
13
|
<!-- release.mjs 在此行下方插入新版本区块 -->
|
|
14
14
|
|
|
15
|
+
## v0.3.16 — 2026-07-08
|
|
16
|
+
|
|
17
|
+
### 改动
|
|
18
|
+
- feat(pulse-laser-volley-sequence): add single-shot pulse laser variant
|
|
19
|
+
|
|
20
|
+
### 特效
|
|
21
|
+
- 新增:pulse-laser-volley-sequence
|
|
22
|
+
|
|
15
23
|
## v0.3.11 — 2026-07-07
|
|
16
24
|
|
|
17
25
|
### 改动
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
2
2
|
import { Scene } from '@babylonjs/core/scene';
|
|
3
3
|
import { VfxRegistry } from './registry';
|
|
4
|
-
import { VfxDebugAnchor, VfxEffectHandle, VfxEffectPackage, VfxParamValues, VfxSpawnTransform } from './types';
|
|
4
|
+
import { VfxDebugAnchor, VfxEffectHandle, VfxEffectPackage, VfxParamValues, VfxRuntimeResources, VfxSpawnTransform } from './types';
|
|
5
5
|
export interface EffectPackageServiceOptions {
|
|
6
6
|
/** 注入的注册表(由项目侧 glob + buildRegistry 构建);库不直接引用项目注册表 */
|
|
7
7
|
registry: VfxRegistry;
|
|
8
8
|
getDebugAnchorPosition?: (anchor: VfxDebugAnchor) => Vector3 | null;
|
|
9
|
+
runtime?: VfxRuntimeResources;
|
|
9
10
|
}
|
|
10
11
|
export declare class EffectPackageService {
|
|
11
12
|
private readonly scene;
|
|
12
13
|
private readonly registry;
|
|
13
14
|
private readonly getDebugAnchorPosition?;
|
|
15
|
+
private readonly runtime?;
|
|
14
16
|
private readonly activeHandles;
|
|
15
17
|
private readonly savedParams;
|
|
16
18
|
constructor(scene: Scene, options: EffectPackageServiceOptions);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Scene } from '@babylonjs/core/scene';
|
|
2
2
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
3
3
|
import { VfxRegistry } from './registry';
|
|
4
|
-
import { VfxDebugAnchor, VfxEffectHandle, VfxEffectPackage, VfxParamValues, VfxSpawnTransform } from './types';
|
|
4
|
+
import { VfxDebugAnchor, VfxEffectHandle, VfxEffectPackage, VfxParamValues, VfxRuntimeResources, VfxSpawnTransform } from './types';
|
|
5
5
|
import { EffectPackageService } from './EffectPackageService';
|
|
6
6
|
export interface SceneVfxWarmupEntry {
|
|
7
7
|
effectId: string;
|
|
@@ -12,6 +12,7 @@ export interface SceneVfxServiceOptions {
|
|
|
12
12
|
/** 注入的注册表(项目侧 glob + buildRegistry 构建) */
|
|
13
13
|
registry: VfxRegistry;
|
|
14
14
|
getDebugAnchorPosition?: (anchor: VfxDebugAnchor) => Vector3 | null;
|
|
15
|
+
runtime?: VfxRuntimeResources;
|
|
15
16
|
}
|
|
16
17
|
export declare class SceneVfxService {
|
|
17
18
|
private effectPackageService;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GlowLayer } from '@babylonjs/core/Layers/glowLayer';
|
|
1
2
|
import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial';
|
|
2
3
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
3
4
|
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
|
|
@@ -23,6 +24,8 @@ export interface EnergyShieldParams extends VfxParamValues {
|
|
|
23
24
|
flowIntensity: number;
|
|
24
25
|
marbleContrast: number;
|
|
25
26
|
bloomIntensity: number;
|
|
27
|
+
bloomKernelSize: number;
|
|
28
|
+
showGlowLayer: boolean;
|
|
26
29
|
haloScale: number;
|
|
27
30
|
showHalo: boolean;
|
|
28
31
|
segments: number;
|
|
@@ -33,11 +36,13 @@ export interface CreateEnergyShieldOptions {
|
|
|
33
36
|
position?: Vector3;
|
|
34
37
|
params?: Partial<EnergyShieldParams>;
|
|
35
38
|
renderingGroupId?: number;
|
|
39
|
+
glowLayer?: GlowLayer | null;
|
|
36
40
|
disposeRoot?: boolean;
|
|
37
41
|
}
|
|
38
42
|
export interface EnergyShieldHandle extends VfxEffectHandle {
|
|
39
43
|
shield: Mesh;
|
|
40
44
|
material: ShaderMaterial;
|
|
45
|
+
glowLayer: GlowLayer | null;
|
|
41
46
|
update(params: Partial<EnergyShieldParams>): void;
|
|
42
47
|
}
|
|
43
48
|
export declare const energyShieldEffectPackage: VfxEffectPackage<EnergyShieldParams>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Constants } from "@babylonjs/core/Engines/constants";
|
|
2
|
+
import { GlowLayer } from "@babylonjs/core/Layers/glowLayer";
|
|
2
3
|
import { Effect } from "@babylonjs/core/Materials/effect";
|
|
3
4
|
import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
|
|
4
5
|
import { Color3 } from "@babylonjs/core/Maths/math.color";
|
|
@@ -28,6 +29,8 @@ const DEFAULT_PARAMS = {
|
|
|
28
29
|
flowIntensity: 1.15,
|
|
29
30
|
marbleContrast: 0.67,
|
|
30
31
|
bloomIntensity: 0.45,
|
|
32
|
+
bloomKernelSize: 36,
|
|
33
|
+
showGlowLayer: true,
|
|
31
34
|
haloScale: 1.018,
|
|
32
35
|
showHalo: false,
|
|
33
36
|
segments: 64
|
|
@@ -234,11 +237,19 @@ const energyShieldEffectPackage = {
|
|
|
234
237
|
{ key: "flowIntensity", label: "流动强度", kind: "number", min: 0, max: 8, step: 0.01 },
|
|
235
238
|
{ key: "marbleContrast", label: "纹路锐度", kind: "number", min: 0.25, max: 4, step: 0.01 },
|
|
236
239
|
{ key: "bloomIntensity", label: "Bloom 强度", kind: "number", min: 0, max: 4, step: 0.01 },
|
|
240
|
+
{ key: "bloomKernelSize", label: "GlowLayer 半径", kind: "number", min: 8, max: 128, step: 1 },
|
|
241
|
+
{ key: "showGlowLayer", label: "显示 GlowLayer", kind: "boolean" },
|
|
237
242
|
{ key: "haloScale", label: "外层光晕缩放", kind: "scale", min: 1, max: 1.08, step: 1e-3 },
|
|
238
243
|
{ key: "showHalo", label: "显示外层光晕", kind: "boolean" }
|
|
239
244
|
],
|
|
240
|
-
create({ scene, root, position, renderingGroupId, params }) {
|
|
241
|
-
return createEnergyShield(scene, {
|
|
245
|
+
create({ scene, root, position, renderingGroupId, params, runtime }) {
|
|
246
|
+
return createEnergyShield(scene, {
|
|
247
|
+
root,
|
|
248
|
+
position,
|
|
249
|
+
renderingGroupId,
|
|
250
|
+
params,
|
|
251
|
+
glowLayer: runtime?.glowLayer ?? runtime?.getGlowLayer?.() ?? null
|
|
252
|
+
});
|
|
242
253
|
}
|
|
243
254
|
};
|
|
244
255
|
function createEnergyShield(scene, options = {}) {
|
|
@@ -269,6 +280,10 @@ function createEnergyShield(scene, options = {}) {
|
|
|
269
280
|
shield.alphaIndex = 20;
|
|
270
281
|
const material = createShieldMaterial(scene);
|
|
271
282
|
shield.material = material;
|
|
283
|
+
const sharedGlowLayer = options.glowLayer ?? null;
|
|
284
|
+
let glowLayer = null;
|
|
285
|
+
let ownsGlowLayer = false;
|
|
286
|
+
let glowMeshIncluded = false;
|
|
272
287
|
let halo = null;
|
|
273
288
|
let haloMaterial = null;
|
|
274
289
|
let haloFeather = null;
|
|
@@ -283,6 +298,45 @@ function createEnergyShield(scene, options = {}) {
|
|
|
283
298
|
haloFeather = null;
|
|
284
299
|
haloFeatherMaterial = null;
|
|
285
300
|
};
|
|
301
|
+
const addGlowMesh = () => {
|
|
302
|
+
if (!glowLayer || glowMeshIncluded) return;
|
|
303
|
+
glowLayer.addIncludedOnlyMesh(shield);
|
|
304
|
+
glowLayer.referenceMeshToUseItsOwnMaterial(shield);
|
|
305
|
+
glowMeshIncluded = true;
|
|
306
|
+
};
|
|
307
|
+
const removeGlowMesh = () => {
|
|
308
|
+
if (!glowLayer || !glowMeshIncluded) return;
|
|
309
|
+
glowLayer.removeIncludedOnlyMesh(shield);
|
|
310
|
+
glowMeshIncluded = false;
|
|
311
|
+
};
|
|
312
|
+
const disposeGlowLayer = () => {
|
|
313
|
+
removeGlowMesh();
|
|
314
|
+
if (ownsGlowLayer) glowLayer?.dispose();
|
|
315
|
+
glowLayer = null;
|
|
316
|
+
ownsGlowLayer = false;
|
|
317
|
+
};
|
|
318
|
+
const ensureGlowLayer = () => {
|
|
319
|
+
if (!params.showGlowLayer || params.bloomIntensity <= 1e-3) {
|
|
320
|
+
disposeGlowLayer();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (!glowLayer) {
|
|
324
|
+
if (sharedGlowLayer) {
|
|
325
|
+
glowLayer = sharedGlowLayer;
|
|
326
|
+
} else {
|
|
327
|
+
glowLayer = new GlowLayer(`vfx_energyShield_glow_${shield.uniqueId}`, scene, {
|
|
328
|
+
blurKernelSize: params.bloomKernelSize,
|
|
329
|
+
mainTextureRatio: 0.35
|
|
330
|
+
});
|
|
331
|
+
ownsGlowLayer = true;
|
|
332
|
+
}
|
|
333
|
+
addGlowMesh();
|
|
334
|
+
}
|
|
335
|
+
if (ownsGlowLayer) {
|
|
336
|
+
glowLayer.intensity = params.bloomIntensity;
|
|
337
|
+
glowLayer.blurKernelSize = params.bloomKernelSize;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
286
340
|
const ensureHalo = () => {
|
|
287
341
|
if (!params.showHalo || params.bloomIntensity <= 1e-3) {
|
|
288
342
|
disposeHalo();
|
|
@@ -335,6 +389,7 @@ function createEnergyShield(scene, options = {}) {
|
|
|
335
389
|
haloFeather.material = haloFeatherMaterial;
|
|
336
390
|
}
|
|
337
391
|
};
|
|
392
|
+
ensureGlowLayer();
|
|
338
393
|
ensureHalo();
|
|
339
394
|
applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params);
|
|
340
395
|
let elapsed = 0;
|
|
@@ -349,6 +404,7 @@ function createEnergyShield(scene, options = {}) {
|
|
|
349
404
|
if (disposed) return;
|
|
350
405
|
disposed = true;
|
|
351
406
|
scene.onBeforeRenderObservable.remove(observer);
|
|
407
|
+
disposeGlowLayer();
|
|
352
408
|
disposeHalo();
|
|
353
409
|
shield.dispose(false, false);
|
|
354
410
|
material.dispose();
|
|
@@ -358,8 +414,12 @@ function createEnergyShield(scene, options = {}) {
|
|
|
358
414
|
root,
|
|
359
415
|
shield,
|
|
360
416
|
material,
|
|
417
|
+
get glowLayer() {
|
|
418
|
+
return glowLayer;
|
|
419
|
+
},
|
|
361
420
|
update(partial) {
|
|
362
421
|
params = mergeParams(partial, params);
|
|
422
|
+
ensureGlowLayer();
|
|
363
423
|
ensureHalo();
|
|
364
424
|
applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params);
|
|
365
425
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
|
|
2
|
+
interface PulseLaserParams extends VfxParamValues {
|
|
3
|
+
lifetimeMode: string;
|
|
4
|
+
lifetime: number;
|
|
5
|
+
targetDistance: number;
|
|
6
|
+
boltSpeed: number;
|
|
7
|
+
boltLen: number;
|
|
8
|
+
boltColor: VfxColorValue;
|
|
9
|
+
boltCore: number;
|
|
10
|
+
boltCorePow: number;
|
|
11
|
+
boltBright: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const pulseLaserVolleySequenceEffectPackage: VfxEffectPackage<PulseLaserParams>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { CreatePlane } from "@babylonjs/core/Meshes/Builders/planeBuilder";
|
|
2
|
+
import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
|
|
3
|
+
import { Effect } from "@babylonjs/core/Materials/effect";
|
|
4
|
+
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
|
|
5
|
+
import { Color3 } from "@babylonjs/core/Maths/math.color";
|
|
6
|
+
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
|
|
7
|
+
import { Space } from "@babylonjs/core/Maths/math.axis";
|
|
8
|
+
import { Constants } from "@babylonjs/core/Engines/constants";
|
|
9
|
+
import { L as LIFETIME_DEBUG_PARAMS, a as LIFETIME_DEFAULTS, s as scheduleLifetime } from "../../chunks/space-shared-xh5IOcLH.js";
|
|
10
|
+
const boltTextureUrl = "data:image/webp;base64,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";
|
|
11
|
+
const DEFAULT_PARAMS = {
|
|
12
|
+
...LIFETIME_DEFAULTS,
|
|
13
|
+
targetDistance: 12,
|
|
14
|
+
boltSpeed: 26,
|
|
15
|
+
boltLen: 1,
|
|
16
|
+
boltColor: { r: 0.071, g: 0.408, b: 1 },
|
|
17
|
+
// #1268ff
|
|
18
|
+
boltCore: 0.3,
|
|
19
|
+
boltCorePow: 3,
|
|
20
|
+
boltBright: 1.3
|
|
21
|
+
};
|
|
22
|
+
const BOLT_LEN = 2, BOLT_W = 0.34;
|
|
23
|
+
const BOLT_VERTEX = `precision highp float;
|
|
24
|
+
attribute vec3 position; attribute vec2 uv;
|
|
25
|
+
uniform mat4 worldViewProjection;
|
|
26
|
+
varying vec2 vUV;
|
|
27
|
+
void main() { vUV = uv; gl_Position = worldViewProjection * vec4(position, 1.0); }`;
|
|
28
|
+
const BOLT_FRAGMENT = `precision highp float;
|
|
29
|
+
varying vec2 vUV;
|
|
30
|
+
uniform sampler2D tex;
|
|
31
|
+
uniform vec3 tint;
|
|
32
|
+
uniform float coreStr; uniform float corePow; uniform float bright;
|
|
33
|
+
void main() {
|
|
34
|
+
vec4 t = texture2D(tex, vUV);
|
|
35
|
+
float core = pow(t.a, corePow) * coreStr;
|
|
36
|
+
vec3 col = mix(tint, vec3(1.0), core) * bright;
|
|
37
|
+
gl_FragColor = vec4(col, t.a);
|
|
38
|
+
}`;
|
|
39
|
+
let shadersRegistered = false;
|
|
40
|
+
function registerShaders() {
|
|
41
|
+
if (shadersRegistered) return;
|
|
42
|
+
Effect.ShadersStore["vfxPulseBoltVertexShader"] = BOLT_VERTEX;
|
|
43
|
+
Effect.ShadersStore["vfxPulseBoltFragmentShader"] = BOLT_FRAGMENT;
|
|
44
|
+
shadersRegistered = true;
|
|
45
|
+
}
|
|
46
|
+
function colorVec(c) {
|
|
47
|
+
return new Color3(c.r, c.g, c.b);
|
|
48
|
+
}
|
|
49
|
+
function excludeFromGlow(scene, mesh) {
|
|
50
|
+
for (const layer of scene.effectLayers ?? []) {
|
|
51
|
+
const add = layer.addExcludedMesh;
|
|
52
|
+
if (typeof add === "function") add.call(layer, mesh);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const pulseLaserVolleySequenceEffectPackage = {
|
|
56
|
+
id: "pulse-laser-volley-sequence",
|
|
57
|
+
nameZh: "脉冲激光(单发)",
|
|
58
|
+
placement: "socket",
|
|
59
|
+
optionalInputs: ["aim"],
|
|
60
|
+
defaultParams: DEFAULT_PARAMS,
|
|
61
|
+
debugAnchor: "playerRight",
|
|
62
|
+
debugParams: [
|
|
63
|
+
...LIFETIME_DEBUG_PARAMS,
|
|
64
|
+
{ key: "targetDistance", label: "目标距离", kind: "number", min: 2, max: 40, step: 0.5 },
|
|
65
|
+
{ key: "boltSpeed", label: "光弹速度", kind: "number", min: 5, max: 60, step: 1 },
|
|
66
|
+
{ key: "boltLen", label: "光弹长度", kind: "number", min: 0.3, max: 4, step: 0.05 },
|
|
67
|
+
{ key: "boltColor", label: "光弹颜色", kind: "color" },
|
|
68
|
+
{ key: "boltCore", label: "白芯强度", kind: "number", min: 0, max: 1, step: 0.01 },
|
|
69
|
+
{ key: "boltCorePow", label: "白芯收窄", kind: "number", min: 0.5, max: 8, step: 0.1 },
|
|
70
|
+
{ key: "boltBright", label: "光弹亮度", kind: "number", min: 0.2, max: 3, step: 0.05 }
|
|
71
|
+
],
|
|
72
|
+
create({ scene, root, position, rotationY, params, inputs }) {
|
|
73
|
+
root.position.copyFrom(position);
|
|
74
|
+
root.rotation.y = rotationY;
|
|
75
|
+
registerShaders();
|
|
76
|
+
const effectiveDist = () => {
|
|
77
|
+
if (!inputs?.aim) return params.targetDistance;
|
|
78
|
+
const tgt = inputs.aim();
|
|
79
|
+
root.lookAt(tgt, 0, 0, 0, Space.WORLD);
|
|
80
|
+
return Vector3.Distance(root.getAbsolutePosition(), tgt);
|
|
81
|
+
};
|
|
82
|
+
const boltTex = new Texture(boltTextureUrl, scene);
|
|
83
|
+
boltTex.hasAlpha = true;
|
|
84
|
+
const boltMat = new ShaderMaterial("vfx_pulse_boltMat", scene, { vertex: "vfxPulseBolt", fragment: "vfxPulseBolt" }, {
|
|
85
|
+
attributes: ["position", "uv"],
|
|
86
|
+
uniforms: ["worldViewProjection", "tint", "coreStr", "corePow", "bright"],
|
|
87
|
+
samplers: ["tex"],
|
|
88
|
+
needAlphaBlending: true
|
|
89
|
+
});
|
|
90
|
+
boltMat.setTexture("tex", boltTex);
|
|
91
|
+
boltMat.backFaceCulling = false;
|
|
92
|
+
boltMat.disableDepthWrite = true;
|
|
93
|
+
boltMat.alphaMode = Constants.ALPHA_ADD;
|
|
94
|
+
boltMat.setColor3("tint", colorVec(params.boltColor));
|
|
95
|
+
boltMat.setFloat("coreStr", params.boltCore);
|
|
96
|
+
boltMat.setFloat("corePow", params.boltCorePow);
|
|
97
|
+
boltMat.setFloat("bright", params.boltBright);
|
|
98
|
+
const mesh = CreatePlane("vfx_pulse_single_bolt", { width: BOLT_W, height: BOLT_LEN }, scene);
|
|
99
|
+
mesh.rotation.x = Math.PI / 2;
|
|
100
|
+
mesh.material = boltMat;
|
|
101
|
+
mesh.isPickable = false;
|
|
102
|
+
mesh.parent = root;
|
|
103
|
+
mesh.setEnabled(false);
|
|
104
|
+
excludeFromGlow(scene, mesh);
|
|
105
|
+
const bolt = { mesh, left: 0 };
|
|
106
|
+
let fired = false;
|
|
107
|
+
const observer = scene.onBeforeRenderObservable.add(() => {
|
|
108
|
+
const dt = Math.min(scene.getEngine().getDeltaTime() / 1e3, 0.05);
|
|
109
|
+
const dist = effectiveDist();
|
|
110
|
+
if (!fired) {
|
|
111
|
+
bolt.mesh.position.set(0, 0, 0);
|
|
112
|
+
bolt.mesh.scaling.z = params.boltLen / BOLT_LEN;
|
|
113
|
+
bolt.left = dist / params.boltSpeed;
|
|
114
|
+
bolt.mesh.setEnabled(true);
|
|
115
|
+
fired = true;
|
|
116
|
+
}
|
|
117
|
+
const step = params.boltSpeed * dt;
|
|
118
|
+
if (bolt.left > 0) {
|
|
119
|
+
bolt.left -= dt;
|
|
120
|
+
bolt.mesh.position.z += step;
|
|
121
|
+
if (bolt.left <= 0) bolt.mesh.setEnabled(false);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
let disposed = false;
|
|
125
|
+
let ltTimer = 0;
|
|
126
|
+
const dispose = () => {
|
|
127
|
+
if (disposed) return;
|
|
128
|
+
disposed = true;
|
|
129
|
+
if (ltTimer) window.clearTimeout(ltTimer);
|
|
130
|
+
scene.onBeforeRenderObservable.remove(observer);
|
|
131
|
+
bolt.mesh.dispose();
|
|
132
|
+
boltMat.dispose();
|
|
133
|
+
boltTex.dispose();
|
|
134
|
+
root.dispose();
|
|
135
|
+
};
|
|
136
|
+
ltTimer = scheduleLifetime(params, dispose);
|
|
137
|
+
return { root, dispose };
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
export {
|
|
141
|
+
pulseLaserVolleySequenceEffectPackage
|
|
142
|
+
};
|
|
@@ -76,8 +76,15 @@ const treasureAttentionEffectPackage = {
|
|
|
76
76
|
{ key: "bloomKernelSize", label: "Bloom 半径", kind: "number", min: 8, max: 96, step: 1 },
|
|
77
77
|
{ key: "showGlowLayer", label: "显示 GlowLayer", kind: "boolean" }
|
|
78
78
|
],
|
|
79
|
-
create({ scene, root, position, renderingGroupId, params }) {
|
|
80
|
-
return createTreasureAttentionFx(scene, {
|
|
79
|
+
create({ scene, root, position, renderingGroupId, params, runtime }) {
|
|
80
|
+
return createTreasureAttentionFx(scene, {
|
|
81
|
+
root,
|
|
82
|
+
position,
|
|
83
|
+
renderingGroupId,
|
|
84
|
+
params,
|
|
85
|
+
glowLayer: runtime?.glowLayer ?? runtime?.getGlowLayer?.() ?? null,
|
|
86
|
+
serviceOwnedRoot: true
|
|
87
|
+
});
|
|
81
88
|
}
|
|
82
89
|
};
|
|
83
90
|
function createTreasureAttentionFx(scene, options = {}) {
|
package/dist/index.js
CHANGED
|
@@ -75,6 +75,7 @@ class EffectPackageService {
|
|
|
75
75
|
this.scene = scene;
|
|
76
76
|
this.registry = options.registry;
|
|
77
77
|
this.getDebugAnchorPosition = options.getDebugAnchorPosition;
|
|
78
|
+
this.runtime = options.runtime;
|
|
78
79
|
this.savedParams = cloneOverrideMap(options.registry.savedParams);
|
|
79
80
|
}
|
|
80
81
|
getEffectPackages() {
|
|
@@ -127,7 +128,8 @@ class EffectPackageService {
|
|
|
127
128
|
renderingGroupId: spawn.renderingGroupId,
|
|
128
129
|
params: runtimeParams,
|
|
129
130
|
scale: spawn.scale,
|
|
130
|
-
inputs: spawn.inputs
|
|
131
|
+
inputs: spawn.inputs,
|
|
132
|
+
runtime: this.runtime
|
|
131
133
|
});
|
|
132
134
|
applySpawnToRoot(root, spawn, applyRootScale);
|
|
133
135
|
} catch (error) {
|
|
@@ -315,7 +317,8 @@ class SceneVfxService {
|
|
|
315
317
|
constructor(scene, options) {
|
|
316
318
|
this.effectPackageService = new EffectPackageService(scene, {
|
|
317
319
|
registry: options.registry,
|
|
318
|
-
getDebugAnchorPosition: options.getDebugAnchorPosition
|
|
320
|
+
getDebugAnchorPosition: options.getDebugAnchorPosition,
|
|
321
|
+
runtime: options.runtime
|
|
319
322
|
});
|
|
320
323
|
}
|
|
321
324
|
getEffectPackageService() {
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Scene } from '@babylonjs/core/scene';
|
|
|
2
2
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
3
3
|
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
4
4
|
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
|
|
5
|
+
import { GlowLayer } from '@babylonjs/core/Layers/glowLayer';
|
|
5
6
|
export type VfxDebugAnchor = 'playerRight' | 'player';
|
|
6
7
|
export type VfxInput<T> = T | (() => T);
|
|
7
8
|
export type VfxInputKey = 'aim' | 'normal' | 'endpoints' | 'target' | 'reference';
|
|
@@ -24,6 +25,11 @@ export interface VfxResolvedInputs {
|
|
|
24
25
|
reference?: () => Vector3;
|
|
25
26
|
scalars?: Record<string, () => number>;
|
|
26
27
|
}
|
|
28
|
+
/** 运行时共享资源:不可序列化,由项目/游戏层注入,与可保存 params 分开。 */
|
|
29
|
+
export interface VfxRuntimeResources {
|
|
30
|
+
glowLayer?: GlowLayer | null;
|
|
31
|
+
getGlowLayer?: () => GlowLayer | null;
|
|
32
|
+
}
|
|
27
33
|
export type VfxParamValue = number | string | boolean | VfxColorValue;
|
|
28
34
|
export type VfxParamValues = Record<string, VfxParamValue>;
|
|
29
35
|
export interface VfxColorValue {
|
|
@@ -74,6 +80,8 @@ export interface VfxEffectPackageContext<PParams extends VfxParamValues = VfxPar
|
|
|
74
80
|
scale: Vector3;
|
|
75
81
|
/** 运行时外部信息(已归一化为访问器)。link/target/定向武器/活改标量等按 required/optionalInputs 读取。 */
|
|
76
82
|
inputs?: VfxResolvedInputs;
|
|
83
|
+
/** 运行时共享资源(如 scene 级共享 GlowLayer)。 */
|
|
84
|
+
runtime?: VfxRuntimeResources;
|
|
77
85
|
}
|
|
78
86
|
export interface VfxEffectHandle {
|
|
79
87
|
root: TransformNode;
|
package/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "vfx-index/1.0",
|
|
3
|
-
"count":
|
|
3
|
+
"count": 41,
|
|
4
4
|
"effects": [
|
|
5
5
|
{
|
|
6
6
|
"id": "build.particle.lumber_debris_scatter",
|
|
@@ -277,6 +277,16 @@
|
|
|
277
277
|
"media": {},
|
|
278
278
|
"path": "packages/effects/pulse-laser-volley"
|
|
279
279
|
},
|
|
280
|
+
{
|
|
281
|
+
"id": "pulse-laser-volley-sequence",
|
|
282
|
+
"dir": "pulse-laser-volley-sequence",
|
|
283
|
+
"nameZh": "pulse-laser-volley-sequence",
|
|
284
|
+
"kind": "code",
|
|
285
|
+
"category": null,
|
|
286
|
+
"tags": [],
|
|
287
|
+
"media": {},
|
|
288
|
+
"path": "packages/effects/pulse-laser-volley-sequence"
|
|
289
|
+
},
|
|
280
290
|
{
|
|
281
291
|
"id": "resource-collect-sparkles",
|
|
282
292
|
"dir": "resource-collect-sparkles",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fps-games/vfx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
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": {
|