@fps-games/vfx 0.3.11 → 0.3.13

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.
@@ -0,0 +1,7 @@
1
+ declare const _default: {
2
+ "id": "golden-bubble-pop",
3
+ "nameZh": "金色泡泡炸开"
4
+ }
5
+ ;
6
+
7
+ export default _default;
@@ -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
+ };
package/index.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "vfx-index/1.0",
3
- "count": 39,
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.11",
3
+ "version": "0.3.13",
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": {
@@ -0,0 +1,5 @@
1
+ {
2
+ "schemaVersion": "vfx-params/1.0",
3
+ "effectId": "golden-bubble-pop",
4
+ "params": {}
5
+ }