@fps-games/vfx 0.3.11 → 0.3.14
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/dist/effects/golden-bubble-pop/effect.json.d.ts +7 -0
- package/dist/effects/golden-bubble-pop/index.d.ts +25 -0
- package/dist/effects/golden-bubble-pop/index.js +323 -0
- package/dist/effects/magnet-suction/index.js +83 -101
- package/dist/effects/player-upgrade/index.js +69 -99
- package/index.json +11 -1
- package/package.json +1 -1
- package/packages/effects/golden-bubble-pop/vfx-params.json +5 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
|
|
2
|
+
interface GoldenBubblePopParams extends VfxParamValues {
|
|
3
|
+
lifetime: number;
|
|
4
|
+
emitRate: number;
|
|
5
|
+
spawnRadius: number;
|
|
6
|
+
spawnHeight: number;
|
|
7
|
+
spawnHeightRandom: number;
|
|
8
|
+
bubbleMinSize: number;
|
|
9
|
+
bubbleMaxSize: number;
|
|
10
|
+
growTime: number;
|
|
11
|
+
growCurve: number;
|
|
12
|
+
wobbleAmount: number;
|
|
13
|
+
wobbleSpeed: number;
|
|
14
|
+
popSparkCount: number;
|
|
15
|
+
popRadius: number;
|
|
16
|
+
popDuration: number;
|
|
17
|
+
popDropletSize: number;
|
|
18
|
+
popGravity: number;
|
|
19
|
+
popFlashStrength: number;
|
|
20
|
+
color: VfxColorValue;
|
|
21
|
+
opacity: number;
|
|
22
|
+
glowStrength: number;
|
|
23
|
+
}
|
|
24
|
+
export declare const goldenBubblePopEffectPackage: VfxEffectPackage<GoldenBubblePopParams>;
|
|
25
|
+
export default goldenBubblePopEffectPackage;
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { Constants } from "@babylonjs/core/Engines/constants";
|
|
2
|
+
import { Material } from "@babylonjs/core/Materials/material";
|
|
3
|
+
import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
|
|
4
|
+
import { DynamicTexture } from "@babylonjs/core/Materials/Textures/dynamicTexture";
|
|
5
|
+
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
|
|
6
|
+
import { Color3 } from "@babylonjs/core/Maths/math.color";
|
|
7
|
+
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
|
|
8
|
+
import { Mesh } from "@babylonjs/core/Meshes/mesh";
|
|
9
|
+
import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder";
|
|
10
|
+
import { V as VFX_RENDERING_GROUP_ID } from "../../chunks/rendering-CV3nhygX.js";
|
|
11
|
+
const id = "golden-bubble-pop";
|
|
12
|
+
const nameZh = "金色泡泡炸开";
|
|
13
|
+
const manifest = {
|
|
14
|
+
id,
|
|
15
|
+
nameZh
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_PARAMS = {
|
|
18
|
+
lifetime: 2.5,
|
|
19
|
+
emitRate: 8,
|
|
20
|
+
spawnRadius: 0.6,
|
|
21
|
+
spawnHeight: 0,
|
|
22
|
+
spawnHeightRandom: 0,
|
|
23
|
+
bubbleMinSize: 0.05,
|
|
24
|
+
bubbleMaxSize: 0.42,
|
|
25
|
+
growTime: 0.75,
|
|
26
|
+
growCurve: 1.35,
|
|
27
|
+
wobbleAmount: 0.025,
|
|
28
|
+
wobbleSpeed: 3.2,
|
|
29
|
+
popSparkCount: 5,
|
|
30
|
+
popRadius: 0.38,
|
|
31
|
+
popDuration: 0.42,
|
|
32
|
+
popDropletSize: 0.12,
|
|
33
|
+
popGravity: 3.2,
|
|
34
|
+
popFlashStrength: 0.75,
|
|
35
|
+
color: { r: 1, g: 0.78, b: 0.18 },
|
|
36
|
+
opacity: 0.72,
|
|
37
|
+
glowStrength: 0.9
|
|
38
|
+
};
|
|
39
|
+
const goldenBubblePopEffectPackage = {
|
|
40
|
+
id: "golden-bubble-pop",
|
|
41
|
+
nameZh: "金色泡泡炸开",
|
|
42
|
+
placement: "socket",
|
|
43
|
+
geometrySpace: "local",
|
|
44
|
+
debugAnchor: "playerRight",
|
|
45
|
+
manifest,
|
|
46
|
+
defaultParams: DEFAULT_PARAMS,
|
|
47
|
+
debugParams: [
|
|
48
|
+
{ key: "lifetime", label: "生成时长", kind: "lifetime", min: 0.1, max: 8, step: 0.01 },
|
|
49
|
+
{ key: "emitRate", label: "生成速度", kind: "number", min: 0, max: 40, step: 0.1 },
|
|
50
|
+
{ key: "spawnRadius", label: "生成半径", kind: "scale", min: 0, max: 4, step: 0.01 },
|
|
51
|
+
{ key: "spawnHeight", label: "生成平面高度", kind: "scale", min: -2, max: 2, step: 0.01 },
|
|
52
|
+
{ key: "spawnHeightRandom", label: "生成高度随机", kind: "scale", min: 0, max: 2, step: 0.01 },
|
|
53
|
+
{ key: "bubbleMinSize", label: "泡泡初始大小", kind: "scale", min: 5e-3, max: 1.5, step: 5e-3 },
|
|
54
|
+
{ key: "bubbleMaxSize", label: "泡泡最大大小", kind: "scale", min: 0.02, max: 2.5, step: 0.01 },
|
|
55
|
+
{ key: "growTime", label: "泡泡生长时间", kind: "lifetime", min: 0.05, max: 3, step: 0.01 },
|
|
56
|
+
{ key: "growCurve", label: "生长曲线", kind: "number", min: 0.3, max: 3, step: 0.01 },
|
|
57
|
+
{ key: "wobbleAmount", label: "原地微抖", kind: "scale", min: 0, max: 0.3, step: 1e-3 },
|
|
58
|
+
{ key: "wobbleSpeed", label: "微抖速度", kind: "number", min: 0, max: 12, step: 0.05 },
|
|
59
|
+
{ key: "popSparkCount", label: "炸开碎光数量", kind: "number", min: 0, max: 20, step: 1 },
|
|
60
|
+
{ key: "popRadius", label: "碎光飞散范围", kind: "scale", min: 0, max: 2.5, step: 0.01 },
|
|
61
|
+
{ key: "popDuration", label: "碎光淡出时长", kind: "lifetime", min: 0.05, max: 1.5, step: 0.01 },
|
|
62
|
+
{ key: "popDropletSize", label: "碎光大小", kind: "scale", min: 0.01, max: 1, step: 5e-3 },
|
|
63
|
+
{ key: "popGravity", label: "碎光重力", kind: "number", min: 0, max: 12, step: 0.05 },
|
|
64
|
+
{ key: "popFlashStrength", label: "破裂亮度", kind: "number", min: 0, max: 2, step: 0.01 },
|
|
65
|
+
{ key: "color", label: "泡泡颜色", kind: "color" },
|
|
66
|
+
{ key: "opacity", label: "透明度", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
67
|
+
{ key: "glowStrength", label: "发光强度", kind: "number", min: 0.1, max: 5, step: 0.05 }
|
|
68
|
+
],
|
|
69
|
+
create({ scene, root, params, renderingGroupId }) {
|
|
70
|
+
const textures = [
|
|
71
|
+
makeTexture(scene, "vfx_goldenBubblePop_bubbleTex", 128, (context, size) => drawBubbleTexture(context, size, params.color)),
|
|
72
|
+
makeTexture(scene, "vfx_goldenBubblePop_sparkTex", 64, (context, size) => drawSparkTexture(context, size, params.color))
|
|
73
|
+
];
|
|
74
|
+
const bubbleMat = makeMaterial(scene, "vfx_goldenBubblePop_bubbleMat", textures[0], params.opacity, params.glowStrength);
|
|
75
|
+
const dropletMat = makeMaterial(scene, "vfx_goldenBubblePop_dropletMat", textures[1], params.opacity, params.glowStrength * params.popFlashStrength);
|
|
76
|
+
const materials = [bubbleMat, dropletMat];
|
|
77
|
+
const bubbles = [];
|
|
78
|
+
const pops = [];
|
|
79
|
+
const groupId = renderingGroupId ?? VFX_RENDERING_GROUP_ID;
|
|
80
|
+
const growTime = Math.max(0.03, params.growTime);
|
|
81
|
+
const emitDuration = Math.max(0, params.lifetime);
|
|
82
|
+
const disposeAfter = emitDuration + growTime + Math.max(0.05, params.popDuration) + 0.2;
|
|
83
|
+
let elapsed = 0;
|
|
84
|
+
let emitCarry = 0;
|
|
85
|
+
let disposed = false;
|
|
86
|
+
const observer = scene.onBeforeRenderObservable.add(() => {
|
|
87
|
+
const dt = Math.min(0.05, scene.getEngine().getDeltaTime() / 1e3);
|
|
88
|
+
elapsed += dt;
|
|
89
|
+
if (elapsed <= emitDuration) {
|
|
90
|
+
emitCarry += Math.max(0, params.emitRate) * dt;
|
|
91
|
+
const emitCount = Math.floor(emitCarry);
|
|
92
|
+
emitCarry -= emitCount;
|
|
93
|
+
for (let i = 0; i < emitCount; i += 1) spawnBubble();
|
|
94
|
+
}
|
|
95
|
+
updateBubbles(dt);
|
|
96
|
+
updatePops(dt);
|
|
97
|
+
if (elapsed > disposeAfter && bubbles.length === 0 && pops.length === 0) disposeAll();
|
|
98
|
+
});
|
|
99
|
+
function spawnBubble() {
|
|
100
|
+
const maxSize = randomRange(
|
|
101
|
+
Math.min(params.bubbleMinSize, params.bubbleMaxSize),
|
|
102
|
+
Math.max(params.bubbleMinSize, params.bubbleMaxSize)
|
|
103
|
+
);
|
|
104
|
+
const mesh = MeshBuilder.CreatePlane("vfx_goldenBubblePop_bubble", { size: 1 }, scene);
|
|
105
|
+
mesh.parent = root;
|
|
106
|
+
mesh.billboardMode = Mesh.BILLBOARDMODE_ALL;
|
|
107
|
+
mesh.material = bubbleMat;
|
|
108
|
+
mesh.renderingGroupId = groupId;
|
|
109
|
+
mesh.isPickable = false;
|
|
110
|
+
mesh.alphaIndex = 20;
|
|
111
|
+
const position = sampleSpawnPosition(params.spawnRadius, params.spawnHeight, params.spawnHeightRandom);
|
|
112
|
+
mesh.position.copyFrom(position);
|
|
113
|
+
mesh.scaling.setAll(Math.max(1e-3, params.bubbleMinSize));
|
|
114
|
+
bubbles.push({
|
|
115
|
+
mesh,
|
|
116
|
+
age: 0,
|
|
117
|
+
lifetime: growTime,
|
|
118
|
+
seed: Math.random() * Math.PI * 2,
|
|
119
|
+
basePosition: position,
|
|
120
|
+
maxSize
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function updateBubbles(dt) {
|
|
124
|
+
for (let i = bubbles.length - 1; i >= 0; i -= 1) {
|
|
125
|
+
const bubble = bubbles[i];
|
|
126
|
+
bubble.age += dt;
|
|
127
|
+
const t = clamp01(bubble.age / bubble.lifetime);
|
|
128
|
+
const eased = Math.pow(t, Math.max(0.05, params.growCurve));
|
|
129
|
+
const size = lerp(Math.max(1e-3, params.bubbleMinSize), bubble.maxSize, eased);
|
|
130
|
+
const pulse = 1 + Math.sin((bubble.age * params.wobbleSpeed + bubble.seed) * 1.7) * 0.035;
|
|
131
|
+
const wobble = Math.max(0, params.wobbleAmount);
|
|
132
|
+
bubble.mesh.position.set(
|
|
133
|
+
bubble.basePosition.x + Math.sin(bubble.age * params.wobbleSpeed + bubble.seed) * wobble,
|
|
134
|
+
bubble.basePosition.y,
|
|
135
|
+
bubble.basePosition.z + Math.cos(bubble.age * params.wobbleSpeed + bubble.seed * 1.37) * wobble
|
|
136
|
+
);
|
|
137
|
+
bubble.mesh.scaling.setAll(size * pulse);
|
|
138
|
+
bubbleMat.alpha = params.opacity * (0.34 + 0.66 * (1 - t * 0.24));
|
|
139
|
+
if (t >= 1) {
|
|
140
|
+
spawnPop(bubble.mesh.position, bubble.maxSize);
|
|
141
|
+
bubble.mesh.dispose();
|
|
142
|
+
bubbles.splice(i, 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function spawnPop(position, bubbleSize) {
|
|
147
|
+
const duration = Math.max(0.03, params.popDuration);
|
|
148
|
+
const dropletCount = Math.max(0, Math.round(params.popSparkCount));
|
|
149
|
+
const droplets = [];
|
|
150
|
+
const radius = Math.max(0, params.popRadius);
|
|
151
|
+
for (let i = 0; i < dropletCount; i += 1) {
|
|
152
|
+
const droplet = MeshBuilder.CreatePlane("vfx_goldenBubblePop_droplet", { size: 1 }, scene);
|
|
153
|
+
droplet.parent = root;
|
|
154
|
+
droplet.billboardMode = Mesh.BILLBOARDMODE_ALL;
|
|
155
|
+
droplet.material = dropletMat;
|
|
156
|
+
droplet.renderingGroupId = groupId;
|
|
157
|
+
droplet.isPickable = false;
|
|
158
|
+
droplet.position.copyFrom(position);
|
|
159
|
+
droplet.alphaIndex = 31;
|
|
160
|
+
const direction = sampleBurstDirection(i, dropletCount);
|
|
161
|
+
const travel = radius * randomRange(0.62, 1.1);
|
|
162
|
+
droplets.push({
|
|
163
|
+
mesh: droplet,
|
|
164
|
+
velocity: new Vector3(
|
|
165
|
+
direction.x * travel / duration,
|
|
166
|
+
randomRange(0.06, 0.18) + Math.max(0, direction.y) * travel / duration,
|
|
167
|
+
direction.z * travel / duration
|
|
168
|
+
),
|
|
169
|
+
size: Math.max(1e-3, params.popDropletSize) * randomRange(0.72, 1.32) + bubbleSize * 0.05,
|
|
170
|
+
seed: Math.random() * Math.PI * 2
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
pops.push({
|
|
174
|
+
origin: position.clone(),
|
|
175
|
+
droplets,
|
|
176
|
+
age: 0,
|
|
177
|
+
duration
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function updatePops(dt) {
|
|
181
|
+
for (let i = pops.length - 1; i >= 0; i -= 1) {
|
|
182
|
+
const pop = pops[i];
|
|
183
|
+
pop.age += dt;
|
|
184
|
+
const t = clamp01(pop.age / pop.duration);
|
|
185
|
+
const fade = 1 - smoothstep(0.48, 1, t);
|
|
186
|
+
const appear = smoothstep(0, 0.18, t);
|
|
187
|
+
dropletMat.alpha = params.opacity * params.popFlashStrength * fade;
|
|
188
|
+
for (const droplet of pop.droplets) {
|
|
189
|
+
const gravity = Math.max(0, params.popGravity);
|
|
190
|
+
droplet.mesh.position.set(
|
|
191
|
+
pop.origin.x + droplet.velocity.x * pop.age,
|
|
192
|
+
pop.origin.y + droplet.velocity.y * pop.age - 0.5 * gravity * pop.age * pop.age,
|
|
193
|
+
pop.origin.z + droplet.velocity.z * pop.age
|
|
194
|
+
);
|
|
195
|
+
droplet.mesh.scaling.setAll(droplet.size * appear * (1 - t * 0.35));
|
|
196
|
+
droplet.mesh.rotation.z = droplet.seed + pop.age * 6;
|
|
197
|
+
}
|
|
198
|
+
if (t >= 1) {
|
|
199
|
+
for (const droplet of pop.droplets) droplet.mesh.dispose();
|
|
200
|
+
pops.splice(i, 1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function disposeAll() {
|
|
205
|
+
if (disposed) return;
|
|
206
|
+
disposed = true;
|
|
207
|
+
if (observer) scene.onBeforeRenderObservable.remove(observer);
|
|
208
|
+
for (const bubble of bubbles) bubble.mesh.dispose();
|
|
209
|
+
for (const pop of pops) {
|
|
210
|
+
for (const droplet of pop.droplets) droplet.mesh.dispose();
|
|
211
|
+
}
|
|
212
|
+
for (const material of materials) material.dispose();
|
|
213
|
+
for (const texture of textures) texture.dispose();
|
|
214
|
+
root.dispose();
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
root,
|
|
218
|
+
dispose() {
|
|
219
|
+
disposeAll();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
function makeMaterial(scene, name, texture, opacity, glowStrength) {
|
|
225
|
+
const material = new StandardMaterial(name, scene);
|
|
226
|
+
const brightness = Math.max(0.1, glowStrength);
|
|
227
|
+
material.diffuseTexture = texture;
|
|
228
|
+
material.emissiveTexture = texture;
|
|
229
|
+
material.diffuseColor = Color3.White();
|
|
230
|
+
material.emissiveColor = Color3.White().scale(brightness);
|
|
231
|
+
material.specularColor = Color3.Black();
|
|
232
|
+
material.alpha = opacity;
|
|
233
|
+
material.disableLighting = true;
|
|
234
|
+
material.backFaceCulling = false;
|
|
235
|
+
material.useAlphaFromDiffuseTexture = true;
|
|
236
|
+
material.transparencyMode = Material.MATERIAL_ALPHABLEND;
|
|
237
|
+
material.alphaMode = Constants.ALPHA_ADD;
|
|
238
|
+
material.disableDepthWrite = true;
|
|
239
|
+
return material;
|
|
240
|
+
}
|
|
241
|
+
function makeTexture(scene, name, size, draw) {
|
|
242
|
+
const texture = new DynamicTexture(name, { width: size, height: size }, scene, true);
|
|
243
|
+
texture.wrapU = Texture.CLAMP_ADDRESSMODE;
|
|
244
|
+
texture.wrapV = Texture.CLAMP_ADDRESSMODE;
|
|
245
|
+
const context = texture.getContext();
|
|
246
|
+
context.clearRect(0, 0, size, size);
|
|
247
|
+
draw(context, size);
|
|
248
|
+
texture.update(false);
|
|
249
|
+
return texture;
|
|
250
|
+
}
|
|
251
|
+
function drawBubbleTexture(context, size, color) {
|
|
252
|
+
const center = size * 0.5;
|
|
253
|
+
const radius = size * 0.42;
|
|
254
|
+
const fill = context.createRadialGradient(center, center, radius * 0.08, center, center, radius);
|
|
255
|
+
fill.addColorStop(0, colorRgba(color, 0.1, 1.18));
|
|
256
|
+
fill.addColorStop(0.68, colorRgba(color, 0.06, 1.08));
|
|
257
|
+
fill.addColorStop(0.82, colorRgba(color, 0.68, 1.18));
|
|
258
|
+
fill.addColorStop(0.95, colorRgba(color, 0.9, 1.28));
|
|
259
|
+
fill.addColorStop(1, colorRgba(color, 0, 1.28));
|
|
260
|
+
context.fillStyle = fill;
|
|
261
|
+
context.beginPath();
|
|
262
|
+
context.arc(center, center, radius, 0, Math.PI * 2);
|
|
263
|
+
context.fill();
|
|
264
|
+
const highlight = context.createRadialGradient(size * 0.34, size * 0.3, 1, size * 0.34, size * 0.3, size * 0.16);
|
|
265
|
+
highlight.addColorStop(0, colorRgba(color, 0.72, 1.6));
|
|
266
|
+
highlight.addColorStop(0.55, colorRgba(color, 0.28, 1.4));
|
|
267
|
+
highlight.addColorStop(1, colorRgba(color, 0, 1.4));
|
|
268
|
+
context.fillStyle = highlight;
|
|
269
|
+
context.beginPath();
|
|
270
|
+
context.arc(size * 0.34, size * 0.3, size * 0.16, 0, Math.PI * 2);
|
|
271
|
+
context.fill();
|
|
272
|
+
}
|
|
273
|
+
function drawSparkTexture(context, size, color) {
|
|
274
|
+
const center = size * 0.5;
|
|
275
|
+
const spark = context.createRadialGradient(center, center, 1, center, center, size * 0.45);
|
|
276
|
+
spark.addColorStop(0, colorRgba(color, 0.98, 1.5));
|
|
277
|
+
spark.addColorStop(0.34, colorRgba(color, 0.82, 1.22));
|
|
278
|
+
spark.addColorStop(0.78, colorRgba(color, 0.32, 1));
|
|
279
|
+
spark.addColorStop(1, colorRgba(color, 0, 1));
|
|
280
|
+
context.fillStyle = spark;
|
|
281
|
+
context.beginPath();
|
|
282
|
+
context.arc(center, center, size * 0.45, 0, Math.PI * 2);
|
|
283
|
+
context.fill();
|
|
284
|
+
}
|
|
285
|
+
function sampleSpawnPosition(radius, height, heightRandom) {
|
|
286
|
+
const r = Math.sqrt(Math.random()) * Math.max(0, radius);
|
|
287
|
+
const a = Math.random() * Math.PI * 2;
|
|
288
|
+
return new Vector3(
|
|
289
|
+
Math.cos(a) * r,
|
|
290
|
+
height + (Math.random() - 0.5) * Math.max(0, heightRandom),
|
|
291
|
+
Math.sin(a) * r
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
function sampleBurstDirection(index, count) {
|
|
295
|
+
const angle = (index + Math.random() * 0.35) / Math.max(1, count) * Math.PI * 2;
|
|
296
|
+
const y = (Math.random() - 0.5) * 0.36;
|
|
297
|
+
const direction = new Vector3(Math.cos(angle), y, Math.sin(angle));
|
|
298
|
+
direction.normalize();
|
|
299
|
+
return direction;
|
|
300
|
+
}
|
|
301
|
+
function randomRange(min, max) {
|
|
302
|
+
return min + Math.random() * (max - min);
|
|
303
|
+
}
|
|
304
|
+
function lerp(a, b, t) {
|
|
305
|
+
return a + (b - a) * t;
|
|
306
|
+
}
|
|
307
|
+
function clamp01(value) {
|
|
308
|
+
return Math.max(0, Math.min(1, value));
|
|
309
|
+
}
|
|
310
|
+
function smoothstep(edge0, edge1, value) {
|
|
311
|
+
const t = clamp01((value - edge0) / (edge1 - edge0));
|
|
312
|
+
return t * t * (3 - 2 * t);
|
|
313
|
+
}
|
|
314
|
+
function colorRgba(color, alpha, boost) {
|
|
315
|
+
const r = Math.round(clamp01(color.r * boost) * 255);
|
|
316
|
+
const g = Math.round(clamp01(color.g * boost) * 255);
|
|
317
|
+
const b = Math.round(clamp01(color.b * boost) * 255);
|
|
318
|
+
return `rgba(${r}, ${g}, ${b}, ${clamp01(alpha)})`;
|
|
319
|
+
}
|
|
320
|
+
export {
|
|
321
|
+
goldenBubblePopEffectPackage as default,
|
|
322
|
+
goldenBubblePopEffectPackage
|
|
323
|
+
};
|
|
@@ -20,12 +20,13 @@ const manifest = {
|
|
|
20
20
|
assets,
|
|
21
21
|
source
|
|
22
22
|
};
|
|
23
|
+
const BOLT_SEGMENTS = 6;
|
|
23
24
|
const DEFAULT_PARAMS = {
|
|
24
25
|
lifetimeMode: "infinite",
|
|
25
26
|
lifetime: 2.5,
|
|
26
27
|
scale: 1.15,
|
|
27
28
|
initialHeight: 0,
|
|
28
|
-
color: { r: 0.
|
|
29
|
+
color: { r: 0.48, g: 0.9, b: 1 },
|
|
29
30
|
opacity: 1,
|
|
30
31
|
intensity: 1,
|
|
31
32
|
forwardOffset: 0,
|
|
@@ -37,18 +38,18 @@ const DEFAULT_PARAMS = {
|
|
|
37
38
|
fadeDistance: 0.58,
|
|
38
39
|
spawnFade: 0.45,
|
|
39
40
|
suctionSpeed: 1.65,
|
|
40
|
-
lineCount:
|
|
41
|
+
lineCount: 0,
|
|
41
42
|
lineLength: 0.46,
|
|
42
|
-
lineWidth: 0.
|
|
43
|
+
lineWidth: 0.052,
|
|
43
44
|
lineOpacity: 1,
|
|
44
45
|
lineSpread: 1,
|
|
45
46
|
lineTwist: 0,
|
|
46
|
-
particleCount:
|
|
47
|
-
particleSize: 0.
|
|
48
|
-
particleSpeed:
|
|
47
|
+
particleCount: 5,
|
|
48
|
+
particleSize: 0.036,
|
|
49
|
+
particleSpeed: 1.65,
|
|
49
50
|
particleDrift: 0,
|
|
50
51
|
particleOuterOffset: 0,
|
|
51
|
-
particleOpacity: 0.
|
|
52
|
+
particleOpacity: 0.68,
|
|
52
53
|
directionX: 0,
|
|
53
54
|
directionZ: -1
|
|
54
55
|
};
|
|
@@ -79,16 +80,14 @@ const magnetSuctionEffectPackage = {
|
|
|
79
80
|
{ key: "mouthPadding", label: "入口保护距离", kind: "number", min: 0.01, max: 2, step: 0.01 },
|
|
80
81
|
{ key: "fadeDistance", label: "入口淡出距离", kind: "number", min: 0.01, max: 3, step: 0.01 },
|
|
81
82
|
{ key: "spawnFade", label: "外侧淡入距离", kind: "number", min: 0.01, max: 3, step: 0.01 },
|
|
82
|
-
{ key: "
|
|
83
|
-
{ key: "
|
|
84
|
-
{ key: "
|
|
85
|
-
{ key: "lineWidth", label: "白线尾宽", kind: "number", min: 2e-3, max: 0.18, step: 1e-3 },
|
|
86
|
-
{ key: "lineOpacity", label: "白线透明度", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
83
|
+
{ key: "lineLength", label: "电弧长度", kind: "number", min: 0.02, max: 2, step: 0.01 },
|
|
84
|
+
{ key: "lineWidth", label: "电弧宽度", kind: "number", min: 2e-3, max: 0.18, step: 1e-3 },
|
|
85
|
+
{ key: "lineOpacity", label: "电弧透明度", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
87
86
|
{ key: "lineSpread", label: "散布缩放", kind: "number", min: 0.1, max: 2, step: 0.01 },
|
|
88
|
-
{ key: "particleCount", label: "
|
|
89
|
-
{ key: "particleSize", label: "
|
|
90
|
-
{ key: "particleSpeed", label: "
|
|
91
|
-
{ key: "particleOpacity", label: "
|
|
87
|
+
{ key: "particleCount", label: "电弧数量", kind: "number", min: 0, max: 24, step: 1 },
|
|
88
|
+
{ key: "particleSize", label: "电弧抖动大小", kind: "number", min: 5e-3, max: 0.3, step: 1e-3 },
|
|
89
|
+
{ key: "particleSpeed", label: "电弧吸入速度", kind: "number", min: 0, max: 8, step: 0.01 },
|
|
90
|
+
{ key: "particleOpacity", label: "电弧闪烁强度", kind: "opacity", min: 0, max: 1, step: 0.01 },
|
|
92
91
|
{ key: "directionX", label: "吸入方向 X", kind: "number", min: -1, max: 1, step: 0.01 },
|
|
93
92
|
{ key: "directionZ", label: "吸入方向 Z", kind: "number", min: -1, max: 1, step: 0.01 }
|
|
94
93
|
],
|
|
@@ -139,26 +138,15 @@ function createMagnetSuctionField(scene, root, params, renderingGroupId) {
|
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const particleVertexCount = particleCount * 4;
|
|
146
|
-
const totalVertexCount = lineVertexCount + particleVertexCount;
|
|
141
|
+
const boltCount = Math.max(0, Math.round(params.particleCount));
|
|
142
|
+
const boltVertexCount = boltCount * BOLT_SEGMENTS * 4;
|
|
143
|
+
const totalVertexCount = boltVertexCount;
|
|
147
144
|
const positions = new Float32Array(totalVertexCount * 3);
|
|
148
145
|
const colors = new Float32Array(totalVertexCount * 4);
|
|
149
146
|
const indices = [];
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
for (let index = 0; index < lineCount; index += 1) {
|
|
154
|
-
const base = index * 3;
|
|
155
|
-
indices.push(base, base + 1, base + 2);
|
|
156
|
-
lines.push(resetLine({}, true));
|
|
157
|
-
}
|
|
158
|
-
for (let index = 0; index < particleCount; index += 1) {
|
|
159
|
-
const base = lineVertexCount + index * 4;
|
|
160
|
-
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
161
|
-
particles.push(resetParticle({}, true));
|
|
147
|
+
let time = 0;
|
|
148
|
+
for (let base = 0; base < totalVertexCount; base += 4) {
|
|
149
|
+
pushQuadIndices(indices, base);
|
|
162
150
|
}
|
|
163
151
|
const mesh = new Mesh("magnet-suction.mesh", scene);
|
|
164
152
|
mesh.parent = root;
|
|
@@ -176,15 +164,7 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
|
176
164
|
writeGeometry();
|
|
177
165
|
return { update, dispose };
|
|
178
166
|
function update(deltaSeconds) {
|
|
179
|
-
|
|
180
|
-
for (const line of lines) {
|
|
181
|
-
line.d -= line.speed * deltaSeconds;
|
|
182
|
-
if (line.d < basis.mouthPadding) resetLine(line, false);
|
|
183
|
-
}
|
|
184
|
-
for (const particle of particles) {
|
|
185
|
-
particle.d -= particle.speed * deltaSeconds;
|
|
186
|
-
if (particle.d < basis.mouthPadding) resetParticle(particle, false);
|
|
187
|
-
}
|
|
167
|
+
time += deltaSeconds;
|
|
188
168
|
writeGeometry();
|
|
189
169
|
}
|
|
190
170
|
function dispose() {
|
|
@@ -193,10 +173,12 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
|
193
173
|
function writeGeometry() {
|
|
194
174
|
const basis = createSuctionBasis(params);
|
|
195
175
|
const cameraBasis = createCameraBasis(scene, root, basis);
|
|
196
|
-
const
|
|
176
|
+
const suctionColor = toColor3(params.color);
|
|
177
|
+
const electric = new Color3(0.82, 0.98, 1);
|
|
178
|
+
const boltBlue = new Color3(0.25, 0.62, 1);
|
|
197
179
|
const alphaScale = clamp01(params.opacity) * Math.max(0, params.intensity);
|
|
198
180
|
let vertexIndex = 0;
|
|
199
|
-
const putVertex = (position, alpha, brightness = 1) => {
|
|
181
|
+
const putVertex = (position, color, alpha, brightness = 1) => {
|
|
200
182
|
const positionOffset = vertexIndex * 3;
|
|
201
183
|
const colorOffset = vertexIndex * 4;
|
|
202
184
|
positions[positionOffset] = position.x;
|
|
@@ -208,61 +190,67 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
|
208
190
|
colors[colorOffset + 3] = clamp01(alpha * alphaScale);
|
|
209
191
|
vertexIndex += 1;
|
|
210
192
|
};
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
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);
|
|
193
|
+
const putQuad = (a, b, c, d, color, alpha, brightness = 1) => {
|
|
194
|
+
putVertex(a, color, alpha, brightness);
|
|
195
|
+
putVertex(b, color, alpha, brightness);
|
|
196
|
+
putVertex(c, color, alpha, brightness);
|
|
197
|
+
putVertex(d, color, alpha, brightness);
|
|
198
|
+
};
|
|
199
|
+
const putRibbon = (a, b, width, color, alpha, brightness = 1) => {
|
|
200
|
+
const segment = b.subtract(a);
|
|
201
|
+
let side = Vector3.Cross(segment, basis.axis);
|
|
202
|
+
if (side.lengthSquared() < 1e-4) side = cameraBasis.ribbonSide.clone();
|
|
203
|
+
side.normalize();
|
|
204
|
+
const half = side.scale(width * 0.5);
|
|
205
|
+
putQuad(a.subtract(half), a.add(half), b.add(half), b.subtract(half), color, alpha, brightness);
|
|
206
|
+
};
|
|
207
|
+
writeBolts(basis, electric, boltBlue, putRibbon);
|
|
208
|
+
while (vertexIndex < totalVertexCount) {
|
|
209
|
+
putVertex(Vector3.Zero(), suctionColor, 0);
|
|
233
210
|
}
|
|
234
211
|
mesh.updateVerticesData(VertexBuffer.PositionKind, positions, false, false);
|
|
235
212
|
mesh.updateVerticesData(VertexBuffer.ColorKind, colors, false, false);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
213
|
+
function writeBolts(currentBasis, boltColor, edgeColor, writeRibbon) {
|
|
214
|
+
for (let bolt = 0; bolt < boltCount; bolt += 1) {
|
|
215
|
+
const seed = sequence01(bolt + 7, bolt % 8);
|
|
216
|
+
const speedScale = Math.max(0, params.particleSpeed) / DEFAULT_PARAMS.particleSpeed;
|
|
217
|
+
const cycle = (time * speedScale * (1.45 + seed * 0.55) + seed) % 1;
|
|
218
|
+
const flicker = sequence01(bolt * 17 + Math.floor(time * 20), bolt % 8);
|
|
219
|
+
const active = flicker > 0.28 ? 1 : 0.18;
|
|
220
|
+
const angle = bolt * 2.399 + time * (1.7 + seed * 0.9);
|
|
221
|
+
const lengthScale = Math.max(0.04, params.lineLength / DEFAULT_PARAMS.lineLength);
|
|
222
|
+
const boltLength = lerp(currentBasis.length * 0.16, currentBasis.length * 0.28, seed) * lengthScale;
|
|
223
|
+
const headDistance = lerp(currentBasis.length * 0.92, currentBasis.mouthPadding * 1.15, cycle);
|
|
224
|
+
let previousDistance = Math.min(currentBasis.length, headDistance + boltLength);
|
|
225
|
+
let previous = suctionPosition(
|
|
226
|
+
previousDistance,
|
|
227
|
+
Math.cos(angle) * currentBasis.spreadX * 0.5 * lateralScale(previousDistance, currentBasis),
|
|
228
|
+
Math.sin(angle * 1.3) * currentBasis.spreadY * 0.5 * lateralScale(previousDistance, currentBasis),
|
|
229
|
+
currentBasis
|
|
230
|
+
);
|
|
231
|
+
for (let segment = 0; segment < BOLT_SEGMENTS; segment += 1) {
|
|
232
|
+
const t = (segment + 1) / BOLT_SEGMENTS;
|
|
233
|
+
const distance = Math.max(currentBasis.mouthPadding * 1.08, lerp(previousDistance, headDistance, t));
|
|
234
|
+
const suckScale = lateralScale(distance, currentBasis);
|
|
235
|
+
const jitter = (sequence01(bolt * 19 + segment + Math.floor(time * 18), segment) - 0.5) * 0.34 * currentBasis.spreadX * suckScale;
|
|
236
|
+
const curl = angle + t * (2.6 + seed * 1.8) + Math.sin(time * 4 + bolt) * 0.35;
|
|
237
|
+
const next = suctionPosition(
|
|
238
|
+
distance,
|
|
239
|
+
Math.cos(curl) * currentBasis.spreadX * suckScale * (1 - t * 0.45) + jitter,
|
|
240
|
+
Math.sin(curl * 1.25) * currentBasis.spreadY * suckScale * (1 - t * 0.5),
|
|
241
|
+
currentBasis
|
|
242
|
+
);
|
|
243
|
+
const mouthFade = smoothstep(0.04, 0.18, cycle) * (1 - smoothstep(0.88, 1, cycle));
|
|
244
|
+
writeRibbon(previous, next, basis.lineWidth * 0.58, segment % 2 === 0 ? boltColor : edgeColor, 0.38 * params.lineOpacity * active * mouthFade, 1.85);
|
|
245
|
+
previous = next;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
264
249
|
}
|
|
265
250
|
}
|
|
251
|
+
function pushQuadIndices(indices, base) {
|
|
252
|
+
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
253
|
+
}
|
|
266
254
|
function createSuctionBasis(params) {
|
|
267
255
|
const scale = Math.max(0.01, params.scale);
|
|
268
256
|
const axis = new Vector3(params.directionX, 0, params.directionZ);
|
|
@@ -360,7 +348,7 @@ function createSuctionMaterial(scene) {
|
|
|
360
348
|
});
|
|
361
349
|
material.backFaceCulling = false;
|
|
362
350
|
material.transparencyMode = Material.MATERIAL_ALPHABLEND;
|
|
363
|
-
material.alphaMode = Constants.
|
|
351
|
+
material.alphaMode = Constants.ALPHA_ADD;
|
|
364
352
|
material.disableDepthWrite = true;
|
|
365
353
|
return material;
|
|
366
354
|
}
|
|
@@ -389,12 +377,6 @@ function registerSuctionShaders() {
|
|
|
389
377
|
`;
|
|
390
378
|
}
|
|
391
379
|
}
|
|
392
|
-
function lineFade(distance, basis) {
|
|
393
|
-
const mouthFade = smoothstep(basis.mouthPadding, basis.mouthPadding + basis.fadeDistance, distance);
|
|
394
|
-
const clampedDistance = Math.min(distance, basis.length);
|
|
395
|
-
const outerFade = smoothstep(0, basis.spawnFade, basis.length - clampedDistance);
|
|
396
|
-
return mouthFade * outerFade;
|
|
397
|
-
}
|
|
398
380
|
function toColor3(color) {
|
|
399
381
|
return new Color3(clamp01(color.r), clamp01(color.g), clamp01(color.b));
|
|
400
382
|
}
|
|
@@ -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
|
|
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
|
|
80
|
-
const
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
281
|
+
const mesh = CreatePlane(
|
|
355
282
|
`player-upgrade.expanding-ring.${index}`,
|
|
356
|
-
{
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "vfx-index/1.0",
|
|
3
|
-
"count":
|
|
3
|
+
"count": 40,
|
|
4
4
|
"effects": [
|
|
5
5
|
{
|
|
6
6
|
"id": "build.particle.lumber_debris_scatter",
|
|
@@ -147,6 +147,16 @@
|
|
|
147
147
|
"media": {},
|
|
148
148
|
"path": "packages/effects/explosion"
|
|
149
149
|
},
|
|
150
|
+
{
|
|
151
|
+
"id": "golden-bubble-pop",
|
|
152
|
+
"dir": "golden-bubble-pop",
|
|
153
|
+
"nameZh": "金色泡泡炸开",
|
|
154
|
+
"kind": "code",
|
|
155
|
+
"category": "golden-bubble-pop",
|
|
156
|
+
"tags": [],
|
|
157
|
+
"media": {},
|
|
158
|
+
"path": "packages/effects/golden-bubble-pop"
|
|
159
|
+
},
|
|
150
160
|
{
|
|
151
161
|
"id": "ground-tile-upgrade",
|
|
152
162
|
"dir": "ground-tile-upgrade",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fps-games/vfx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
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": {
|