@zylem/game-lib 0.6.2 → 0.6.4
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/LICENSE +21 -0
- package/README.md +9 -16
- package/dist/actions.d.ts +30 -21
- package/dist/actions.js +793 -146
- package/dist/actions.js.map +1 -1
- package/dist/behavior/jumper-2d.d.ts +114 -0
- package/dist/behavior/jumper-2d.js +711 -0
- package/dist/behavior/jumper-2d.js.map +1 -0
- package/dist/behavior/platformer-3d.d.ts +296 -0
- package/dist/behavior/platformer-3d.js +761 -0
- package/dist/behavior/platformer-3d.js.map +1 -0
- package/dist/behavior/ricochet-2d.d.ts +275 -0
- package/dist/behavior/ricochet-2d.js +425 -0
- package/dist/behavior/ricochet-2d.js.map +1 -0
- package/dist/behavior/ricochet-3d.d.ts +117 -0
- package/dist/behavior/ricochet-3d.js +443 -0
- package/dist/behavior/ricochet-3d.js.map +1 -0
- package/dist/behavior/screen-visibility.d.ts +79 -0
- package/dist/behavior/screen-visibility.js +358 -0
- package/dist/behavior/screen-visibility.js.map +1 -0
- package/dist/behavior/screen-wrap.d.ts +87 -0
- package/dist/behavior/screen-wrap.js +246 -0
- package/dist/behavior/screen-wrap.js.map +1 -0
- package/dist/behavior/shooter-2d.d.ts +79 -0
- package/dist/behavior/shooter-2d.js +180 -0
- package/dist/behavior/shooter-2d.js.map +1 -0
- package/dist/behavior/thruster.d.ts +11 -0
- package/dist/behavior/thruster.js +292 -0
- package/dist/behavior/thruster.js.map +1 -0
- package/dist/behavior/top-down-movement.d.ts +56 -0
- package/dist/behavior/top-down-movement.js +125 -0
- package/dist/behavior/top-down-movement.js.map +1 -0
- package/dist/behavior/world-boundary-2d.d.ts +142 -0
- package/dist/behavior/world-boundary-2d.js +235 -0
- package/dist/behavior/world-boundary-2d.js.map +1 -0
- package/dist/behavior/world-boundary-3d.d.ts +76 -0
- package/dist/behavior/world-boundary-3d.js +274 -0
- package/dist/behavior/world-boundary-3d.js.map +1 -0
- package/dist/behavior-descriptor-BXnVR8Ki.d.ts +159 -0
- package/dist/{blueprints-Cq3Ko6_G.d.ts → blueprints-DmbK2dki.d.ts} +2 -2
- package/dist/camera-4XO5gbQH.d.ts +905 -0
- package/dist/camera.d.ts +3 -2
- package/dist/camera.js +1653 -377
- package/dist/camera.js.map +1 -1
- package/dist/composition-BASvMKrW.d.ts +218 -0
- package/dist/{core-bO8TzV7u.d.ts → core-CARRaS55.d.ts} +110 -69
- package/dist/core.d.ts +11 -6
- package/dist/core.js +10766 -5626
- package/dist/core.js.map +1 -1
- package/dist/{entities-DvByhMGU.d.ts → entities-ChFirVL9.d.ts} +133 -29
- package/dist/entities.d.ts +5 -3
- package/dist/entities.js +4679 -3202
- package/dist/entities.js.map +1 -1
- package/dist/entity-vj-HTjzU.d.ts +1169 -0
- package/dist/global-change-2JvMaz44.d.ts +25 -0
- package/dist/main.d.ts +1118 -16
- package/dist/main.js +17538 -8499
- package/dist/main.js.map +1 -1
- package/dist/physics-pose-DCc4oE44.d.ts +25 -0
- package/dist/physics-protocol-BDD3P5W2.d.ts +200 -0
- package/dist/physics-worker.d.ts +21 -0
- package/dist/physics-worker.js +306 -0
- package/dist/physics-worker.js.map +1 -0
- package/dist/physics.d.ts +205 -0
- package/dist/physics.js +577 -0
- package/dist/physics.js.map +1 -0
- package/dist/stage-types-C19IhuzA.d.ts +731 -0
- package/dist/stage.d.ts +11 -7
- package/dist/stage.js +8024 -3852
- package/dist/stage.js.map +1 -1
- package/dist/sync-state-machine-CZyspBpj.d.ts +16 -0
- package/dist/thruster-23lzoPZd.d.ts +180 -0
- package/dist/world-DfgxoNMt.d.ts +105 -0
- package/package.json +53 -13
- package/dist/behaviors.d.ts +0 -854
- package/dist/behaviors.js +0 -1209
- package/dist/behaviors.js.map +0 -1
- package/dist/camera-CeJPAgGg.d.ts +0 -116
- package/dist/moveable-B_vyA6cw.d.ts +0 -67
- package/dist/stage-types-Bd-KtcYT.d.ts +0 -375
- package/dist/transformable-CUhvyuYO.d.ts +0 -67
- package/dist/world-C8tQ7Plj.d.ts +0 -774
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
// src/lib/behaviors/platformer-3d/components.ts
|
|
2
|
+
function createPlatformer3DMovementComponent(options = {}) {
|
|
3
|
+
return {
|
|
4
|
+
walkSpeed: options.walkSpeed ?? 12,
|
|
5
|
+
runSpeed: options.runSpeed ?? 24,
|
|
6
|
+
jumpForce: options.jumpForce ?? 12,
|
|
7
|
+
maxJumps: options.maxJumps ?? 1,
|
|
8
|
+
gravity: options.gravity ?? 9.82,
|
|
9
|
+
groundRayLength: options.groundRayLength ?? 1,
|
|
10
|
+
coyoteTime: options.coyoteTime ?? 0.1,
|
|
11
|
+
jumpBufferTime: options.jumpBufferTime ?? 0.1,
|
|
12
|
+
jumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,
|
|
13
|
+
multiJumpWindowTime: options.multiJumpWindowTime ?? 0.15,
|
|
14
|
+
// 150ms default
|
|
15
|
+
debugGroundProbe: options.debugGroundProbe ?? false
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function createPlatformer3DInputComponent() {
|
|
19
|
+
return {
|
|
20
|
+
moveX: 0,
|
|
21
|
+
moveZ: 0,
|
|
22
|
+
jump: false,
|
|
23
|
+
run: false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createPlatformer3DStateComponent() {
|
|
27
|
+
return {
|
|
28
|
+
grounded: false,
|
|
29
|
+
jumping: false,
|
|
30
|
+
falling: false,
|
|
31
|
+
jumpCount: 0,
|
|
32
|
+
jumpStartHeight: 0,
|
|
33
|
+
currentSpeed: 0,
|
|
34
|
+
lastGroundedY: 0,
|
|
35
|
+
jumpPressedLastFrame: false,
|
|
36
|
+
collisionGrounded: false,
|
|
37
|
+
groundedCollisionTime: 0,
|
|
38
|
+
timeSinceGrounded: 0,
|
|
39
|
+
jumpBuffered: false,
|
|
40
|
+
jumpBufferTimer: 0,
|
|
41
|
+
jumpHeld: false,
|
|
42
|
+
jumpCutApplied: false,
|
|
43
|
+
jumpReleasedSinceLastJump: true,
|
|
44
|
+
timeSinceJump: 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/lib/core/utility/sync-state-machine.ts
|
|
49
|
+
import {
|
|
50
|
+
StateMachine as BaseStateMachine
|
|
51
|
+
} from "typescript-fsm";
|
|
52
|
+
import { t } from "typescript-fsm";
|
|
53
|
+
var SyncStateMachine = class extends BaseStateMachine {
|
|
54
|
+
constructor(init, transitions = [], logger = console) {
|
|
55
|
+
super(init, transitions, logger);
|
|
56
|
+
}
|
|
57
|
+
dispatch(_event, ..._args) {
|
|
58
|
+
throw new Error("SyncStateMachine does not support async dispatch.");
|
|
59
|
+
}
|
|
60
|
+
syncDispatch(event, ...args) {
|
|
61
|
+
const found = this.transitions.some((transition) => {
|
|
62
|
+
if (transition.fromState !== this._current || transition.event !== event) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const current = this._current;
|
|
66
|
+
this._current = transition.toState;
|
|
67
|
+
if (!transition.cb) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
transition.cb(...args);
|
|
72
|
+
return true;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this._current = current;
|
|
75
|
+
this.logger.error("Exception in callback", error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (!found) {
|
|
80
|
+
const errorMessage = this.formatErr(this._current, event);
|
|
81
|
+
this.logger.error(errorMessage);
|
|
82
|
+
}
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/lib/behaviors/platformer-3d/platformer-3d-fsm.ts
|
|
88
|
+
var Platformer3DState = /* @__PURE__ */ ((Platformer3DState2) => {
|
|
89
|
+
Platformer3DState2["Idle"] = "idle";
|
|
90
|
+
Platformer3DState2["Walking"] = "walking";
|
|
91
|
+
Platformer3DState2["Running"] = "running";
|
|
92
|
+
Platformer3DState2["Jumping"] = "jumping";
|
|
93
|
+
Platformer3DState2["Falling"] = "falling";
|
|
94
|
+
Platformer3DState2["Landing"] = "landing";
|
|
95
|
+
return Platformer3DState2;
|
|
96
|
+
})(Platformer3DState || {});
|
|
97
|
+
var Platformer3DEvent = /* @__PURE__ */ ((Platformer3DEvent2) => {
|
|
98
|
+
Platformer3DEvent2["Walk"] = "walk";
|
|
99
|
+
Platformer3DEvent2["Run"] = "run";
|
|
100
|
+
Platformer3DEvent2["Jump"] = "jump";
|
|
101
|
+
Platformer3DEvent2["Fall"] = "fall";
|
|
102
|
+
Platformer3DEvent2["Land"] = "land";
|
|
103
|
+
Platformer3DEvent2["Stop"] = "stop";
|
|
104
|
+
return Platformer3DEvent2;
|
|
105
|
+
})(Platformer3DEvent || {});
|
|
106
|
+
var Platformer3DFSM = class {
|
|
107
|
+
constructor(ctx) {
|
|
108
|
+
this.ctx = ctx;
|
|
109
|
+
this.machine = new SyncStateMachine(
|
|
110
|
+
"idle" /* Idle */,
|
|
111
|
+
[
|
|
112
|
+
// Idle transitions
|
|
113
|
+
t("idle" /* Idle */, "walk" /* Walk */, "walking" /* Walking */),
|
|
114
|
+
t("idle" /* Idle */, "run" /* Run */, "running" /* Running */),
|
|
115
|
+
t("idle" /* Idle */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
116
|
+
t("idle" /* Idle */, "fall" /* Fall */, "falling" /* Falling */),
|
|
117
|
+
// Walking transitions
|
|
118
|
+
t("walking" /* Walking */, "run" /* Run */, "running" /* Running */),
|
|
119
|
+
t("walking" /* Walking */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
120
|
+
t("walking" /* Walking */, "stop" /* Stop */, "idle" /* Idle */),
|
|
121
|
+
t("walking" /* Walking */, "fall" /* Fall */, "falling" /* Falling */),
|
|
122
|
+
// Running transitions
|
|
123
|
+
t("running" /* Running */, "walk" /* Walk */, "walking" /* Walking */),
|
|
124
|
+
t("running" /* Running */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
125
|
+
t("running" /* Running */, "stop" /* Stop */, "idle" /* Idle */),
|
|
126
|
+
t("running" /* Running */, "fall" /* Fall */, "falling" /* Falling */),
|
|
127
|
+
// Jumping transitions
|
|
128
|
+
t("jumping" /* Jumping */, "fall" /* Fall */, "falling" /* Falling */),
|
|
129
|
+
t("jumping" /* Jumping */, "land" /* Land */, "landing" /* Landing */),
|
|
130
|
+
t("jumping" /* Jumping */, "jump" /* Jump */, "jumping" /* Jumping */),
|
|
131
|
+
// Multi-jump
|
|
132
|
+
// Falling transitions
|
|
133
|
+
t("falling" /* Falling */, "land" /* Land */, "landing" /* Landing */),
|
|
134
|
+
// Landing transitions
|
|
135
|
+
t("landing" /* Landing */, "walk" /* Walk */, "walking" /* Walking */),
|
|
136
|
+
t("landing" /* Landing */, "run" /* Run */, "running" /* Running */),
|
|
137
|
+
t("landing" /* Landing */, "stop" /* Stop */, "idle" /* Idle */),
|
|
138
|
+
// Self-transitions (no-ops)
|
|
139
|
+
t("idle" /* Idle */, "stop" /* Stop */, "idle" /* Idle */)
|
|
140
|
+
]
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
machine;
|
|
144
|
+
/**
|
|
145
|
+
* Get the current state
|
|
146
|
+
*/
|
|
147
|
+
getState() {
|
|
148
|
+
return this.machine.getState();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Dispatch an event to the FSM
|
|
152
|
+
*/
|
|
153
|
+
dispatch(event) {
|
|
154
|
+
if (this.machine.can(event)) {
|
|
155
|
+
this.machine.syncDispatch(event);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Check if grounded
|
|
160
|
+
*/
|
|
161
|
+
isGrounded() {
|
|
162
|
+
const state = this.getState();
|
|
163
|
+
return state === "idle" /* Idle */ || state === "walking" /* Walking */ || state === "running" /* Running */ || state === "landing" /* Landing */;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get current jump count from context
|
|
167
|
+
*/
|
|
168
|
+
getJumpCount() {
|
|
169
|
+
return this.ctx.state.jumpCount;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Handle collision event to update ground state
|
|
173
|
+
*/
|
|
174
|
+
handleCollision(ctx) {
|
|
175
|
+
if (ctx.contact.normal.y > 0.5) {
|
|
176
|
+
this.ctx.state.collisionGrounded = true;
|
|
177
|
+
this.ctx.state.groundedCollisionTime = performance.now();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Update FSM based on current state
|
|
182
|
+
*/
|
|
183
|
+
update(input, state) {
|
|
184
|
+
this.ctx.input = input;
|
|
185
|
+
this.ctx.state = state;
|
|
186
|
+
const currentState = this.getState();
|
|
187
|
+
const hasInput = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;
|
|
188
|
+
const isRunning = input.run;
|
|
189
|
+
if (currentState === "falling" /* Falling */ && state.grounded) {
|
|
190
|
+
this.dispatch("land" /* Land */);
|
|
191
|
+
}
|
|
192
|
+
if (state.grounded) {
|
|
193
|
+
if (hasInput) {
|
|
194
|
+
if (isRunning) {
|
|
195
|
+
this.dispatch("run" /* Run */);
|
|
196
|
+
} else {
|
|
197
|
+
this.dispatch("walk" /* Walk */);
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
this.dispatch("stop" /* Stop */);
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
if (state.falling) {
|
|
204
|
+
this.dispatch("fall" /* Fall */);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (input.jump && !state.jumpPressedLastFrame) {
|
|
208
|
+
this.dispatch("jump" /* Jump */);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/lib/behaviors/shared/ground-probe-3d.ts
|
|
214
|
+
import { Ray } from "@dimforge/rapier3d-compat";
|
|
215
|
+
import { BufferGeometry, Line, LineBasicMaterial, Vector3 } from "three";
|
|
216
|
+
|
|
217
|
+
// src/lib/physics/serialize-descriptors.ts
|
|
218
|
+
import { RigidBodyType } from "@dimforge/rapier3d-compat";
|
|
219
|
+
function serializeColliderDesc(desc) {
|
|
220
|
+
const internal = desc;
|
|
221
|
+
const customShapeData = internal.__zylemShapeData;
|
|
222
|
+
if (customShapeData?.shape === "trimesh") {
|
|
223
|
+
const result2 = {
|
|
224
|
+
shape: "trimesh",
|
|
225
|
+
dimensions: [],
|
|
226
|
+
vertices: [...customShapeData.vertices],
|
|
227
|
+
indices: [...customShapeData.indices]
|
|
228
|
+
};
|
|
229
|
+
const translation = internal.translation;
|
|
230
|
+
if (translation && (translation.x !== 0 || translation.y !== 0 || translation.z !== 0)) {
|
|
231
|
+
result2.translation = [translation.x, translation.y, translation.z];
|
|
232
|
+
}
|
|
233
|
+
if (internal.isSensor) {
|
|
234
|
+
result2.sensor = true;
|
|
235
|
+
}
|
|
236
|
+
if (internal.collisionGroups !== void 0 && internal.collisionGroups !== 4294967295) {
|
|
237
|
+
result2.collisionGroups = internal.collisionGroups;
|
|
238
|
+
}
|
|
239
|
+
if (internal.activeCollisionTypes !== void 0) {
|
|
240
|
+
result2.activeCollisionTypes = internal.activeCollisionTypes;
|
|
241
|
+
}
|
|
242
|
+
return result2;
|
|
243
|
+
}
|
|
244
|
+
const shapeType = internal.shape?.type ?? internal.shapeType ?? 0;
|
|
245
|
+
const { shape, dimensions, heightfieldMeta } = extractShapeData(shapeType, internal);
|
|
246
|
+
const result = {
|
|
247
|
+
shape,
|
|
248
|
+
dimensions
|
|
249
|
+
};
|
|
250
|
+
const t2 = internal.translation;
|
|
251
|
+
if (t2 && (t2.x !== 0 || t2.y !== 0 || t2.z !== 0)) {
|
|
252
|
+
result.translation = [t2.x, t2.y, t2.z];
|
|
253
|
+
}
|
|
254
|
+
if (internal.isSensor) {
|
|
255
|
+
result.sensor = true;
|
|
256
|
+
}
|
|
257
|
+
if (internal.collisionGroups !== void 0 && internal.collisionGroups !== 4294967295) {
|
|
258
|
+
result.collisionGroups = internal.collisionGroups;
|
|
259
|
+
}
|
|
260
|
+
if (internal.activeCollisionTypes !== void 0) {
|
|
261
|
+
result.activeCollisionTypes = internal.activeCollisionTypes;
|
|
262
|
+
}
|
|
263
|
+
if (heightfieldMeta) {
|
|
264
|
+
result.heightfieldMeta = heightfieldMeta;
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function extractShapeData(shapeType, internal) {
|
|
269
|
+
switch (shapeType) {
|
|
270
|
+
case 0:
|
|
271
|
+
return {
|
|
272
|
+
shape: "ball",
|
|
273
|
+
dimensions: [internal.shape?.radius ?? internal.halfExtents?.x ?? 1]
|
|
274
|
+
};
|
|
275
|
+
case 1:
|
|
276
|
+
return {
|
|
277
|
+
shape: "cuboid",
|
|
278
|
+
dimensions: [
|
|
279
|
+
internal.shape?.halfExtents?.x ?? internal.halfExtents?.x ?? 0.5,
|
|
280
|
+
internal.shape?.halfExtents?.y ?? internal.halfExtents?.y ?? 0.5,
|
|
281
|
+
internal.shape?.halfExtents?.z ?? internal.halfExtents?.z ?? 0.5
|
|
282
|
+
]
|
|
283
|
+
};
|
|
284
|
+
case 2:
|
|
285
|
+
return {
|
|
286
|
+
shape: "capsule",
|
|
287
|
+
dimensions: [
|
|
288
|
+
internal.shape?.halfHeight ?? 0.5,
|
|
289
|
+
internal.shape?.radius ?? 0.5
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
case 6:
|
|
293
|
+
return {
|
|
294
|
+
shape: "cone",
|
|
295
|
+
dimensions: [
|
|
296
|
+
internal.shape?.halfHeight ?? 1,
|
|
297
|
+
internal.shape?.radius ?? 1
|
|
298
|
+
]
|
|
299
|
+
};
|
|
300
|
+
case 7:
|
|
301
|
+
return {
|
|
302
|
+
shape: "cylinder",
|
|
303
|
+
dimensions: [
|
|
304
|
+
internal.shape?.halfHeight ?? 1,
|
|
305
|
+
internal.shape?.radius ?? 1
|
|
306
|
+
]
|
|
307
|
+
};
|
|
308
|
+
case 11: {
|
|
309
|
+
const nrows = internal.shape?.nrows ?? 10;
|
|
310
|
+
const ncols = internal.shape?.ncols ?? 10;
|
|
311
|
+
const heights = internal.shape?.heights;
|
|
312
|
+
return {
|
|
313
|
+
shape: "heightfield",
|
|
314
|
+
dimensions: heights ? Array.from(heights) : [],
|
|
315
|
+
heightfieldMeta: { nrows, ncols }
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
default:
|
|
319
|
+
return { shape: "cuboid", dimensions: [0.5, 0.5, 0.5] };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/lib/behaviors/shared/ground-probe-3d.ts
|
|
324
|
+
var DEFAULT_OFFSETS = [
|
|
325
|
+
{ x: 0, z: 0 },
|
|
326
|
+
{ x: 0.4, z: 0.4 },
|
|
327
|
+
{ x: -0.4, z: 0.4 },
|
|
328
|
+
{ x: 0.4, z: -0.4 },
|
|
329
|
+
{ x: -0.4, z: -0.4 }
|
|
330
|
+
];
|
|
331
|
+
var GroundProbe3D = class {
|
|
332
|
+
constructor(world) {
|
|
333
|
+
this.world = world;
|
|
334
|
+
}
|
|
335
|
+
rays = /* @__PURE__ */ new Map();
|
|
336
|
+
debugLines = /* @__PURE__ */ new Map();
|
|
337
|
+
probeSupport(entity, options) {
|
|
338
|
+
if (!this.world?.world || !entity.body) return null;
|
|
339
|
+
const mode = options.mode ?? "any";
|
|
340
|
+
const offsets = mode === "center" ? (options.offsets ?? DEFAULT_OFFSETS).slice(0, 1) : options.offsets ?? DEFAULT_OFFSETS;
|
|
341
|
+
const translation = entity.body.translation();
|
|
342
|
+
const rays = this.getOrCreateRays(entity.uuid, offsets.length);
|
|
343
|
+
const originYOffset = options.originYOffset ?? 0;
|
|
344
|
+
let support = null;
|
|
345
|
+
for (let index = 0; index < offsets.length; index++) {
|
|
346
|
+
const offset = offsets[index];
|
|
347
|
+
const ray = rays[index];
|
|
348
|
+
ray.origin = {
|
|
349
|
+
x: translation.x + offset.x,
|
|
350
|
+
y: translation.y + originYOffset,
|
|
351
|
+
z: translation.z + offset.z
|
|
352
|
+
};
|
|
353
|
+
ray.dir = { x: 0, y: -1, z: 0 };
|
|
354
|
+
const hit = this.world.world.castRay(
|
|
355
|
+
ray,
|
|
356
|
+
options.rayLength,
|
|
357
|
+
true,
|
|
358
|
+
void 0,
|
|
359
|
+
void 0,
|
|
360
|
+
void 0,
|
|
361
|
+
entity.body
|
|
362
|
+
);
|
|
363
|
+
if (!hit) continue;
|
|
364
|
+
const nextSupport = {
|
|
365
|
+
toi: hit.toi,
|
|
366
|
+
point: {
|
|
367
|
+
x: ray.origin.x + ray.dir.x * hit.toi,
|
|
368
|
+
y: ray.origin.y + ray.dir.y * hit.toi,
|
|
369
|
+
z: ray.origin.z + ray.dir.z * hit.toi
|
|
370
|
+
},
|
|
371
|
+
origin: {
|
|
372
|
+
x: ray.origin.x,
|
|
373
|
+
y: ray.origin.y,
|
|
374
|
+
z: ray.origin.z
|
|
375
|
+
},
|
|
376
|
+
rayIndex: index,
|
|
377
|
+
colliderUuid: hit.collider?._parent?.userData?.uuid
|
|
378
|
+
};
|
|
379
|
+
if (mode === "center") {
|
|
380
|
+
support = nextSupport;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
if (!support || nextSupport.toi < support.toi) {
|
|
384
|
+
support = nextSupport;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (options.debug && options.scene) {
|
|
388
|
+
this.updateDebugLines(
|
|
389
|
+
entity.uuid,
|
|
390
|
+
rays,
|
|
391
|
+
Boolean(support),
|
|
392
|
+
options.rayLength,
|
|
393
|
+
options.scene
|
|
394
|
+
);
|
|
395
|
+
} else {
|
|
396
|
+
this.disposeDebugLines(entity.uuid);
|
|
397
|
+
}
|
|
398
|
+
return support;
|
|
399
|
+
}
|
|
400
|
+
detect(entity, options) {
|
|
401
|
+
return this.probeSupport(entity, options) != null;
|
|
402
|
+
}
|
|
403
|
+
destroyEntity(uuid) {
|
|
404
|
+
this.rays.delete(uuid);
|
|
405
|
+
this.disposeDebugLines(uuid);
|
|
406
|
+
}
|
|
407
|
+
destroy() {
|
|
408
|
+
this.rays.clear();
|
|
409
|
+
for (const uuid of this.debugLines.keys()) {
|
|
410
|
+
this.disposeDebugLines(uuid);
|
|
411
|
+
}
|
|
412
|
+
this.debugLines.clear();
|
|
413
|
+
}
|
|
414
|
+
getOrCreateRays(uuid, count) {
|
|
415
|
+
let rays = this.rays.get(uuid);
|
|
416
|
+
if (!rays || rays.length !== count) {
|
|
417
|
+
rays = Array.from(
|
|
418
|
+
{ length: count },
|
|
419
|
+
() => new Ray({ x: 0, y: 0, z: 0 }, { x: 0, y: -1, z: 0 })
|
|
420
|
+
);
|
|
421
|
+
this.rays.set(uuid, rays);
|
|
422
|
+
}
|
|
423
|
+
return rays;
|
|
424
|
+
}
|
|
425
|
+
updateDebugLines(uuid, rays, hasGround, length, scene) {
|
|
426
|
+
let lines = this.debugLines.get(uuid);
|
|
427
|
+
if (!lines) {
|
|
428
|
+
lines = rays.map(() => {
|
|
429
|
+
const geometry = new BufferGeometry().setFromPoints([
|
|
430
|
+
new Vector3(),
|
|
431
|
+
new Vector3()
|
|
432
|
+
]);
|
|
433
|
+
const material = new LineBasicMaterial({ color: 16711680 });
|
|
434
|
+
const line = new Line(geometry, material);
|
|
435
|
+
scene.add(line);
|
|
436
|
+
return line;
|
|
437
|
+
});
|
|
438
|
+
this.debugLines.set(uuid, lines);
|
|
439
|
+
}
|
|
440
|
+
rays.forEach((ray, index) => {
|
|
441
|
+
const line = lines[index];
|
|
442
|
+
const start = new Vector3(ray.origin.x, ray.origin.y, ray.origin.z);
|
|
443
|
+
const end = new Vector3(
|
|
444
|
+
ray.origin.x + ray.dir.x * length,
|
|
445
|
+
ray.origin.y + ray.dir.y * length,
|
|
446
|
+
ray.origin.z + ray.dir.z * length
|
|
447
|
+
);
|
|
448
|
+
line.visible = true;
|
|
449
|
+
line.geometry.setFromPoints([start, end]);
|
|
450
|
+
line.material.color.setHex(
|
|
451
|
+
hasGround ? 65280 : 16711680
|
|
452
|
+
);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
disposeDebugLines(uuid) {
|
|
456
|
+
const lines = this.debugLines.get(uuid);
|
|
457
|
+
if (!lines) return;
|
|
458
|
+
for (const line of lines) {
|
|
459
|
+
line.removeFromParent();
|
|
460
|
+
line.geometry.dispose();
|
|
461
|
+
line.material.dispose();
|
|
462
|
+
}
|
|
463
|
+
this.debugLines.delete(uuid);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
function getGroundAnchorOffsetY(entity) {
|
|
467
|
+
const runtimeColliderDesc = entity?.colliderDesc;
|
|
468
|
+
if (runtimeColliderDesc) {
|
|
469
|
+
const serialized = serializeColliderDesc(runtimeColliderDesc);
|
|
470
|
+
const centerY2 = serialized.translation?.[1] ?? 0;
|
|
471
|
+
if (serialized.shape === "capsule" && serialized.dimensions.length >= 2) {
|
|
472
|
+
const halfCylinder = serialized.dimensions[0] ?? 0;
|
|
473
|
+
const radius = serialized.dimensions[1] ?? 0;
|
|
474
|
+
return halfCylinder + radius - centerY2;
|
|
475
|
+
}
|
|
476
|
+
if (serialized.shape === "cuboid" && serialized.dimensions.length >= 2) {
|
|
477
|
+
const halfHeight = serialized.dimensions[1] ?? 0;
|
|
478
|
+
return halfHeight - centerY2;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const collisionSize = entity?.options?.collision?.size ?? entity?.options?.collisionSize ?? entity?.options?.size;
|
|
482
|
+
const height = collisionSize?.y ?? 0;
|
|
483
|
+
if (height <= 0) {
|
|
484
|
+
return 0;
|
|
485
|
+
}
|
|
486
|
+
const collisionPosition = entity?.options?.collision?.position ?? entity?.options?.collisionPosition;
|
|
487
|
+
const centerY = collisionPosition?.y ?? height / 2;
|
|
488
|
+
return height / 2 - centerY;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/lib/behaviors/platformer-3d/platformer-3d.behavior.ts
|
|
492
|
+
var Platformer3DBehavior = class {
|
|
493
|
+
world;
|
|
494
|
+
scene;
|
|
495
|
+
groundProbe;
|
|
496
|
+
constructor(world, scene) {
|
|
497
|
+
this.world = world;
|
|
498
|
+
this.scene = scene;
|
|
499
|
+
this.groundProbe = new GroundProbe3D(world);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Apply horizontal movement based on input
|
|
503
|
+
*/
|
|
504
|
+
applyMovement(entity, delta) {
|
|
505
|
+
const input = entity.$platformer;
|
|
506
|
+
const movement = entity.platformer;
|
|
507
|
+
const state = entity.platformerState;
|
|
508
|
+
const speed = input.run ? movement.runSpeed : movement.walkSpeed;
|
|
509
|
+
state.currentSpeed = speed;
|
|
510
|
+
const moveX = input.moveX * speed;
|
|
511
|
+
const moveZ = input.moveZ * speed;
|
|
512
|
+
const currentVel = entity.body.linvel();
|
|
513
|
+
entity.transformStore.velocity.x = moveX;
|
|
514
|
+
entity.transformStore.velocity.y = currentVel.y;
|
|
515
|
+
entity.transformStore.velocity.z = moveZ;
|
|
516
|
+
entity.transformStore.dirty.velocity = true;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Handle jump logic with multi-jump support
|
|
520
|
+
*/
|
|
521
|
+
/**
|
|
522
|
+
* Handle jump logic with multi-jump, coyote time, and buffering
|
|
523
|
+
*/
|
|
524
|
+
handleJump(entity, delta) {
|
|
525
|
+
const input = entity.$platformer;
|
|
526
|
+
const movement = entity.platformer;
|
|
527
|
+
const state = entity.platformerState;
|
|
528
|
+
if (state.jumping || state.falling) {
|
|
529
|
+
state.timeSinceJump += delta;
|
|
530
|
+
}
|
|
531
|
+
if (!input.jump && state.jumpHeld) {
|
|
532
|
+
state.jumpReleasedSinceLastJump = true;
|
|
533
|
+
}
|
|
534
|
+
if (input.jump && !state.jumpPressedLastFrame) {
|
|
535
|
+
state.jumpBuffered = true;
|
|
536
|
+
state.jumpBufferTimer = movement.jumpBufferTime;
|
|
537
|
+
}
|
|
538
|
+
state.jumpPressedLastFrame = input.jump;
|
|
539
|
+
state.jumpHeld = input.jump;
|
|
540
|
+
if (state.jumpBuffered) {
|
|
541
|
+
state.jumpBufferTimer -= delta;
|
|
542
|
+
if (state.jumpBufferTimer <= 0) {
|
|
543
|
+
state.jumpBuffered = false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const minTimeBeforeCut = 0.1;
|
|
547
|
+
const canApplyCut = state.timeSinceJump >= minTimeBeforeCut;
|
|
548
|
+
if (!input.jump && state.jumping && !state.jumpCutApplied && canApplyCut) {
|
|
549
|
+
const velocity = entity.body.linvel();
|
|
550
|
+
entity.transformStore.velocity.y = velocity.y * movement.jumpCutMultiplier;
|
|
551
|
+
entity.transformStore.dirty.velocity = true;
|
|
552
|
+
state.jumpCutApplied = true;
|
|
553
|
+
}
|
|
554
|
+
if (!state.jumpBuffered) return;
|
|
555
|
+
const inCoyoteWindow = !state.grounded && state.timeSinceGrounded <= movement.coyoteTime;
|
|
556
|
+
const isFirstJump = state.grounded || inCoyoteWindow && state.jumpCount === 0;
|
|
557
|
+
const hasJumpsRemaining = state.jumpCount < movement.maxJumps;
|
|
558
|
+
const buttonReleased = state.jumpReleasedSinceLastJump;
|
|
559
|
+
const inMultiJumpWindow = state.timeSinceJump >= movement.multiJumpWindowTime;
|
|
560
|
+
const canMultiJump = !state.grounded && hasJumpsRemaining && buttonReleased && inMultiJumpWindow;
|
|
561
|
+
if (isFirstJump || canMultiJump) {
|
|
562
|
+
state.jumpBuffered = false;
|
|
563
|
+
state.jumpCount++;
|
|
564
|
+
state.jumpReleasedSinceLastJump = false;
|
|
565
|
+
state.timeSinceJump = 0;
|
|
566
|
+
state.jumpStartHeight = entity.body.translation().y;
|
|
567
|
+
state.jumping = true;
|
|
568
|
+
state.falling = false;
|
|
569
|
+
state.jumpCutApplied = false;
|
|
570
|
+
entity.transformStore.velocity.y = movement.jumpForce;
|
|
571
|
+
entity.transformStore.dirty.velocity = true;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Apply gravity when not grounded
|
|
576
|
+
*/
|
|
577
|
+
applyGravity(entity, delta) {
|
|
578
|
+
const movement = entity.platformer;
|
|
579
|
+
const state = entity.platformerState;
|
|
580
|
+
if (state.grounded) return;
|
|
581
|
+
if (state.jumping && state.timeSinceJump < 0.01) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const currentVel = entity.body.linvel();
|
|
585
|
+
const newYVelocity = currentVel.y - movement.gravity * delta;
|
|
586
|
+
entity.transformStore.velocity.y = newYVelocity;
|
|
587
|
+
entity.transformStore.dirty.velocity = true;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Update entity state based on physics
|
|
591
|
+
*/
|
|
592
|
+
updateState(entity, delta) {
|
|
593
|
+
const state = entity.platformerState;
|
|
594
|
+
const wasGrounded = state.grounded;
|
|
595
|
+
const velocity = entity.body.linvel();
|
|
596
|
+
let isGrounded = false;
|
|
597
|
+
const isAirborne = state.jumping || state.falling;
|
|
598
|
+
if (isAirborne) {
|
|
599
|
+
const probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(entity);
|
|
600
|
+
const nearGround = this.groundProbe.detect(entity, {
|
|
601
|
+
rayLength: entity.platformer.groundRayLength,
|
|
602
|
+
mode: "any",
|
|
603
|
+
debug: entity.platformer.debugGroundProbe,
|
|
604
|
+
scene: this.scene,
|
|
605
|
+
originYOffset: probeOriginYOffset
|
|
606
|
+
});
|
|
607
|
+
const canLand = state.falling && !state.jumping;
|
|
608
|
+
const hasLanded = Math.abs(velocity.y) < 0.5;
|
|
609
|
+
isGrounded = nearGround && canLand && hasLanded;
|
|
610
|
+
} else {
|
|
611
|
+
const probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(entity);
|
|
612
|
+
const nearGround = this.groundProbe.detect(entity, {
|
|
613
|
+
rayLength: entity.platformer.groundRayLength,
|
|
614
|
+
mode: "any",
|
|
615
|
+
debug: entity.platformer.debugGroundProbe,
|
|
616
|
+
scene: this.scene,
|
|
617
|
+
originYOffset: probeOriginYOffset
|
|
618
|
+
});
|
|
619
|
+
const notFallingFast = velocity.y > -2;
|
|
620
|
+
isGrounded = nearGround && notFallingFast;
|
|
621
|
+
}
|
|
622
|
+
state.grounded = isGrounded;
|
|
623
|
+
if (state.grounded) {
|
|
624
|
+
state.timeSinceGrounded = 0;
|
|
625
|
+
state.lastGroundedY = entity.body.translation().y;
|
|
626
|
+
} else {
|
|
627
|
+
state.timeSinceGrounded += delta;
|
|
628
|
+
}
|
|
629
|
+
if (!wasGrounded && state.grounded) {
|
|
630
|
+
state.jumpCount = 0;
|
|
631
|
+
state.jumping = false;
|
|
632
|
+
state.falling = false;
|
|
633
|
+
state.jumpCutApplied = false;
|
|
634
|
+
}
|
|
635
|
+
if (velocity.y < -0.1 && !state.grounded) {
|
|
636
|
+
if (state.jumping && velocity.y < 0) {
|
|
637
|
+
state.jumping = false;
|
|
638
|
+
state.falling = true;
|
|
639
|
+
} else if (!state.jumping) {
|
|
640
|
+
state.falling = true;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Update one platformer entity.
|
|
646
|
+
*/
|
|
647
|
+
updateEntity(entity, delta) {
|
|
648
|
+
if (!entity.platformer || !entity.$platformer || !entity.platformerState) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const platformerEntity = entity;
|
|
652
|
+
this.updateState(platformerEntity, delta);
|
|
653
|
+
this.applyMovement(platformerEntity, delta);
|
|
654
|
+
this.handleJump(platformerEntity, delta);
|
|
655
|
+
this.applyGravity(platformerEntity, delta);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Update all platformer entities.
|
|
659
|
+
*/
|
|
660
|
+
update(delta) {
|
|
661
|
+
if (!this.world?.collisionMap) return;
|
|
662
|
+
for (const [, entity] of this.world.collisionMap) {
|
|
663
|
+
this.updateEntity(entity, delta);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Cleanup
|
|
668
|
+
*/
|
|
669
|
+
destroy() {
|
|
670
|
+
this.groundProbe.destroy();
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// src/lib/behaviors/behavior-descriptor.ts
|
|
675
|
+
function defineBehavior(config) {
|
|
676
|
+
return {
|
|
677
|
+
key: /* @__PURE__ */ Symbol.for(`zylem:behavior:${config.name}`),
|
|
678
|
+
defaultOptions: config.defaultOptions,
|
|
679
|
+
systemFactory: config.systemFactory,
|
|
680
|
+
createHandle: config.createHandle
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/lib/behaviors/platformer-3d/platformer-3d.descriptor.ts
|
|
685
|
+
var defaultOptions = {
|
|
686
|
+
walkSpeed: 12,
|
|
687
|
+
runSpeed: 24,
|
|
688
|
+
jumpForce: 12,
|
|
689
|
+
maxJumps: 1,
|
|
690
|
+
gravity: 9.82,
|
|
691
|
+
groundRayLength: 1,
|
|
692
|
+
debugGroundProbe: false
|
|
693
|
+
};
|
|
694
|
+
var PLATFORMER_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:platformer-3d");
|
|
695
|
+
var Platformer3DBehaviorSystem = class {
|
|
696
|
+
constructor(world, scene, getBehaviorLinks) {
|
|
697
|
+
this.world = world;
|
|
698
|
+
this.scene = scene;
|
|
699
|
+
this.getBehaviorLinks = getBehaviorLinks;
|
|
700
|
+
this.movementBehavior = new Platformer3DBehavior(world, scene);
|
|
701
|
+
}
|
|
702
|
+
movementBehavior;
|
|
703
|
+
update(_ecs, delta) {
|
|
704
|
+
const links = this.getBehaviorLinks?.(PLATFORMER_BEHAVIOR_KEY);
|
|
705
|
+
if (!links) return;
|
|
706
|
+
for (const link of links) {
|
|
707
|
+
const gameEntity = link.entity;
|
|
708
|
+
const platformerRef = link.ref;
|
|
709
|
+
if (!gameEntity.body) continue;
|
|
710
|
+
const options = platformerRef.options;
|
|
711
|
+
if (!gameEntity.platformer) {
|
|
712
|
+
gameEntity.platformer = createPlatformer3DMovementComponent(options);
|
|
713
|
+
}
|
|
714
|
+
if (!gameEntity.$platformer) {
|
|
715
|
+
gameEntity.$platformer = createPlatformer3DInputComponent();
|
|
716
|
+
}
|
|
717
|
+
if (!gameEntity.platformerState) {
|
|
718
|
+
gameEntity.platformerState = createPlatformer3DStateComponent();
|
|
719
|
+
}
|
|
720
|
+
if (!platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {
|
|
721
|
+
platformerRef.fsm = new Platformer3DFSM({
|
|
722
|
+
input: gameEntity.$platformer,
|
|
723
|
+
state: gameEntity.platformerState
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (platformerRef.fsm && gameEntity.$platformer && gameEntity.platformerState) {
|
|
727
|
+
platformerRef.fsm.update(gameEntity.$platformer, gameEntity.platformerState);
|
|
728
|
+
}
|
|
729
|
+
this.movementBehavior.updateEntity(gameEntity, delta);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
destroy(_ecs) {
|
|
733
|
+
this.movementBehavior.destroy();
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
var Platformer3DBehavior2 = defineBehavior({
|
|
737
|
+
name: "platformer-3d",
|
|
738
|
+
defaultOptions,
|
|
739
|
+
systemFactory: (ctx) => new Platformer3DBehaviorSystem(
|
|
740
|
+
ctx.world,
|
|
741
|
+
ctx.scene,
|
|
742
|
+
ctx.getBehaviorLinks
|
|
743
|
+
),
|
|
744
|
+
createHandle: (ref) => ({
|
|
745
|
+
getState: () => ref.fsm?.getState() ?? "idle" /* Idle */,
|
|
746
|
+
isGrounded: () => ref.fsm?.isGrounded() ?? false,
|
|
747
|
+
getJumpCount: () => ref.fsm?.getJumpCount() ?? 0,
|
|
748
|
+
onPlatformCollision: (ctx) => ref.fsm?.handleCollision(ctx)
|
|
749
|
+
})
|
|
750
|
+
});
|
|
751
|
+
export {
|
|
752
|
+
Platformer3DBehavior2 as Platformer3DBehavior,
|
|
753
|
+
Platformer3DEvent,
|
|
754
|
+
Platformer3DFSM,
|
|
755
|
+
Platformer3DBehavior as Platformer3DMovementBehavior,
|
|
756
|
+
Platformer3DState,
|
|
757
|
+
createPlatformer3DInputComponent,
|
|
758
|
+
createPlatformer3DMovementComponent,
|
|
759
|
+
createPlatformer3DStateComponent
|
|
760
|
+
};
|
|
761
|
+
//# sourceMappingURL=platformer-3d.js.map
|