@fps-games/vfx 0.1.1 → 0.3.2
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 +107 -0
- package/README.md +41 -0
- package/dist/effects/coin-burst/index.js +6 -2
- package/dist/effects/comet-trail-flame/index.js +3 -1
- package/dist/effects/debris-smoke/index.js +1 -0
- package/dist/effects/electric-arc/index.js +1 -0
- package/dist/effects/energy-shield/index.js +1 -0
- package/dist/effects/explosion/index.js +3 -2
- package/dist/effects/hit-flash/index.js +12 -4
- package/dist/effects/laser-beam/index.js +14 -2
- package/dist/effects/lumber_vehicle_upgrade_aura/index.js +1 -0
- package/dist/effects/monster-hit/index.js +2 -1
- package/dist/effects/player-upgrade/index.js +5 -3
- package/dist/effects/pulse-laser-volley/index.js +14 -4
- package/dist/effects/thruster-flame/index.js +3 -1
- package/dist/effects/treasure-attention/index.d.ts +54 -0
- package/dist/effects/treasure-attention/index.js +535 -0
- package/dist/effects/ufo-thread-flame/index.js +1 -0
- package/dist/effects/vehicle-upgrade/index.js +4 -1
- package/dist/effects/weapon-trail/index.js +8 -3
- package/dist/effects/wood_crusher_sawdust/index.d.ts +4 -0
- package/dist/effects/wood_cutter_sawdust/index.d.ts +4 -0
- package/dist/index.js +85 -17
- package/dist/types.d.ts +80 -0
- package/docs/INTEGRATION.md +194 -0
- package/docs/hit-flash-red-method.md +155 -0
- package/docs/treasure_attention_fx_babylonjs_guide.md +901 -0
- package/docs/vfx-placement-design.md +164 -0
- package/index.json +19 -1
- package/package.json +5 -1
- package/packages/EffectPackageService.ts +123 -17
- package/packages/effects/coin-burst/index.ts +7 -2
- package/packages/effects/comet-trail-flame/index.ts +3 -2
- package/packages/effects/debris-smoke/index.ts +1 -0
- package/packages/effects/electric-arc/index.ts +1 -0
- package/packages/effects/energy-shield/index.ts +1 -0
- package/packages/effects/explosion/index.ts +3 -2
- package/packages/effects/hit-flash/index.ts +11 -3
- package/packages/effects/laser-beam-factory.ts +14 -3
- package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +2 -0
- package/packages/effects/monster-hit/index.ts +2 -1
- package/packages/effects/player-upgrade/index.ts +8 -3
- package/packages/effects/pulse-laser-volley/index.ts +15 -4
- package/packages/effects/thruster-flame/index.ts +3 -2
- package/packages/effects/treasure-attention/effect.json +7 -0
- package/packages/effects/treasure-attention/index.ts +630 -0
- package/packages/effects/treasure-attention/vfx-params.json +49 -0
- package/packages/effects/ufo-thread-flame/index.ts +1 -0
- package/packages/effects/vehicle-upgrade/index.ts +4 -1
- package/packages/effects/weapon-trail/index.ts +7 -3
- package/packages/types.ts +88 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { Constants } from "@babylonjs/core/Engines/constants";
|
|
2
|
+
import { GlowLayer } from "@babylonjs/core/Layers/glowLayer";
|
|
3
|
+
import { Material } from "@babylonjs/core/Materials/material";
|
|
4
|
+
import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
|
|
5
|
+
import { DynamicTexture } from "@babylonjs/core/Materials/Textures/dynamicTexture";
|
|
6
|
+
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
|
|
7
|
+
import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
|
|
8
|
+
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
|
|
9
|
+
import { Mesh } from "@babylonjs/core/Meshes/mesh";
|
|
10
|
+
import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData";
|
|
11
|
+
import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder";
|
|
12
|
+
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
|
|
13
|
+
import { ParticleSystem } from "@babylonjs/core/Particles/particleSystem";
|
|
14
|
+
import { V as VFX_RENDERING_GROUP_ID, c as configureVfxParticleSystem } from "../../chunks/rendering-CV3nhygX.js";
|
|
15
|
+
const DEFAULT_PARAMS = {
|
|
16
|
+
radius: 1.15,
|
|
17
|
+
yOffset: 0.05,
|
|
18
|
+
height: 1.35,
|
|
19
|
+
rays: 9,
|
|
20
|
+
rayWidth: 0.34,
|
|
21
|
+
spinSpeed: 0.85,
|
|
22
|
+
color: { r: 1, g: 0.9, b: 0.22 },
|
|
23
|
+
auraColor: { r: 1, g: 0.82, b: 0.18 },
|
|
24
|
+
ringColor: { r: 1, g: 0.95, b: 0.38 },
|
|
25
|
+
rayColor: { r: 1, g: 0.76, b: 0.12 },
|
|
26
|
+
beamColor: { r: 1, g: 0.92, b: 0.32 },
|
|
27
|
+
opacity: 1,
|
|
28
|
+
auraOpacity: 0.82,
|
|
29
|
+
ringOpacity: 0.95,
|
|
30
|
+
beamOpacity: 0.68,
|
|
31
|
+
particleRate: 42,
|
|
32
|
+
particleLife: 0.82,
|
|
33
|
+
sparkleMinSize: 0.045,
|
|
34
|
+
sparkleMaxSize: 0.13,
|
|
35
|
+
riseSpeed: 0.72,
|
|
36
|
+
drift: 0.12,
|
|
37
|
+
bloomIntensity: 0.55,
|
|
38
|
+
bloomKernelSize: 32
|
|
39
|
+
};
|
|
40
|
+
const treasureAttentionEffectPackage = {
|
|
41
|
+
id: "treasure-attention",
|
|
42
|
+
nameZh: "宝藏注意力光效",
|
|
43
|
+
placement: "socket",
|
|
44
|
+
// 附着在宝藏上的注意力标记:geometry 挂 fxRoot 子节点承 yOffset,root 交给 service(挂载/位置由 service 管)
|
|
45
|
+
defaultParams: DEFAULT_PARAMS,
|
|
46
|
+
debugAnchor: "playerRight",
|
|
47
|
+
manifest: {
|
|
48
|
+
id: "treasure-attention",
|
|
49
|
+
nameZh: "宝藏注意力光效",
|
|
50
|
+
source: "docs/treasure_attention_fx_babylonjs_guide.md"
|
|
51
|
+
},
|
|
52
|
+
debugParams: [
|
|
53
|
+
{ key: "radius", label: "光效半径", kind: "scale", min: 0.35, max: 3.5, step: 0.01 },
|
|
54
|
+
{ key: "yOffset", label: "离地高度", kind: "number", min: -0.1, max: 0.4, step: 5e-3 },
|
|
55
|
+
{ key: "height", label: "竖光高度", kind: "scale", min: 0.2, max: 3.5, step: 0.01 },
|
|
56
|
+
{ key: "rays", label: "星芒数量", kind: "number", min: 0, max: 18, step: 1 },
|
|
57
|
+
{ key: "rayWidth", label: "星芒宽度", kind: "scale", min: 0.05, max: 1, step: 0.01 },
|
|
58
|
+
{ key: "spinSpeed", label: "旋转速度", kind: "number", min: -3, max: 3, step: 0.01 },
|
|
59
|
+
{ key: "color", label: "上浮星星颜色", kind: "color" },
|
|
60
|
+
{ key: "auraColor", label: "地面柔光颜色", kind: "color" },
|
|
61
|
+
{ key: "ringColor", label: "光环颜色", kind: "color" },
|
|
62
|
+
{ key: "rayColor", label: "旋转星芒颜色", kind: "color" },
|
|
63
|
+
{ key: "beamColor", label: "竖向光柱颜色", kind: "color" },
|
|
64
|
+
{ key: "opacity", label: "整体透明度", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
65
|
+
{ key: "auraOpacity", label: "地面柔光", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
66
|
+
{ key: "ringOpacity", label: "光环亮度", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
67
|
+
{ key: "beamOpacity", label: "竖光亮度", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
68
|
+
{ key: "particleRate", label: "闪片频率", kind: "number", min: 0, max: 120, step: 1 },
|
|
69
|
+
{ key: "particleLife", label: "闪片寿命", kind: "lifetime", min: 0.2, max: 2.5, step: 0.01 },
|
|
70
|
+
{ key: "sparkleMinSize", label: "闪片最小", kind: "scale", min: 0.01, max: 0.5, step: 5e-3 },
|
|
71
|
+
{ key: "sparkleMaxSize", label: "闪片最大", kind: "scale", min: 0.01, max: 0.8, step: 5e-3 },
|
|
72
|
+
{ key: "riseSpeed", label: "上浮速度", kind: "number", min: 0, max: 2.5, step: 0.01 },
|
|
73
|
+
{ key: "drift", label: "横向漂移", kind: "number", min: 0, max: 1.2, step: 0.01 },
|
|
74
|
+
{ key: "bloomIntensity", label: "Bloom 强度", kind: "number", min: 0, max: 2.5, step: 0.01 },
|
|
75
|
+
{ key: "bloomKernelSize", label: "Bloom 半径", kind: "number", min: 8, max: 96, step: 1 }
|
|
76
|
+
],
|
|
77
|
+
create({ scene, root, position, renderingGroupId, params }) {
|
|
78
|
+
return createTreasureAttentionFx(scene, { root, position, renderingGroupId, params, serviceOwnedRoot: true });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
function createTreasureAttentionFx(scene, options = {}) {
|
|
82
|
+
let params = mergeParams(options.params);
|
|
83
|
+
const root = options.root ?? new TransformNode("vfx_treasureAttention_root", scene);
|
|
84
|
+
const disposeRoot = options.disposeRoot ?? true;
|
|
85
|
+
const renderingGroupId = options.renderingGroupId ?? VFX_RENDERING_GROUP_ID;
|
|
86
|
+
const sharedGlowLayer = options.glowLayer ?? null;
|
|
87
|
+
let target = options.target ?? null;
|
|
88
|
+
const serviceOwnedRoot = options.serviceOwnedRoot ?? false;
|
|
89
|
+
const basePosition = (options.position ?? root.position).clone();
|
|
90
|
+
const fxRoot = new TransformNode("vfx_treasureAttention_fxRoot", scene);
|
|
91
|
+
fxRoot.parent = root;
|
|
92
|
+
let enabled = true;
|
|
93
|
+
let disposed = false;
|
|
94
|
+
let elapsed = 0;
|
|
95
|
+
let raySignature = "";
|
|
96
|
+
const textures = /* @__PURE__ */ new Set();
|
|
97
|
+
const materials = /* @__PURE__ */ new Set();
|
|
98
|
+
const meshes = /* @__PURE__ */ new Set();
|
|
99
|
+
let rayMeshes = [];
|
|
100
|
+
let glowLayer = null;
|
|
101
|
+
let ownsGlowLayer = false;
|
|
102
|
+
const raysRoot = new TransformNode("vfx_treasureAttention_raysRoot", scene);
|
|
103
|
+
raysRoot.parent = fxRoot;
|
|
104
|
+
const auraTex = makeTexture(scene, textures, "vfx_treasureAttention_auraTex", 256, (ctx, size) => {
|
|
105
|
+
drawAuraTexture(ctx, size, DEFAULT_PARAMS.auraColor);
|
|
106
|
+
});
|
|
107
|
+
const ringTex = makeTexture(scene, textures, "vfx_treasureAttention_ringTex", 256, (ctx, size) => {
|
|
108
|
+
drawRingTexture(ctx, size, DEFAULT_PARAMS.ringColor);
|
|
109
|
+
});
|
|
110
|
+
const rayTex = makeTexture(scene, textures, "vfx_treasureAttention_rayTex", 256, (ctx, size) => {
|
|
111
|
+
drawRayTexture(ctx, size, DEFAULT_PARAMS.rayColor);
|
|
112
|
+
});
|
|
113
|
+
const beamTex = makeTexture(scene, textures, "vfx_treasureAttention_beamTex", 256, (ctx, size) => {
|
|
114
|
+
drawBeamTexture(ctx, size, DEFAULT_PARAMS.beamColor);
|
|
115
|
+
});
|
|
116
|
+
const sparkleTex = makeTexture(scene, textures, "vfx_treasureAttention_sparkleTex", 128, drawSparkleTexture);
|
|
117
|
+
const auraMat = makeAdditiveMaterial(scene, materials, "vfx_treasureAttention_auraMat", auraTex);
|
|
118
|
+
const ringMat = makeAdditiveMaterial(scene, materials, "vfx_treasureAttention_ringMat", ringTex);
|
|
119
|
+
const rayMat = makeAdditiveMaterial(scene, materials, "vfx_treasureAttention_rayMat", rayTex);
|
|
120
|
+
const beamMat = makeAdditiveMaterial(scene, materials, "vfx_treasureAttention_beamMat", beamTex);
|
|
121
|
+
const aura = addMesh(MeshBuilder.CreatePlane("vfx_treasureAttention_aura", { size: 1 }, scene));
|
|
122
|
+
aura.rotation.x = Math.PI / 2;
|
|
123
|
+
aura.position.y = 0.025;
|
|
124
|
+
aura.material = auraMat;
|
|
125
|
+
aura.parent = fxRoot;
|
|
126
|
+
aura.alphaIndex = 0;
|
|
127
|
+
const ring = addMesh(MeshBuilder.CreatePlane("vfx_treasureAttention_ring", { size: 1 }, scene));
|
|
128
|
+
ring.rotation.x = Math.PI / 2;
|
|
129
|
+
ring.position.y = 0.04;
|
|
130
|
+
ring.material = ringMat;
|
|
131
|
+
ring.parent = fxRoot;
|
|
132
|
+
ring.alphaIndex = 1;
|
|
133
|
+
const beam = addMesh(MeshBuilder.CreatePlane("vfx_treasureAttention_beam", { size: 1 }, scene));
|
|
134
|
+
beam.position.y = params.height * 0.52;
|
|
135
|
+
beam.billboardMode = Mesh.BILLBOARDMODE_Y;
|
|
136
|
+
beam.material = beamMat;
|
|
137
|
+
beam.parent = fxRoot;
|
|
138
|
+
beam.alphaIndex = 3;
|
|
139
|
+
const particleEmitter = addMesh(new Mesh("vfx_treasureAttention_particleEmitter", scene), false);
|
|
140
|
+
particleEmitter.isVisible = false;
|
|
141
|
+
particleEmitter.parent = fxRoot;
|
|
142
|
+
const particleSystem = new ParticleSystem("vfx_treasureAttention_sparkles", 180, scene);
|
|
143
|
+
configureVfxParticleSystem(particleSystem);
|
|
144
|
+
particleSystem.particleTexture = sparkleTex;
|
|
145
|
+
particleSystem.emitter = particleEmitter;
|
|
146
|
+
particleSystem.blendMode = ParticleSystem.BLENDMODE_ADD;
|
|
147
|
+
particleSystem.updateSpeed = 1 / 60;
|
|
148
|
+
particleSystem.minAngularSpeed = -4;
|
|
149
|
+
particleSystem.maxAngularSpeed = 4;
|
|
150
|
+
particleSystem.gravity = new Vector3(0, -0.04, 0);
|
|
151
|
+
particleSystem.start();
|
|
152
|
+
applyParams();
|
|
153
|
+
syncRoot();
|
|
154
|
+
const observer = scene.onBeforeRenderObservable.add(() => {
|
|
155
|
+
if (disposed || !enabled) return;
|
|
156
|
+
const dt = Math.min(scene.getEngine().getDeltaTime() / 1e3, 0.05);
|
|
157
|
+
elapsed += dt;
|
|
158
|
+
syncRoot();
|
|
159
|
+
animate(dt);
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
root,
|
|
163
|
+
particleSystem,
|
|
164
|
+
update(partial) {
|
|
165
|
+
if (disposed) return;
|
|
166
|
+
params = mergeParams(partial, params);
|
|
167
|
+
applyParams();
|
|
168
|
+
syncRoot();
|
|
169
|
+
},
|
|
170
|
+
setEnabled(nextEnabled) {
|
|
171
|
+
enabled = nextEnabled;
|
|
172
|
+
root.setEnabled(nextEnabled);
|
|
173
|
+
if (nextEnabled) {
|
|
174
|
+
particleSystem.start();
|
|
175
|
+
} else {
|
|
176
|
+
particleSystem.stop();
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
setTarget(nextTarget) {
|
|
180
|
+
target = nextTarget;
|
|
181
|
+
syncRoot();
|
|
182
|
+
},
|
|
183
|
+
setPosition(position) {
|
|
184
|
+
basePosition.copyFrom(position);
|
|
185
|
+
syncRoot();
|
|
186
|
+
},
|
|
187
|
+
dispose() {
|
|
188
|
+
disposeAll();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
function addMesh(mesh, includeInGlow = true) {
|
|
192
|
+
mesh.isPickable = false;
|
|
193
|
+
mesh.alwaysSelectAsActiveMesh = true;
|
|
194
|
+
mesh.renderingGroupId = renderingGroupId;
|
|
195
|
+
meshes.add(mesh);
|
|
196
|
+
if (includeInGlow) addGlowMesh(mesh);
|
|
197
|
+
return mesh;
|
|
198
|
+
}
|
|
199
|
+
function addGlowMesh(mesh) {
|
|
200
|
+
if (!glowLayer || !mesh.material) return;
|
|
201
|
+
glowLayer.addIncludedOnlyMesh(mesh);
|
|
202
|
+
glowLayer.referenceMeshToUseItsOwnMaterial(mesh);
|
|
203
|
+
}
|
|
204
|
+
function removeGlowMesh(mesh) {
|
|
205
|
+
glowLayer?.removeIncludedOnlyMesh(mesh);
|
|
206
|
+
}
|
|
207
|
+
function applyParams() {
|
|
208
|
+
redrawTexture(auraTex, 256, (ctx, size) => drawAuraTexture(ctx, size, params.auraColor));
|
|
209
|
+
redrawTexture(ringTex, 256, (ctx, size) => drawRingTexture(ctx, size, params.ringColor));
|
|
210
|
+
redrawTexture(rayTex, 256, (ctx, size) => drawRayTexture(ctx, size, params.rayColor));
|
|
211
|
+
redrawTexture(beamTex, 256, (ctx, size) => drawBeamTexture(ctx, size, params.beamColor));
|
|
212
|
+
auraMat.alpha = clamp(params.opacity * params.auraOpacity, 0, 1);
|
|
213
|
+
ringMat.alpha = clamp(params.opacity * params.ringOpacity, 0, 1);
|
|
214
|
+
rayMat.alpha = clamp(params.opacity * 0.9, 0, 1);
|
|
215
|
+
beamMat.alpha = clamp(params.opacity * params.beamOpacity, 0, 1);
|
|
216
|
+
fxRoot.position.y = params.yOffset;
|
|
217
|
+
aura.position.y = 0.015;
|
|
218
|
+
ring.position.y = aura.position.y + 0.018;
|
|
219
|
+
beam.position.y = params.height * 0.52;
|
|
220
|
+
beam.scaling.set(Math.max(0.02, params.radius * 1.05), Math.max(0.02, params.height), 1);
|
|
221
|
+
const particleLife = Math.max(0.04, params.particleLife);
|
|
222
|
+
const minSize = Math.max(1e-3, Math.min(params.sparkleMinSize, params.sparkleMaxSize));
|
|
223
|
+
const maxSize = Math.max(minSize, Math.max(params.sparkleMinSize, params.sparkleMaxSize));
|
|
224
|
+
particleSystem.emitRate = Math.max(0, params.particleRate);
|
|
225
|
+
particleSystem.minLifeTime = particleLife * 0.75;
|
|
226
|
+
particleSystem.maxLifeTime = particleLife * 1.2;
|
|
227
|
+
particleSystem.minSize = minSize;
|
|
228
|
+
particleSystem.maxSize = maxSize;
|
|
229
|
+
particleSystem.minEmitBox = new Vector3(-params.radius * 0.45, 0.15, -params.radius * 0.45);
|
|
230
|
+
particleSystem.maxEmitBox = new Vector3(params.radius * 0.45, 0.55, params.radius * 0.45);
|
|
231
|
+
particleSystem.direction1 = new Vector3(-params.drift, params.riseSpeed * 0.76, -params.drift);
|
|
232
|
+
particleSystem.direction2 = new Vector3(params.drift, params.riseSpeed * 1.24, params.drift);
|
|
233
|
+
particleSystem.minEmitPower = Math.max(0.01, params.riseSpeed * 0.28);
|
|
234
|
+
particleSystem.maxEmitPower = Math.max(particleSystem.minEmitPower, params.riseSpeed * 0.58);
|
|
235
|
+
particleSystem.color1 = tintColor(params.color, 1.15, 0.9 * params.opacity);
|
|
236
|
+
particleSystem.color2 = tintColor(params.color, 1.55, 0.76 * params.opacity);
|
|
237
|
+
particleSystem.colorDead = tintColor(params.color, 0.8, 0);
|
|
238
|
+
rebuildRaysIfNeeded();
|
|
239
|
+
syncGlowLayer();
|
|
240
|
+
}
|
|
241
|
+
function rebuildRaysIfNeeded() {
|
|
242
|
+
const rayCount = clamp(Math.round(params.rays), 0, 32);
|
|
243
|
+
const signature = `${rayCount}:${params.radius.toFixed(3)}:${params.rayWidth.toFixed(3)}`;
|
|
244
|
+
if (signature === raySignature) return;
|
|
245
|
+
raySignature = signature;
|
|
246
|
+
for (const ray of rayMeshes) {
|
|
247
|
+
removeGlowMesh(ray);
|
|
248
|
+
meshes.delete(ray);
|
|
249
|
+
ray.dispose(false, false);
|
|
250
|
+
}
|
|
251
|
+
rayMeshes = [];
|
|
252
|
+
for (let i = 0; i < rayCount; i += 1) {
|
|
253
|
+
const angle = i / Math.max(1, rayCount) * Math.PI * 2;
|
|
254
|
+
const length = params.radius * (0.95 + i % 3 * 0.13);
|
|
255
|
+
const width = params.rayWidth * (0.8 + i % 2 * 0.22);
|
|
256
|
+
const ray = createRayMesh(scene, `vfx_treasureAttention_ray_${i}`, length, width);
|
|
257
|
+
ray.parent = raysRoot;
|
|
258
|
+
ray.rotation.y = angle;
|
|
259
|
+
ray.position.y = ring.position.y + 0.014 + i * 1e-3;
|
|
260
|
+
ray.material = rayMat;
|
|
261
|
+
ray.alphaIndex = 2;
|
|
262
|
+
addMesh(ray);
|
|
263
|
+
rayMeshes.push(ray);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function syncGlowLayer() {
|
|
267
|
+
const shouldGlow = params.bloomIntensity > 0;
|
|
268
|
+
if (!shouldGlow) {
|
|
269
|
+
if (glowLayer) {
|
|
270
|
+
for (const mesh of meshes) removeGlowMesh(mesh);
|
|
271
|
+
}
|
|
272
|
+
if (ownsGlowLayer) glowLayer?.dispose();
|
|
273
|
+
glowLayer = null;
|
|
274
|
+
ownsGlowLayer = false;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (!glowLayer) {
|
|
278
|
+
if (sharedGlowLayer) {
|
|
279
|
+
glowLayer = sharedGlowLayer;
|
|
280
|
+
} else {
|
|
281
|
+
glowLayer = new GlowLayer(`vfx_treasureAttention_glow_${root.uniqueId}`, scene, {
|
|
282
|
+
blurKernelSize: params.bloomKernelSize,
|
|
283
|
+
mainTextureRatio: 0.4
|
|
284
|
+
});
|
|
285
|
+
ownsGlowLayer = true;
|
|
286
|
+
}
|
|
287
|
+
for (const mesh of meshes) addGlowMesh(mesh);
|
|
288
|
+
}
|
|
289
|
+
if (ownsGlowLayer && glowLayer) {
|
|
290
|
+
glowLayer.intensity = params.bloomIntensity;
|
|
291
|
+
glowLayer.blurKernelSize = params.bloomKernelSize;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function syncRoot() {
|
|
295
|
+
if (serviceOwnedRoot) return;
|
|
296
|
+
if (target) {
|
|
297
|
+
root.position.copyFrom(target.getAbsolutePosition());
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
root.position.copyFrom(basePosition);
|
|
301
|
+
}
|
|
302
|
+
function animate(dt) {
|
|
303
|
+
const pulse = 0.5 + 0.5 * Math.sin(elapsed * 5.2);
|
|
304
|
+
const auraSize = params.radius * 2.55 * (0.96 + pulse * 0.11);
|
|
305
|
+
const ringSize = params.radius * 2.15 * (0.94 + pulse * 0.08);
|
|
306
|
+
aura.scaling.set(auraSize, auraSize, 1);
|
|
307
|
+
aura.visibility = clamp((0.72 + pulse * 0.24) * params.opacity * params.auraOpacity, 0, 1);
|
|
308
|
+
ring.rotation.z += dt * 0.65;
|
|
309
|
+
ring.scaling.set(ringSize, ringSize, 1);
|
|
310
|
+
ring.visibility = clamp((0.55 + pulse * 0.36) * params.opacity * params.ringOpacity, 0, 1);
|
|
311
|
+
beam.scaling.x = Math.max(0.02, params.radius * 1.05) * (0.85 + pulse * 0.28);
|
|
312
|
+
beam.scaling.y = Math.max(0.02, params.height);
|
|
313
|
+
beam.visibility = clamp((0.35 + pulse * 0.28) * params.opacity * params.beamOpacity, 0, 1);
|
|
314
|
+
raysRoot.rotation.y += dt * params.spinSpeed;
|
|
315
|
+
for (let i = 0; i < rayMeshes.length; i += 1) {
|
|
316
|
+
const q = 0.5 + 0.5 * Math.sin(elapsed * 4.4 + i * 0.9);
|
|
317
|
+
const ray = rayMeshes[i];
|
|
318
|
+
ray.visibility = clamp((0.42 + q * 0.42) * params.opacity, 0, 1);
|
|
319
|
+
ray.scaling.x = 0.92 + q * 0.18;
|
|
320
|
+
ray.scaling.z = 0.82 + q * 0.35;
|
|
321
|
+
}
|
|
322
|
+
if (ownsGlowLayer && glowLayer) {
|
|
323
|
+
glowLayer.intensity = params.bloomIntensity * (0.75 + pulse * 0.35);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function disposeAll() {
|
|
327
|
+
if (disposed) return;
|
|
328
|
+
disposed = true;
|
|
329
|
+
scene.onBeforeRenderObservable.remove(observer);
|
|
330
|
+
particleSystem.stop();
|
|
331
|
+
particleSystem.dispose(false);
|
|
332
|
+
if (glowLayer) {
|
|
333
|
+
for (const mesh of meshes) removeGlowMesh(mesh);
|
|
334
|
+
}
|
|
335
|
+
if (ownsGlowLayer) glowLayer?.dispose();
|
|
336
|
+
for (const mesh of meshes) {
|
|
337
|
+
mesh.dispose(false, false);
|
|
338
|
+
}
|
|
339
|
+
raysRoot.dispose();
|
|
340
|
+
for (const material of materials) material.dispose();
|
|
341
|
+
for (const texture of textures) texture.dispose();
|
|
342
|
+
if (disposeRoot) root.dispose();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function makeTexture(scene, textures, name, size, draw) {
|
|
346
|
+
const texture = new DynamicTexture(name, { width: size, height: size }, scene, false);
|
|
347
|
+
texture.hasAlpha = true;
|
|
348
|
+
texture.wrapU = Texture.CLAMP_ADDRESSMODE;
|
|
349
|
+
texture.wrapV = Texture.CLAMP_ADDRESSMODE;
|
|
350
|
+
const context = texture.getContext();
|
|
351
|
+
context.clearRect(0, 0, size, size);
|
|
352
|
+
draw(context, size);
|
|
353
|
+
texture.update(false);
|
|
354
|
+
textures.add(texture);
|
|
355
|
+
return texture;
|
|
356
|
+
}
|
|
357
|
+
function makeAdditiveMaterial(scene, materials, name, texture) {
|
|
358
|
+
const material = new StandardMaterial(name, scene);
|
|
359
|
+
material.diffuseTexture = texture;
|
|
360
|
+
material.emissiveTexture = texture;
|
|
361
|
+
material.diffuseColor = Color3.White();
|
|
362
|
+
material.emissiveColor = Color3.White();
|
|
363
|
+
material.specularColor = Color3.Black();
|
|
364
|
+
material.disableLighting = true;
|
|
365
|
+
material.backFaceCulling = false;
|
|
366
|
+
material.useAlphaFromDiffuseTexture = true;
|
|
367
|
+
material.transparencyMode = Material.MATERIAL_ALPHABLEND;
|
|
368
|
+
material.alphaMode = Constants.ALPHA_ADD;
|
|
369
|
+
material.disableDepthWrite = true;
|
|
370
|
+
materials.add(material);
|
|
371
|
+
return material;
|
|
372
|
+
}
|
|
373
|
+
function createRayMesh(scene, name, length, width) {
|
|
374
|
+
const mesh = new Mesh(name, scene);
|
|
375
|
+
const positions = [
|
|
376
|
+
0,
|
|
377
|
+
0,
|
|
378
|
+
-width * 0.5,
|
|
379
|
+
length,
|
|
380
|
+
0,
|
|
381
|
+
-width * 0.12,
|
|
382
|
+
length,
|
|
383
|
+
0,
|
|
384
|
+
width * 0.12,
|
|
385
|
+
0,
|
|
386
|
+
0,
|
|
387
|
+
width * 0.5
|
|
388
|
+
];
|
|
389
|
+
const vertexData = new VertexData();
|
|
390
|
+
vertexData.positions = positions;
|
|
391
|
+
vertexData.indices = [0, 1, 2, 0, 2, 3];
|
|
392
|
+
vertexData.normals = [
|
|
393
|
+
0,
|
|
394
|
+
1,
|
|
395
|
+
0,
|
|
396
|
+
0,
|
|
397
|
+
1,
|
|
398
|
+
0,
|
|
399
|
+
0,
|
|
400
|
+
1,
|
|
401
|
+
0,
|
|
402
|
+
0,
|
|
403
|
+
1,
|
|
404
|
+
0
|
|
405
|
+
];
|
|
406
|
+
vertexData.uvs = [0, 0, 1, 0, 1, 1, 0, 1];
|
|
407
|
+
vertexData.applyToMesh(mesh, false);
|
|
408
|
+
return mesh;
|
|
409
|
+
}
|
|
410
|
+
function redrawTexture(texture, size, draw) {
|
|
411
|
+
const context = texture.getContext();
|
|
412
|
+
context.clearRect(0, 0, size, size);
|
|
413
|
+
draw(context, size);
|
|
414
|
+
texture.update(false);
|
|
415
|
+
}
|
|
416
|
+
function drawAuraTexture(ctx, size, color) {
|
|
417
|
+
const center = size / 2;
|
|
418
|
+
const gradient = ctx.createRadialGradient(center, center, 0, center, center, center);
|
|
419
|
+
gradient.addColorStop(0, colorRgba(color, 0.86));
|
|
420
|
+
gradient.addColorStop(0.25, colorRgba(color, 0.46));
|
|
421
|
+
gradient.addColorStop(0.55, colorRgba(color, 0.16));
|
|
422
|
+
gradient.addColorStop(1, colorRgba(color, 0));
|
|
423
|
+
ctx.fillStyle = gradient;
|
|
424
|
+
ctx.fillRect(0, 0, size, size);
|
|
425
|
+
}
|
|
426
|
+
function drawRingTexture(ctx, size, color) {
|
|
427
|
+
const center = size / 2;
|
|
428
|
+
ctx.shadowColor = colorRgba(color, 0.9);
|
|
429
|
+
ctx.shadowBlur = size * 0.05;
|
|
430
|
+
ctx.strokeStyle = colorRgba(color, 0.78);
|
|
431
|
+
ctx.lineWidth = size * 0.035;
|
|
432
|
+
ctx.beginPath();
|
|
433
|
+
ctx.arc(center, center, size * 0.31, 0, Math.PI * 2);
|
|
434
|
+
ctx.stroke();
|
|
435
|
+
ctx.lineWidth = size * 0.012;
|
|
436
|
+
ctx.strokeStyle = colorRgba(lightenColor(color, 0.58), 0.85);
|
|
437
|
+
ctx.beginPath();
|
|
438
|
+
ctx.arc(center, center, size * 0.42, 0, Math.PI * 2);
|
|
439
|
+
ctx.stroke();
|
|
440
|
+
}
|
|
441
|
+
function drawRayTexture(ctx, size, color) {
|
|
442
|
+
const gradient = ctx.createLinearGradient(0, 0, size, 0);
|
|
443
|
+
gradient.addColorStop(0, colorRgba(color, 0));
|
|
444
|
+
gradient.addColorStop(0.18, colorRgba(lightenColor(color, 0.35), 0.72));
|
|
445
|
+
gradient.addColorStop(0.55, colorRgba(color, 0.34));
|
|
446
|
+
gradient.addColorStop(1, colorRgba(color, 0));
|
|
447
|
+
ctx.fillStyle = gradient;
|
|
448
|
+
ctx.beginPath();
|
|
449
|
+
ctx.moveTo(0, size * 0.5);
|
|
450
|
+
ctx.quadraticCurveTo(size * 0.45, size * 0.1, size, size * 0.18);
|
|
451
|
+
ctx.lineTo(size, size * 0.82);
|
|
452
|
+
ctx.quadraticCurveTo(size * 0.45, size * 0.9, 0, size * 0.5);
|
|
453
|
+
ctx.closePath();
|
|
454
|
+
ctx.fill();
|
|
455
|
+
}
|
|
456
|
+
function drawBeamTexture(ctx, size, color) {
|
|
457
|
+
const center = size / 2;
|
|
458
|
+
const radial = ctx.createRadialGradient(center, center, 0, center, center, center * 0.55);
|
|
459
|
+
radial.addColorStop(0, colorRgba(lightenColor(color, 0.65), 0.75));
|
|
460
|
+
radial.addColorStop(0.28, colorRgba(color, 0.35));
|
|
461
|
+
radial.addColorStop(1, colorRgba(color, 0));
|
|
462
|
+
ctx.fillStyle = radial;
|
|
463
|
+
ctx.fillRect(0, 0, size, size);
|
|
464
|
+
const vertical = ctx.createLinearGradient(0, 0, 0, size);
|
|
465
|
+
vertical.addColorStop(0, colorRgba(color, 0));
|
|
466
|
+
vertical.addColorStop(0.42, colorRgba(lightenColor(color, 0.42), 0.42));
|
|
467
|
+
vertical.addColorStop(0.62, colorRgba(lightenColor(color, 0.42), 0.42));
|
|
468
|
+
vertical.addColorStop(1, colorRgba(color, 0));
|
|
469
|
+
ctx.fillStyle = vertical;
|
|
470
|
+
ctx.fillRect(size * 0.43, 0, size * 0.14, size);
|
|
471
|
+
}
|
|
472
|
+
function drawSparkleTexture(ctx, size) {
|
|
473
|
+
const center = size / 2;
|
|
474
|
+
ctx.save();
|
|
475
|
+
ctx.translate(center, center);
|
|
476
|
+
ctx.shadowColor = "rgba(255,255,255,1)";
|
|
477
|
+
ctx.shadowBlur = size * 0.12;
|
|
478
|
+
ctx.fillStyle = "rgba(255,255,255,0.95)";
|
|
479
|
+
ctx.beginPath();
|
|
480
|
+
for (let i = 0; i < 8; i += 1) {
|
|
481
|
+
const angle = i * Math.PI / 4;
|
|
482
|
+
const radius = i % 2 === 0 ? size * 0.42 : size * 0.12;
|
|
483
|
+
const x = Math.cos(angle) * radius;
|
|
484
|
+
const y = Math.sin(angle) * radius;
|
|
485
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
486
|
+
else ctx.lineTo(x, y);
|
|
487
|
+
}
|
|
488
|
+
ctx.closePath();
|
|
489
|
+
ctx.fill();
|
|
490
|
+
ctx.restore();
|
|
491
|
+
}
|
|
492
|
+
function mergeParams(patch, base = DEFAULT_PARAMS) {
|
|
493
|
+
return {
|
|
494
|
+
...base,
|
|
495
|
+
...patch,
|
|
496
|
+
color: copyColor(patch?.color, base.color),
|
|
497
|
+
auraColor: copyColor(patch?.auraColor, base.auraColor),
|
|
498
|
+
ringColor: copyColor(patch?.ringColor, base.ringColor),
|
|
499
|
+
rayColor: copyColor(patch?.rayColor, base.rayColor),
|
|
500
|
+
beamColor: copyColor(patch?.beamColor, base.beamColor)
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function copyColor(value, fallback) {
|
|
504
|
+
return value ? { ...value } : { ...fallback };
|
|
505
|
+
}
|
|
506
|
+
function tintColor(color, brightness, alpha) {
|
|
507
|
+
return new Color4(
|
|
508
|
+
Math.max(0, color.r * brightness),
|
|
509
|
+
Math.max(0, color.g * brightness),
|
|
510
|
+
Math.max(0, color.b * brightness),
|
|
511
|
+
clamp(alpha, 0, 1)
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
function colorRgba(color, alpha) {
|
|
515
|
+
const r = Math.round(clamp(color.r, 0, 1) * 255);
|
|
516
|
+
const g = Math.round(clamp(color.g, 0, 1) * 255);
|
|
517
|
+
const b = Math.round(clamp(color.b, 0, 1) * 255);
|
|
518
|
+
return `rgba(${r},${g},${b},${clamp(alpha, 0, 1)})`;
|
|
519
|
+
}
|
|
520
|
+
function lightenColor(color, amount) {
|
|
521
|
+
const t = clamp(amount, 0, 1);
|
|
522
|
+
return {
|
|
523
|
+
r: color.r + (1 - color.r) * t,
|
|
524
|
+
g: color.g + (1 - color.g) * t,
|
|
525
|
+
b: color.b + (1 - color.b) * t
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function clamp(value, min, max) {
|
|
529
|
+
return Math.min(max, Math.max(min, value));
|
|
530
|
+
}
|
|
531
|
+
export {
|
|
532
|
+
createTreasureAttentionFx,
|
|
533
|
+
treasureAttentionEffectPackage as default,
|
|
534
|
+
treasureAttentionEffectPackage
|
|
535
|
+
};
|
|
@@ -38,8 +38,11 @@ const DEFAULT_PARAMS = {
|
|
|
38
38
|
const vehicleUpgradeEffectPackage = {
|
|
39
39
|
id: "vehicle-upgrade",
|
|
40
40
|
nameZh: "建筑&地块&设备升级",
|
|
41
|
+
placement: "socket",
|
|
41
42
|
manifest,
|
|
42
43
|
defaultParams: DEFAULT_PARAMS,
|
|
44
|
+
// initialHeight 走 service 存活的 Y 偏移通道(G6/M12)。
|
|
45
|
+
spawnParams: { offsetY: "initialHeight" },
|
|
43
46
|
debugAnchor: "playerRight",
|
|
44
47
|
debugParams: [
|
|
45
48
|
{ key: "lifetime", label: "生命周期", kind: "lifetime", min: 0.2, max: 6, step: 0.01 },
|
|
@@ -59,7 +62,7 @@ const vehicleUpgradeEffectPackage = {
|
|
|
59
62
|
{ key: "glowOpacity", label: "光幕强度", kind: "opacity", min: 0, max: 1, step: 0.01 }
|
|
60
63
|
],
|
|
61
64
|
create({ scene, root, position, params }) {
|
|
62
|
-
root.position.copyFrom(position)
|
|
65
|
+
root.position.copyFrom(position);
|
|
63
66
|
const centerStrands = createStrandSystem(scene, root, params, "center");
|
|
64
67
|
const outlineStrands = createStrandSystem(scene, root, params, "outline");
|
|
65
68
|
const glow = createOutlineGlow(scene, root, params);
|
|
@@ -14,9 +14,10 @@ const trailTextureUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAP4AAABNC
|
|
|
14
14
|
const DEFAULT_PARAMS = {
|
|
15
15
|
...LIFETIME_DEFAULTS,
|
|
16
16
|
orientation: "horizontal",
|
|
17
|
-
orbitRadius:
|
|
17
|
+
orbitRadius: 0,
|
|
18
18
|
orbitSpeed: 7,
|
|
19
19
|
height: 0,
|
|
20
|
+
// 默认 0:真实跟随挂点(orbit>0 是演示用的幻影圆周轨道)
|
|
20
21
|
trailLength: 16,
|
|
21
22
|
worldLength: 4,
|
|
22
23
|
width: 1.01,
|
|
@@ -90,6 +91,8 @@ function lerpSample(a, b, t, dst) {
|
|
|
90
91
|
const weaponTrailEffectPackage = {
|
|
91
92
|
id: "weapon-trail",
|
|
92
93
|
nameZh: "武器拖尾",
|
|
94
|
+
placement: "socket",
|
|
95
|
+
geometrySpace: "world",
|
|
93
96
|
debugAnchor: "player",
|
|
94
97
|
defaultParams: DEFAULT_PARAMS,
|
|
95
98
|
debugParams: [
|
|
@@ -120,8 +123,9 @@ const weaponTrailEffectPackage = {
|
|
|
120
123
|
{ key: "sparkRate", label: "刃头火花/秒", kind: "number", min: 0, max: 80, step: 1 },
|
|
121
124
|
{ key: "tint", label: "颜色", kind: "color" }
|
|
122
125
|
],
|
|
123
|
-
create({ scene, root, position, params }) {
|
|
126
|
+
create({ scene, root, position, params, renderingGroupId }) {
|
|
124
127
|
root.position.copyFrom(position);
|
|
128
|
+
const rgid = renderingGroupId ?? 0;
|
|
125
129
|
registerShaders();
|
|
126
130
|
const blend = params.blendMode === "combine" ? Constants.ALPHA_COMBINE : Constants.ALPHA_ADD;
|
|
127
131
|
const tex = new Texture(trailTextureUrl, scene, true, false);
|
|
@@ -162,7 +166,7 @@ const weaponTrailEffectPackage = {
|
|
|
162
166
|
m.alwaysSelectAsActiveMesh = true;
|
|
163
167
|
m.useVertexColors = true;
|
|
164
168
|
m.hasVertexAlpha = true;
|
|
165
|
-
m.renderingGroupId =
|
|
169
|
+
m.renderingGroupId = rgid;
|
|
166
170
|
m.alphaIndex = ai;
|
|
167
171
|
return m;
|
|
168
172
|
}
|
|
@@ -185,6 +189,7 @@ const weaponTrailEffectPackage = {
|
|
|
185
189
|
const c255 = `${Math.round(params.tint.r * 255)},${Math.round(params.tint.g * 255)},${Math.round(params.tint.b * 255)}`;
|
|
186
190
|
const flare = makeFlareTexture(scene, "vfx_weaponTrail_sparkTex", "255,255,255", c255);
|
|
187
191
|
sparkPs = new ParticleSystem("vfx_weaponTrail_sparks", 160, scene);
|
|
192
|
+
sparkPs.renderingGroupId = rgid;
|
|
188
193
|
sparkPs.particleTexture = flare;
|
|
189
194
|
sparkPs.emitter = sparkEmitter;
|
|
190
195
|
sparkPs.minSize = 0.07;
|
|
@@ -12,5 +12,9 @@ export declare const woodCrusherSawdustEffectPackage: {
|
|
|
12
12
|
spawnParams?: import('../..').VfxSpawnParamKeys;
|
|
13
13
|
savedParams?: Partial<import('../..').VfxParamValues>;
|
|
14
14
|
debugParams: import('../..').VfxParamDefinition[];
|
|
15
|
+
placement?: import('../..').VfxPlacement;
|
|
16
|
+
requiredInputs?: import('../..').VfxInputKey[];
|
|
17
|
+
optionalInputs?: import('../..').VfxInputKey[];
|
|
18
|
+
geometrySpace?: "local" | "world";
|
|
15
19
|
create(context: import('../..').VfxEffectPackageContext<import('../..').VfxParamValues>): import('../..').VfxEffectHandle;
|
|
16
20
|
};
|
|
@@ -12,5 +12,9 @@ export declare const woodCutterSawdustEffectPackage: {
|
|
|
12
12
|
spawnParams?: import('../..').VfxSpawnParamKeys;
|
|
13
13
|
savedParams?: Partial<import('../..').VfxParamValues>;
|
|
14
14
|
debugParams: import('../..').VfxParamDefinition[];
|
|
15
|
+
placement?: import('../..').VfxPlacement;
|
|
16
|
+
requiredInputs?: import('../..').VfxInputKey[];
|
|
17
|
+
optionalInputs?: import('../..').VfxInputKey[];
|
|
18
|
+
geometrySpace?: "local" | "world";
|
|
15
19
|
create(context: import('../..').VfxEffectPackageContext<import('../..').VfxParamValues>): import('../..').VfxEffectHandle;
|
|
16
20
|
};
|