@fps-games/vfx 0.3.8 → 0.3.10
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 +16 -0
- package/dist/chunks/smoke22-B_WJKB0y.js +6 -0
- package/dist/effects/debris-smoke/index.js +1 -2
- package/dist/effects/magnet-suction/effect.json.d.ts +9 -0
- package/dist/effects/magnet-suction/index.d.ts +35 -0
- package/dist/effects/magnet-suction/index.js +419 -0
- package/dist/effects/mining-hit/index.d.ts +25 -0
- package/dist/effects/mining-hit/index.js +232 -0
- package/index.json +21 -1
- package/package.json +1 -1
- package/packages/effects/magnet-suction/vfx-params.json +36 -0
- package/packages/effects/mining-hit/vfx-params.json +5 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { Mesh } from "@babylonjs/core/Meshes/mesh";
|
|
2
|
+
import { ParticleSystem } from "@babylonjs/core/Particles/particleSystem";
|
|
3
|
+
import { DynamicTexture } from "@babylonjs/core/Materials/Textures/dynamicTexture";
|
|
4
|
+
import { Color4 } from "@babylonjs/core/Maths/math.color";
|
|
5
|
+
import { Vector3, Matrix } from "@babylonjs/core/Maths/math.vector";
|
|
6
|
+
import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader";
|
|
7
|
+
import "@babylonjs/loaders/glTF/2.0/glTFLoader";
|
|
8
|
+
import { F as FlyerPool, m as makeBurstPS } from "../../chunks/space-shared-BXV1hc0k.js";
|
|
9
|
+
import { s as smokeUrl, r as rockModelUrl } from "../../chunks/smoke22-B_WJKB0y.js";
|
|
10
|
+
const DEFAULT_PARAMS = {
|
|
11
|
+
flashCount: 14,
|
|
12
|
+
flashSize: 0.9,
|
|
13
|
+
flashLength: 0.9,
|
|
14
|
+
flashBright: 2.1,
|
|
15
|
+
flashColor: { r: 1, g: 0.9, b: 0.46 },
|
|
16
|
+
height: 0,
|
|
17
|
+
debrisCount: 12,
|
|
18
|
+
debrisSize: 0.32,
|
|
19
|
+
debrisRadius: 1.35,
|
|
20
|
+
debrisSpeed: 0.9,
|
|
21
|
+
debrisHorizontalSpeed: 0.95,
|
|
22
|
+
debrisUpSpeed: 0.85,
|
|
23
|
+
debrisDirectionX: 0,
|
|
24
|
+
debrisDirectionY: 0,
|
|
25
|
+
debrisDirectionZ: 0,
|
|
26
|
+
debrisGravity: 8,
|
|
27
|
+
debrisLifetime: 1.15,
|
|
28
|
+
smokeCount: 14,
|
|
29
|
+
smokeSize: 0.95,
|
|
30
|
+
smokeRise: 1.1
|
|
31
|
+
};
|
|
32
|
+
const flashTextureCache = /* @__PURE__ */ new WeakMap();
|
|
33
|
+
function getFlashTexture(scene) {
|
|
34
|
+
const cached = flashTextureCache.get(scene);
|
|
35
|
+
if (cached) return cached;
|
|
36
|
+
const glow = new DynamicTexture("vfx_mining_hit_glow", 128, scene, false);
|
|
37
|
+
glow.hasAlpha = true;
|
|
38
|
+
const ctx = glow.getContext();
|
|
39
|
+
ctx.clearRect(0, 0, 128, 128);
|
|
40
|
+
const gradient = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
|
|
41
|
+
gradient.addColorStop(0, "rgba(255,255,255,1)");
|
|
42
|
+
gradient.addColorStop(0.85, "rgba(255,255,255,0.9)");
|
|
43
|
+
gradient.addColorStop(1, "rgba(255,255,255,0)");
|
|
44
|
+
ctx.fillStyle = gradient;
|
|
45
|
+
ctx.beginPath();
|
|
46
|
+
ctx.arc(64, 64, 64, 0, Math.PI * 2);
|
|
47
|
+
ctx.fill();
|
|
48
|
+
glow.update();
|
|
49
|
+
flashTextureCache.set(scene, glow);
|
|
50
|
+
return glow;
|
|
51
|
+
}
|
|
52
|
+
const cachedRockTemplates = /* @__PURE__ */ new WeakMap();
|
|
53
|
+
function loadRockTemplates(scene) {
|
|
54
|
+
const existing = cachedRockTemplates.get(scene);
|
|
55
|
+
if (existing) return existing;
|
|
56
|
+
const isDataUri = rockModelUrl.startsWith("data:");
|
|
57
|
+
const lastSlash = isDataUri ? -1 : rockModelUrl.lastIndexOf("/");
|
|
58
|
+
const rootUrl = lastSlash >= 0 ? rockModelUrl.slice(0, lastSlash + 1) : "";
|
|
59
|
+
const fileName = lastSlash >= 0 ? rockModelUrl.slice(lastSlash + 1) : rockModelUrl;
|
|
60
|
+
const promise = SceneLoader.ImportMeshAsync("", rootUrl, fileName, scene, void 0, ".glb").then((res) => {
|
|
61
|
+
const templates = res.meshes.filter((mesh) => mesh instanceof Mesh && mesh.getTotalVertices() > 0);
|
|
62
|
+
for (const template of templates) {
|
|
63
|
+
template.setParent(null);
|
|
64
|
+
const center = template.getBoundingInfo().boundingBox.centerWorld.clone();
|
|
65
|
+
template.bakeTransformIntoVertices(Matrix.Translation(-center.x, -center.y, -center.z));
|
|
66
|
+
template.position.set(0, 0, 0);
|
|
67
|
+
template.setEnabled(false);
|
|
68
|
+
template.isPickable = false;
|
|
69
|
+
}
|
|
70
|
+
return templates;
|
|
71
|
+
}).catch((error) => {
|
|
72
|
+
cachedRockTemplates.delete(scene);
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
cachedRockTemplates.set(scene, promise);
|
|
76
|
+
return promise;
|
|
77
|
+
}
|
|
78
|
+
function c4(color, multiplier = 1, alpha = 1) {
|
|
79
|
+
return new Color4(color.r * multiplier, color.g * multiplier, color.b * multiplier, alpha);
|
|
80
|
+
}
|
|
81
|
+
function averageScale(scale) {
|
|
82
|
+
return Math.max(1e-4, (Math.abs(scale.x) + Math.abs(scale.y) + Math.abs(scale.z)) / 3);
|
|
83
|
+
}
|
|
84
|
+
const miningHitEffectPackage = {
|
|
85
|
+
id: "mining-hit",
|
|
86
|
+
nameZh: "采矿受击",
|
|
87
|
+
placement: "world",
|
|
88
|
+
debugAnchor: "playerRight",
|
|
89
|
+
defaultParams: DEFAULT_PARAMS,
|
|
90
|
+
debugParams: [
|
|
91
|
+
{ key: "flashCount", label: "闪光·数量", kind: "number", min: 0, max: 40, step: 1 },
|
|
92
|
+
{ key: "flashSize", label: "闪光·尺寸", kind: "scale", min: 0.2, max: 3, step: 0.05 },
|
|
93
|
+
{ key: "flashLength", label: "闪光·拉伸", kind: "scale", min: 0.2, max: 3, step: 0.05 },
|
|
94
|
+
{ key: "flashBright", label: "闪光·亮度", kind: "number", min: 0.5, max: 5, step: 0.05 },
|
|
95
|
+
{ key: "flashColor", label: "闪光·颜色", kind: "color" },
|
|
96
|
+
{ key: "height", label: "高度", kind: "number", min: -2, max: 3, step: 0.05 },
|
|
97
|
+
{ key: "debrisCount", label: "碎石·数量", kind: "number", min: 0, max: 30, step: 1 },
|
|
98
|
+
{ key: "debrisSize", label: "碎石·大小", kind: "number", min: 0.1, max: 1, step: 0.01 },
|
|
99
|
+
{ key: "debrisRadius", label: "碎石·范围", kind: "scale", min: 0.5, max: 5, step: 0.1 },
|
|
100
|
+
{ key: "debrisSpeed", label: "碎石·整体速度", kind: "number", min: 0, max: 5, step: 0.05 },
|
|
101
|
+
{ key: "debrisHorizontalSpeed", label: "碎石·横向速度", kind: "number", min: 0, max: 5, step: 0.05 },
|
|
102
|
+
{ key: "debrisUpSpeed", label: "碎石·上抛速度", kind: "number", min: 0, max: 5, step: 0.05 },
|
|
103
|
+
{ key: "debrisDirectionX", label: "碎石·方向 X", kind: "number", min: -1, max: 1, step: 0.01 },
|
|
104
|
+
{ key: "debrisDirectionY", label: "碎石·方向 Y", kind: "number", min: -1, max: 1, step: 0.01 },
|
|
105
|
+
{ key: "debrisDirectionZ", label: "碎石·方向 Z", kind: "number", min: -1, max: 1, step: 0.01 },
|
|
106
|
+
{ key: "debrisGravity", label: "碎石·下落重力", kind: "number", min: 0, max: 20, step: 0.1 },
|
|
107
|
+
{ key: "debrisLifetime", label: "碎石·可见时长", kind: "lifetime", min: 0.2, max: 4, step: 0.05 },
|
|
108
|
+
{ key: "smokeCount", label: "烟雾·数量", kind: "number", min: 0, max: 40, step: 1 },
|
|
109
|
+
{ key: "smokeSize", label: "烟雾·尺寸", kind: "scale", min: 0.2, max: 3, step: 0.05 },
|
|
110
|
+
{ key: "smokeRise", label: "烟雾·上浮", kind: "number", min: 0, max: 4, step: 0.05 }
|
|
111
|
+
],
|
|
112
|
+
create({ scene, root, position, params, scale }) {
|
|
113
|
+
root.position.copyFrom(position);
|
|
114
|
+
const spawnScale = averageScale(scale);
|
|
115
|
+
const origin = position.clone();
|
|
116
|
+
origin.y += params.height * spawnScale;
|
|
117
|
+
const flash = createFlashBurst(scene, origin, params, spawnScale);
|
|
118
|
+
const smoke = createSmokeBurst(scene, origin, params, spawnScale);
|
|
119
|
+
const debrisPool = new FlyerPool(scene);
|
|
120
|
+
let disposed = false;
|
|
121
|
+
const ready = (params.debrisCount > 0 ? loadRockTemplates(scene) : Promise.resolve([])).then((templates) => {
|
|
122
|
+
if (disposed || templates.length === 0) return;
|
|
123
|
+
const count = Math.round(params.debrisCount);
|
|
124
|
+
const radius = params.debrisRadius * spawnScale;
|
|
125
|
+
for (let i = 0; i < count; i += 1) {
|
|
126
|
+
const template = templates[Math.random() * templates.length | 0];
|
|
127
|
+
const debris = template.clone("vfx_mining_hit_debris", null, false);
|
|
128
|
+
if (!debris) continue;
|
|
129
|
+
debris.setEnabled(true);
|
|
130
|
+
debris.isPickable = false;
|
|
131
|
+
debris.normalizeToUnitCube();
|
|
132
|
+
debris.scaling.scaleInPlace(radius * params.debrisSize * (0.5 + Math.random() * 0.8));
|
|
133
|
+
debrisPool.spawn(debris, origin, makeDebrisVelocity(params, spawnScale), params.debrisGravity, params.debrisLifetime);
|
|
134
|
+
}
|
|
135
|
+
}).catch((error) => {
|
|
136
|
+
console.warn("[VFX] mining-hit load failed", error);
|
|
137
|
+
});
|
|
138
|
+
let elapsed = 0;
|
|
139
|
+
let flashStopped = false;
|
|
140
|
+
const observer = scene.onBeforeRenderObservable.add(() => {
|
|
141
|
+
elapsed += scene.getEngine().getDeltaTime();
|
|
142
|
+
if (!flashStopped && elapsed >= 180) {
|
|
143
|
+
flash.stop();
|
|
144
|
+
flashStopped = true;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
const disposeDelay = Math.max(2200, params.debrisLifetime * 1e3 + 1e3);
|
|
148
|
+
const disposeTimer = window.setTimeout(() => disposeAll(), disposeDelay);
|
|
149
|
+
function disposeAll() {
|
|
150
|
+
if (disposed) return;
|
|
151
|
+
disposed = true;
|
|
152
|
+
window.clearTimeout(disposeTimer);
|
|
153
|
+
scene.onBeforeRenderObservable.remove(observer);
|
|
154
|
+
debrisPool.dispose();
|
|
155
|
+
flash.dispose(false);
|
|
156
|
+
smoke.dispose();
|
|
157
|
+
root.dispose();
|
|
158
|
+
}
|
|
159
|
+
return { root, ready, dispose() {
|
|
160
|
+
disposeAll();
|
|
161
|
+
} };
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
function createFlashBurst(scene, origin, params, spawnScale) {
|
|
165
|
+
const flash = new ParticleSystem("vfx_mining_hit_flash", 120, scene);
|
|
166
|
+
flash.emitter = origin.clone();
|
|
167
|
+
flash.minEmitBox = new Vector3(0, 0, 0);
|
|
168
|
+
flash.maxEmitBox = new Vector3(0, 0, 0);
|
|
169
|
+
flash.billboardMode = ParticleSystem.BILLBOARDMODE_STRETCHED;
|
|
170
|
+
flash.minLifeTime = 0.12;
|
|
171
|
+
flash.maxLifeTime = 0.25;
|
|
172
|
+
flash.minEmitPower = 9.1 * spawnScale;
|
|
173
|
+
flash.maxEmitPower = 15.6 * spawnScale;
|
|
174
|
+
flash.direction1 = new Vector3(-1, 0.05, -1);
|
|
175
|
+
flash.direction2 = new Vector3(1, 0.15, 1);
|
|
176
|
+
flash.gravity = new Vector3(0, 0, 0);
|
|
177
|
+
flash.emitRate = 0;
|
|
178
|
+
flash.blendMode = ParticleSystem.BLENDMODE_ADD;
|
|
179
|
+
flash.particleTexture = getFlashTexture(scene);
|
|
180
|
+
flash.minSize = 0.78 * params.flashSize * spawnScale;
|
|
181
|
+
flash.maxSize = 1.3 * params.flashSize * spawnScale;
|
|
182
|
+
flash.minScaleY = 3.38 * params.flashLength;
|
|
183
|
+
flash.maxScaleY = 5.46 * params.flashLength;
|
|
184
|
+
flash.minScaleX = 0.18 * params.flashSize;
|
|
185
|
+
flash.maxScaleX = 0.36 * params.flashSize;
|
|
186
|
+
flash.addColorGradient(0, c4(params.flashColor, params.flashBright, 1));
|
|
187
|
+
flash.addColorGradient(0.5, c4(params.flashColor, params.flashBright * 0.75, 1));
|
|
188
|
+
flash.addColorGradient(1, c4(params.flashColor, params.flashBright * 0.5, 0));
|
|
189
|
+
flash.manualEmitCount = Math.round(params.flashCount);
|
|
190
|
+
flash.start();
|
|
191
|
+
return flash;
|
|
192
|
+
}
|
|
193
|
+
function createSmokeBurst(scene, origin, params, spawnScale) {
|
|
194
|
+
const smoke = makeBurstPS("vfx_mining_hit_smoke", scene, smokeUrl, {
|
|
195
|
+
cap: 120,
|
|
196
|
+
size: [2.2 * spawnScale, 4.5 * spawnScale],
|
|
197
|
+
life: [0.9, 1.6],
|
|
198
|
+
power: [1.5 * spawnScale, 4 * spawnScale],
|
|
199
|
+
gravity: new Vector3(0, params.smokeRise, 0),
|
|
200
|
+
blend: ParticleSystem.BLENDMODE_STANDARD,
|
|
201
|
+
c1: new Color4(0.5, 0.45, 0.42, 0.65),
|
|
202
|
+
c2: new Color4(0.32, 0.3, 0.3, 0.5),
|
|
203
|
+
cd: new Color4(0.2, 0.2, 0.2, 0)
|
|
204
|
+
});
|
|
205
|
+
smoke.minSize *= params.smokeSize;
|
|
206
|
+
smoke.maxSize *= params.smokeSize;
|
|
207
|
+
smoke.emitter.copyFrom(origin);
|
|
208
|
+
smoke.manualEmitCount += Math.round(params.smokeCount);
|
|
209
|
+
return smoke;
|
|
210
|
+
}
|
|
211
|
+
function makeDebrisVelocity(params, spawnScale) {
|
|
212
|
+
const velocity = new Vector3(
|
|
213
|
+
(Math.random() - 0.5) * 13 * params.debrisHorizontalSpeed,
|
|
214
|
+
(Math.random() * 6.75 + 2.25) * params.debrisUpSpeed,
|
|
215
|
+
(Math.random() - 0.5) * 13 * params.debrisHorizontalSpeed
|
|
216
|
+
);
|
|
217
|
+
const direction = new Vector3(params.debrisDirectionX, params.debrisDirectionY, params.debrisDirectionZ);
|
|
218
|
+
const directionLength = direction.length();
|
|
219
|
+
if (directionLength > 1e-4) {
|
|
220
|
+
direction.scaleInPlace(1 / directionLength);
|
|
221
|
+
const directionSpeed = 6.5 * Math.min(directionLength, 1);
|
|
222
|
+
velocity.addInPlace(new Vector3(
|
|
223
|
+
direction.x * directionSpeed * params.debrisHorizontalSpeed,
|
|
224
|
+
direction.y * directionSpeed * params.debrisUpSpeed,
|
|
225
|
+
direction.z * directionSpeed * params.debrisHorizontalSpeed
|
|
226
|
+
));
|
|
227
|
+
}
|
|
228
|
+
return velocity.scaleInPlace(params.debrisSpeed * spawnScale);
|
|
229
|
+
}
|
|
230
|
+
export {
|
|
231
|
+
miningHitEffectPackage
|
|
232
|
+
};
|
package/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "vfx-index/1.0",
|
|
3
|
-
"count":
|
|
3
|
+
"count": 39,
|
|
4
4
|
"effects": [
|
|
5
5
|
{
|
|
6
6
|
"id": "build.particle.lumber_debris_scatter",
|
|
@@ -217,6 +217,26 @@
|
|
|
217
217
|
"media": {},
|
|
218
218
|
"path": "packages/effects/lumber-cart-suction"
|
|
219
219
|
},
|
|
220
|
+
{
|
|
221
|
+
"id": "magnet-suction",
|
|
222
|
+
"dir": "magnet-suction",
|
|
223
|
+
"nameZh": "磁铁吸入线",
|
|
224
|
+
"kind": "code",
|
|
225
|
+
"category": "magnet-suction",
|
|
226
|
+
"tags": [],
|
|
227
|
+
"media": {},
|
|
228
|
+
"path": "packages/effects/magnet-suction"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"id": "mining-hit",
|
|
232
|
+
"dir": "mining-hit",
|
|
233
|
+
"nameZh": "采矿受击",
|
|
234
|
+
"kind": "code",
|
|
235
|
+
"category": "mining-hit",
|
|
236
|
+
"tags": [],
|
|
237
|
+
"media": {},
|
|
238
|
+
"path": "packages/effects/mining-hit"
|
|
239
|
+
},
|
|
220
240
|
{
|
|
221
241
|
"id": "monster-hit",
|
|
222
242
|
"dir": "monster-hit",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fps-games/vfx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
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,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lifetimeMode": "infinite",
|
|
3
|
+
"lifetime": 2.5,
|
|
4
|
+
"scale": 1.15,
|
|
5
|
+
"initialHeight": 0,
|
|
6
|
+
"color": {
|
|
7
|
+
"r": 0.82,
|
|
8
|
+
"g": 0.95,
|
|
9
|
+
"b": 1
|
|
10
|
+
},
|
|
11
|
+
"opacity": 1,
|
|
12
|
+
"intensity": 1,
|
|
13
|
+
"forwardOffset": 0,
|
|
14
|
+
"suctionLength": 2.25,
|
|
15
|
+
"startRadius": 0.7,
|
|
16
|
+
"spreadY": 0.34,
|
|
17
|
+
"endRadius": 0.08,
|
|
18
|
+
"mouthPadding": 0.18,
|
|
19
|
+
"fadeDistance": 0.58,
|
|
20
|
+
"spawnFade": 0.45,
|
|
21
|
+
"suctionSpeed": 1.65,
|
|
22
|
+
"lineCount": 32,
|
|
23
|
+
"lineLength": 0.46,
|
|
24
|
+
"lineWidth": 0.045,
|
|
25
|
+
"lineOpacity": 1,
|
|
26
|
+
"lineSpread": 1,
|
|
27
|
+
"lineTwist": 0,
|
|
28
|
+
"particleCount": 14,
|
|
29
|
+
"particleSize": 0.032,
|
|
30
|
+
"particleSpeed": 2.15,
|
|
31
|
+
"particleDrift": 0,
|
|
32
|
+
"particleOuterOffset": 0,
|
|
33
|
+
"particleOpacity": 0.55,
|
|
34
|
+
"directionX": 0,
|
|
35
|
+
"directionZ": -1
|
|
36
|
+
}
|