@fonsecabarreto/genesis-gl-core 0.1.0 → 0.1.1
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/README.md +19 -2
- package/dist/{Camera-DY_8gx3C.d.ts → Camera-CJVYy9fH.d.ts} +13 -2
- package/dist/Core/classes/Material.d.ts +1 -1
- package/dist/Core/classes/Material.js +1 -1
- package/dist/Core/classes/Model.d.ts +3 -3
- package/dist/Core/classes/Model.js +1 -1
- package/dist/Core/classes/Renderer.d.ts +11 -5
- package/dist/Core/classes/Renderer.js +4 -4
- package/dist/Core/classes/Scene.d.ts +2 -2
- package/dist/Core/classes/Viewport.d.ts +1 -1
- package/dist/Core/classes/Viewport.js +1 -1
- package/dist/Core/index.d.ts +4 -4
- package/dist/Core/index.js +4 -4
- package/dist/Core/utils/load-glb.d.ts +3 -3
- package/dist/Core/utils/load-glb.js +4 -4
- package/dist/Core/utils/parse-obj.d.ts +2 -2
- package/dist/Core/utils/parse-obj.js +4 -4
- package/dist/Editor/index.d.ts +126 -15
- package/dist/Editor/index.js +471 -74
- package/dist/Editor/index.js.map +1 -1
- package/dist/Game/controls/KeyboardInput.d.ts +4 -4
- package/dist/Game/index.d.ts +308 -7
- package/dist/Game/index.js +470 -24
- package/dist/Game/index.js.map +1 -1
- package/dist/{KeyboardInput-DTsfj3tE.d.ts → KeyboardInput-1xOAabI0.d.ts} +95 -14
- package/dist/{Material-BGLkldxv.d.ts → Material-DhwSRbP2.d.ts} +8 -0
- package/dist/{Model-CQvDXd-b.d.ts → Model-BBZHnUp1.d.ts} +24 -8
- package/dist/{chunk-6LS6AO5H.js → chunk-L66K4AZU.js} +36 -30
- package/dist/chunk-L66K4AZU.js.map +1 -0
- package/dist/{chunk-JK2HEZAT.js → chunk-QOAQVTAB.js} +26 -22
- package/dist/chunk-QOAQVTAB.js.map +1 -0
- package/dist/{chunk-5TAAXI6S.js → chunk-XMW2MS66.js} +39 -16
- package/dist/chunk-XMW2MS66.js.map +1 -0
- package/dist/{chunk-QCQVJCSR.js → chunk-ZCJ3MJZD.js} +103 -67
- package/dist/chunk-ZCJ3MJZD.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-5TAAXI6S.js.map +0 -1
- package/dist/chunk-6LS6AO5H.js.map +0 -1
- package/dist/chunk-JK2HEZAT.js.map +0 -1
- package/dist/chunk-QCQVJCSR.js.map +0 -1
package/dist/Game/index.js
CHANGED
|
@@ -3,14 +3,22 @@ import {
|
|
|
3
3
|
} from "../chunk-P7QOKDLY.js";
|
|
4
4
|
import "../chunk-SUNYSY45.js";
|
|
5
5
|
import {
|
|
6
|
-
Mesh
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Mesh,
|
|
7
|
+
Renderer,
|
|
8
|
+
WebGLCore
|
|
9
|
+
} from "../chunk-ZCJ3MJZD.js";
|
|
10
|
+
import {
|
|
11
|
+
Material
|
|
12
|
+
} from "../chunk-L66K4AZU.js";
|
|
9
13
|
import {
|
|
10
14
|
Model
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import
|
|
13
|
-
|
|
15
|
+
} from "../chunk-QOAQVTAB.js";
|
|
16
|
+
import {
|
|
17
|
+
Scene
|
|
18
|
+
} from "../chunk-3ULETMWF.js";
|
|
19
|
+
import {
|
|
20
|
+
Viewport
|
|
21
|
+
} from "../chunk-XMW2MS66.js";
|
|
14
22
|
|
|
15
23
|
// src/Game/classes/AnimationController.ts
|
|
16
24
|
var AnimationController = class {
|
|
@@ -76,32 +84,96 @@ var AnimationController = class {
|
|
|
76
84
|
}
|
|
77
85
|
};
|
|
78
86
|
|
|
79
|
-
// src/Game/classes/
|
|
80
|
-
var
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
// src/Game/classes/PhysicsBody.ts
|
|
88
|
+
var PhysicsBody = class extends Model {
|
|
89
|
+
// ── Linear dynamics ───────────────────────────────────────────────────────
|
|
90
|
+
/** World-space velocity in units/second (XYZ). */
|
|
91
|
+
velocity = [0, 0, 0];
|
|
92
|
+
/**
|
|
93
|
+
* When `true` the owning physics step should add gravity to `velocity[1]`
|
|
94
|
+
* every tick. Set to `false` for server-driven actors whose Y position is
|
|
95
|
+
* authoritative from the network and must not be locally simulated.
|
|
96
|
+
*/
|
|
97
|
+
useGravity = true;
|
|
98
|
+
// ── Surface interaction ───────────────────────────────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Friction coefficient of the surface this body is currently standing on.
|
|
101
|
+
* Updated on landing from the collided model's material.
|
|
102
|
+
* Range: 0 (pure ice) → 1 (instant stop).
|
|
103
|
+
*/
|
|
85
104
|
surfaceFriction = 0.3;
|
|
86
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Horizontal damping applied while airborne.
|
|
107
|
+
* Kept low so air strafing feels natural and not floaty.
|
|
108
|
+
*/
|
|
87
109
|
airFriction = 0.02;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
110
|
+
// ── Mass & restitution ────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Mass in kilograms. Used to distribute impulses proportionally when two
|
|
113
|
+
* dynamic bodies collide: heavier objects are displaced and accelerated
|
|
114
|
+
* less. Default: `1.0`. Set higher for heavy objects (e.g. boulders) or
|
|
115
|
+
* lower for light ones (e.g. beach balls).
|
|
116
|
+
*/
|
|
117
|
+
mass = 1;
|
|
118
|
+
/**
|
|
119
|
+
* Coefficient of restitution — fraction of relative velocity preserved
|
|
120
|
+
* along the collision normal. Range: 0 (perfectly inelastic) → 1 (fully
|
|
121
|
+
* elastic). Default: `0` (rigid body, no bounce). `Ball` overrides this
|
|
122
|
+
* to `0.6`. When two bodies collide the effective restitution is the
|
|
123
|
+
* geometric mean of both values.
|
|
124
|
+
*/
|
|
125
|
+
restitution = 0;
|
|
126
|
+
// ── Ground state ──────────────────────────────────────────────────────────
|
|
91
127
|
_isGrounded = false;
|
|
92
|
-
/** Setting to true (landing) resets the jump counter. */
|
|
93
128
|
get isGrounded() {
|
|
94
129
|
return this._isGrounded;
|
|
95
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Setting to `true` when the previous state was `false` triggers
|
|
133
|
+
* the `onLand()` hook — override in subclasses for gameplay responses
|
|
134
|
+
* (e.g. landing roll, jump counter reset).
|
|
135
|
+
*/
|
|
96
136
|
set isGrounded(v) {
|
|
97
|
-
|
|
98
|
-
const hardLanding = this.velocity[1] <= this.minLandingImpact;
|
|
99
|
-
if (this.isInputActive && hardLanding) this._landingRoll = true;
|
|
100
|
-
this._jumpsUsed = 0;
|
|
101
|
-
this.isDoubleJumping = false;
|
|
102
|
-
}
|
|
137
|
+
const wasGrounded = this._isGrounded;
|
|
103
138
|
this._isGrounded = v;
|
|
139
|
+
if (v && !wasGrounded) {
|
|
140
|
+
this.onLand();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ── Constructor ───────────────────────────────────────────────────────────
|
|
144
|
+
constructor(meshes, translation = [0, 0, 0], scale = [1, 1, 1], name = "physicsBody", skeleton = null) {
|
|
145
|
+
super(meshes, translation, scale, name, skeleton);
|
|
146
|
+
}
|
|
147
|
+
// ── Virtual hooks ─────────────────────────────────────────────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Called once when this body transitions from airborne to grounded.
|
|
150
|
+
* Override to add gameplay responses (landing roll, sound, VFX, etc.).
|
|
151
|
+
* Base implementation does nothing.
|
|
152
|
+
*/
|
|
153
|
+
onLand() {
|
|
104
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Called by the collision system when a vertical (Y-axis) surface is hit.
|
|
157
|
+
* Receives the velocity **at the moment of impact** so subclasses can
|
|
158
|
+
* compute a bounce impulse before the caller updates `velocity[1]`.
|
|
159
|
+
*
|
|
160
|
+
* Default: zero out vertical velocity (rigid body, no bounce).
|
|
161
|
+
* Override in `Ball` (or any bouncy actor) to apply a restitution impulse.
|
|
162
|
+
*
|
|
163
|
+
* @param impactVy `velocity[1]` captured just before the collision resolve.
|
|
164
|
+
*/
|
|
165
|
+
resolveYCollision(impactVy) {
|
|
166
|
+
void impactVy;
|
|
167
|
+
this.velocity[1] = 0;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// src/Game/classes/Character.ts
|
|
172
|
+
var Character = class extends PhysicsBody {
|
|
173
|
+
id;
|
|
174
|
+
health = 100;
|
|
175
|
+
speed = 0.2;
|
|
176
|
+
stamina = 10;
|
|
105
177
|
/** Pending auto-roll on landing after a double jump. */
|
|
106
178
|
_landingRoll = false;
|
|
107
179
|
/** Minimum downward impact speed (negative Y velocity) required to trigger
|
|
@@ -136,6 +208,16 @@ var Character = class extends Model {
|
|
|
136
208
|
// seconds
|
|
137
209
|
_jumpsUsed = 0;
|
|
138
210
|
maxJumps = 2;
|
|
211
|
+
/**
|
|
212
|
+
* Called by {@link PhysicsBody.isGrounded} setter on every landing.
|
|
213
|
+
* Queues a landing roll if the impact was hard enough and resets jump state.
|
|
214
|
+
*/
|
|
215
|
+
onLand() {
|
|
216
|
+
const hardLanding = this.velocity[1] <= this.minLandingImpact;
|
|
217
|
+
if (this.isInputActive && hardLanding) this._landingRoll = true;
|
|
218
|
+
this._jumpsUsed = 0;
|
|
219
|
+
this.isDoubleJumping = false;
|
|
220
|
+
}
|
|
139
221
|
constructor(id, baseModel) {
|
|
140
222
|
super(
|
|
141
223
|
baseModel.meshes,
|
|
@@ -342,12 +424,376 @@ var Leg = class extends Mesh {
|
|
|
342
424
|
}
|
|
343
425
|
}
|
|
344
426
|
};
|
|
427
|
+
|
|
428
|
+
// src/Game/classes/Actor.ts
|
|
429
|
+
var Actor = class extends Model {
|
|
430
|
+
constructor(meshes, translation = [0, 0, 0], scale = [1, 1, 1], name = "actor", skeleton = null) {
|
|
431
|
+
super(meshes, translation, scale, name, skeleton);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// src/Core/utils/create-sphere.ts
|
|
436
|
+
function createSphereMesh(webglCore, radius = 1, latBands = 12, lonBands = 12, color = [1, 1, 1]) {
|
|
437
|
+
const positions = [];
|
|
438
|
+
const normals = [];
|
|
439
|
+
for (let lat = 0; lat <= latBands; lat++) {
|
|
440
|
+
const theta = lat * Math.PI / latBands;
|
|
441
|
+
const sinTheta = Math.sin(theta);
|
|
442
|
+
const cosTheta = Math.cos(theta);
|
|
443
|
+
for (let lon = 0; lon <= lonBands; lon++) {
|
|
444
|
+
const phi = lon * 2 * Math.PI / lonBands;
|
|
445
|
+
const sinPhi = Math.sin(phi);
|
|
446
|
+
const cosPhi = Math.cos(phi);
|
|
447
|
+
const x = cosPhi * sinTheta;
|
|
448
|
+
const y = cosTheta;
|
|
449
|
+
const z = sinPhi * sinTheta;
|
|
450
|
+
positions.push(radius * x, radius * y, radius * z);
|
|
451
|
+
normals.push(x, y, z);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const indices = [];
|
|
455
|
+
for (let lat = 0; lat < latBands; lat++) {
|
|
456
|
+
for (let lon = 0; lon < lonBands; lon++) {
|
|
457
|
+
const first = lat * (lonBands + 1) + lon;
|
|
458
|
+
const second = first + lonBands + 1;
|
|
459
|
+
indices.push(first, second, first + 1);
|
|
460
|
+
indices.push(second, second + 1, first + 1);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const vertices = [];
|
|
464
|
+
const finalNormals = [];
|
|
465
|
+
for (const idx of indices) {
|
|
466
|
+
vertices.push(
|
|
467
|
+
positions[idx * 3],
|
|
468
|
+
positions[idx * 3 + 1],
|
|
469
|
+
positions[idx * 3 + 2]
|
|
470
|
+
);
|
|
471
|
+
finalNormals.push(
|
|
472
|
+
normals[idx * 3],
|
|
473
|
+
normals[idx * 3 + 1],
|
|
474
|
+
normals[idx * 3 + 2]
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
const material = new Material(webglCore, {
|
|
478
|
+
albedoColor: [color[0], color[1], color[2], 1],
|
|
479
|
+
unlit: true
|
|
480
|
+
// lights always glow
|
|
481
|
+
});
|
|
482
|
+
return new Mesh(
|
|
483
|
+
"sphere",
|
|
484
|
+
new Float32Array(vertices),
|
|
485
|
+
new Float32Array(finalNormals),
|
|
486
|
+
material
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/Game/classes/InteractiveActor.ts
|
|
491
|
+
var InteractiveActor = class _InteractiveActor extends PhysicsBody {
|
|
492
|
+
/**
|
|
493
|
+
* Minimum impact speed (|vy|) required to trigger a visible bounce.
|
|
494
|
+
* Below this threshold the actor comes to rest instead of micro-bouncing.
|
|
495
|
+
* Default: `0.05`.
|
|
496
|
+
*/
|
|
497
|
+
minBounceSpeed = 0.05;
|
|
498
|
+
constructor(meshes, position = [0, 0, 0], name = "interactiveActor") {
|
|
499
|
+
super(meshes, position, [1, 1, 1], name);
|
|
500
|
+
}
|
|
501
|
+
// ── Static factories ──────────────────────────────────────────────────────
|
|
502
|
+
/**
|
|
503
|
+
* Create an {@link InteractiveActor} with a procedurally generated sphere
|
|
504
|
+
* mesh — convenience shorthand for the common "physics ball" use case.
|
|
505
|
+
*
|
|
506
|
+
* @param webglCore WebGL context used to build the mesh buffers.
|
|
507
|
+
* @param material Material applied to the sphere.
|
|
508
|
+
* @param radius Sphere radius in world units. Default `0.5`.
|
|
509
|
+
* @param position Initial world position. Default origin.
|
|
510
|
+
* @param latBands Latitude subdivisions. Default `16`.
|
|
511
|
+
* @param lonBands Longitude subdivisions. Default `16`.
|
|
512
|
+
*/
|
|
513
|
+
static sphere(webglCore, material, radius = 0.5, position = [0, 0, 0], latBands = 16, lonBands = 16) {
|
|
514
|
+
const mesh = createSphereMesh(
|
|
515
|
+
webglCore,
|
|
516
|
+
radius,
|
|
517
|
+
latBands,
|
|
518
|
+
lonBands,
|
|
519
|
+
material.albedoColor?.slice(0, 3) ?? [1, 1, 1]
|
|
520
|
+
);
|
|
521
|
+
mesh.material = material;
|
|
522
|
+
const actor = new _InteractiveActor([mesh], position, "ball");
|
|
523
|
+
actor.restitution = 0.6;
|
|
524
|
+
actor.surfaceFriction = 0.05;
|
|
525
|
+
return actor;
|
|
526
|
+
}
|
|
527
|
+
// ── PhysicsBody hooks ─────────────────────────────────────────────────────
|
|
528
|
+
/**
|
|
529
|
+
* Apply a bounce impulse instead of zeroing Y velocity.
|
|
530
|
+
* The collision system passes the velocity captured just before impact so
|
|
531
|
+
* the correct outgoing speed can be computed from {@link restitution}.
|
|
532
|
+
*/
|
|
533
|
+
resolveYCollision(impactVy) {
|
|
534
|
+
if (Math.abs(impactVy) > this.minBounceSpeed) {
|
|
535
|
+
this.velocity[1] = -impactVy * this.restitution;
|
|
536
|
+
this._isGrounded = false;
|
|
537
|
+
} else {
|
|
538
|
+
this.velocity[1] = 0;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// ── Update ────────────────────────────────────────────────────────────────
|
|
542
|
+
/**
|
|
543
|
+
* Advance one physics tick: integrate velocity, apply friction, zero
|
|
544
|
+
* sub-threshold components so the actor eventually comes to rest.
|
|
545
|
+
*/
|
|
546
|
+
update(deltaTime) {
|
|
547
|
+
super.update(deltaTime);
|
|
548
|
+
if (this._isGrounded) {
|
|
549
|
+
const friction = Math.min(0.9, this.surfaceFriction + 5e-3);
|
|
550
|
+
this.velocity[0] += (0 - this.velocity[0]) * friction;
|
|
551
|
+
this.velocity[2] += (0 - this.velocity[2]) * friction;
|
|
552
|
+
} else {
|
|
553
|
+
this.velocity[0] *= 1 - this.airFriction;
|
|
554
|
+
this.velocity[2] *= 1 - this.airFriction;
|
|
555
|
+
}
|
|
556
|
+
this.move(this.velocity[0], this.velocity[1], this.velocity[2]);
|
|
557
|
+
if (Math.abs(this.velocity[0]) < 1e-3) this.velocity[0] = 0;
|
|
558
|
+
if (Math.abs(this.velocity[1]) < 1e-3) this.velocity[1] = 0;
|
|
559
|
+
if (Math.abs(this.velocity[2]) < 1e-3) this.velocity[2] = 0;
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// src/Game/GameLoopRunner.ts
|
|
564
|
+
var GameLoopRunner = class {
|
|
565
|
+
/**
|
|
566
|
+
* @param targetFPS Number of fixed-update ticks per second. Defaults to 60.
|
|
567
|
+
*/
|
|
568
|
+
constructor(targetFPS = 60) {
|
|
569
|
+
this.targetFPS = targetFPS;
|
|
570
|
+
this.stepMs = 1e3 / targetFPS;
|
|
571
|
+
}
|
|
572
|
+
targetFPS;
|
|
573
|
+
/** Number of milliseconds between each fixed-update tick. */
|
|
574
|
+
stepMs;
|
|
575
|
+
rafHandle = null;
|
|
576
|
+
lastTime = 0;
|
|
577
|
+
accumulated = 0;
|
|
578
|
+
fixedUpdateFn = null;
|
|
579
|
+
renderFn = null;
|
|
580
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
581
|
+
/**
|
|
582
|
+
* Begin the loop.
|
|
583
|
+
*
|
|
584
|
+
* @param fixedUpdate Called at the fixed timestep rate with `dt` in seconds.
|
|
585
|
+
* @param render Called once per animation frame (variable rate).
|
|
586
|
+
*/
|
|
587
|
+
start(fixedUpdate, render) {
|
|
588
|
+
if (this.rafHandle !== null) {
|
|
589
|
+
console.warn(
|
|
590
|
+
"[GameLoopRunner] start() called while already running \u2014 ignoring."
|
|
591
|
+
);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
this.fixedUpdateFn = fixedUpdate;
|
|
595
|
+
this.renderFn = render;
|
|
596
|
+
this.lastTime = performance.now();
|
|
597
|
+
this.accumulated = 0;
|
|
598
|
+
this.rafHandle = requestAnimationFrame(this.tick);
|
|
599
|
+
}
|
|
600
|
+
/** Pause the loop without discarding callbacks. Resume with `resume()`. */
|
|
601
|
+
pause() {
|
|
602
|
+
if (this.rafHandle !== null) {
|
|
603
|
+
cancelAnimationFrame(this.rafHandle);
|
|
604
|
+
this.rafHandle = null;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/** Resume a paused loop. */
|
|
608
|
+
resume() {
|
|
609
|
+
if (this.rafHandle !== null) return;
|
|
610
|
+
if (!this.fixedUpdateFn || !this.renderFn) {
|
|
611
|
+
console.warn(
|
|
612
|
+
"[GameLoopRunner] resume() called before start() \u2014 ignoring."
|
|
613
|
+
);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
this.lastTime = performance.now();
|
|
617
|
+
this.rafHandle = requestAnimationFrame(this.tick);
|
|
618
|
+
}
|
|
619
|
+
/** Stop the loop and release all callbacks. */
|
|
620
|
+
stop() {
|
|
621
|
+
this.pause();
|
|
622
|
+
this.fixedUpdateFn = null;
|
|
623
|
+
this.renderFn = null;
|
|
624
|
+
this.accumulated = 0;
|
|
625
|
+
}
|
|
626
|
+
get isRunning() {
|
|
627
|
+
return this.rafHandle !== null;
|
|
628
|
+
}
|
|
629
|
+
// ── Private ───────────────────────────────────────────────────────────────
|
|
630
|
+
/** Arrow function to preserve `this` across rAF callbacks. */
|
|
631
|
+
tick = (now) => {
|
|
632
|
+
const elapsed = now - this.lastTime;
|
|
633
|
+
this.lastTime = now;
|
|
634
|
+
this.accumulated += Math.min(elapsed, this.stepMs * 5);
|
|
635
|
+
while (this.accumulated >= this.stepMs) {
|
|
636
|
+
this.fixedUpdateFn?.(this.stepMs / 1e3);
|
|
637
|
+
this.accumulated -= this.stepMs;
|
|
638
|
+
}
|
|
639
|
+
this.renderFn?.();
|
|
640
|
+
if (this.rafHandle !== null) {
|
|
641
|
+
this.rafHandle = requestAnimationFrame(this.tick);
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/Game/GameApplication.ts
|
|
647
|
+
var DEFAULT_CONFIG = {
|
|
648
|
+
viewportWidth: 1280,
|
|
649
|
+
viewportHeight: 720,
|
|
650
|
+
targetFPS: 60
|
|
651
|
+
};
|
|
652
|
+
var GameApplication = class {
|
|
653
|
+
// ── Core engine objects — available after construction ───────────────────
|
|
654
|
+
webglCore;
|
|
655
|
+
config;
|
|
656
|
+
/**
|
|
657
|
+
* The active scene. Populated during construction via `createScene()`
|
|
658
|
+
* so subclasses may reference it as early as `onLoadResources()`.
|
|
659
|
+
*/
|
|
660
|
+
scene;
|
|
661
|
+
/**
|
|
662
|
+
* Set during `start()`, after `onLoadResources()` returns.
|
|
663
|
+
* Use in `onInit()` and lifecycle hooks.
|
|
664
|
+
*/
|
|
665
|
+
viewport = null;
|
|
666
|
+
/**
|
|
667
|
+
* Set during `start()`, after `viewport` is initialised.
|
|
668
|
+
* `null` before `start()` resolves — safe to check from external code.
|
|
669
|
+
*/
|
|
670
|
+
renderer = null;
|
|
671
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
672
|
+
loopRunner;
|
|
673
|
+
_started = false;
|
|
674
|
+
// ── Constructor ───────────────────────────────────────────────────────────
|
|
675
|
+
constructor(config) {
|
|
676
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
677
|
+
this.webglCore = new WebGLCore(this.config.canvasId);
|
|
678
|
+
this.scene = this.createScene();
|
|
679
|
+
this.loopRunner = new GameLoopRunner(this.config.targetFPS);
|
|
680
|
+
}
|
|
681
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
682
|
+
/**
|
|
683
|
+
* Execute the full startup sequence and begin the game loop.
|
|
684
|
+
*
|
|
685
|
+
* The sequence is **sealed** — extend behaviour through the protected hooks,
|
|
686
|
+
* not by overriding `start()`.
|
|
687
|
+
*/
|
|
688
|
+
async start() {
|
|
689
|
+
if (this._started) {
|
|
690
|
+
console.warn(
|
|
691
|
+
"[GameApplication] start() called more than once \u2014 ignoring."
|
|
692
|
+
);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
this._started = true;
|
|
696
|
+
await this.onLoadResources();
|
|
697
|
+
this.viewport = this.createViewport();
|
|
698
|
+
this.renderer = this.createRenderer();
|
|
699
|
+
await this.onInit();
|
|
700
|
+
this.loopRunner.start(
|
|
701
|
+
(dt) => this.onUpdate(dt),
|
|
702
|
+
() => {
|
|
703
|
+
this.onBeforeRender();
|
|
704
|
+
this.renderer.render(this.scene);
|
|
705
|
+
this.onAfterRender();
|
|
706
|
+
}
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Pause the game loop without discarding state.
|
|
711
|
+
* Call `resume()` to continue.
|
|
712
|
+
*/
|
|
713
|
+
pause() {
|
|
714
|
+
this.loopRunner.pause();
|
|
715
|
+
}
|
|
716
|
+
/** Resume a paused game loop. */
|
|
717
|
+
resume() {
|
|
718
|
+
this.loopRunner.resume();
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Stop the game loop permanently and invoke `onDispose()`.
|
|
722
|
+
* After calling `stop()` the instance should be discarded.
|
|
723
|
+
*/
|
|
724
|
+
stop() {
|
|
725
|
+
this.loopRunner.stop();
|
|
726
|
+
this.onDispose();
|
|
727
|
+
this._started = false;
|
|
728
|
+
}
|
|
729
|
+
get isRunning() {
|
|
730
|
+
return this.loopRunner.isRunning;
|
|
731
|
+
}
|
|
732
|
+
// ── Virtual hooks (optional override) ────────────────────────────────────
|
|
733
|
+
/**
|
|
734
|
+
* Factory for the `Scene`. Override to customise lights or initial state.
|
|
735
|
+
* Default: `Scene.withDefaultLights(webglCore)`.
|
|
736
|
+
*/
|
|
737
|
+
createScene() {
|
|
738
|
+
return Scene.withDefaultLights(this.webglCore);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Factory for the `Viewport`. Override to customise camera settings or
|
|
742
|
+
* attach extra resize behaviour.
|
|
743
|
+
* Default: constructed from `config.canvasId`, `viewportWidth`, `viewportHeight`.
|
|
744
|
+
*/
|
|
745
|
+
createViewport() {
|
|
746
|
+
return new Viewport(
|
|
747
|
+
this.config.canvasId,
|
|
748
|
+
this.config.viewportWidth,
|
|
749
|
+
this.config.viewportHeight,
|
|
750
|
+
this.webglCore
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Factory for the `Renderer`. Override to enable debug mode or swap
|
|
755
|
+
* the renderer implementation.
|
|
756
|
+
* Default: `new Renderer(webglCore, viewport)`.
|
|
757
|
+
*/
|
|
758
|
+
createRenderer() {
|
|
759
|
+
return new Renderer(this.webglCore, this.viewport);
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Called every animation frame **before** `renderer.render(scene)`.
|
|
763
|
+
*
|
|
764
|
+
* Suitable for: updating HUD overlays, syncing camera state, or any
|
|
765
|
+
* visual concern that must run at display rate rather than the fixed tick.
|
|
766
|
+
*/
|
|
767
|
+
onBeforeRender() {
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Called every animation frame **after** `renderer.render(scene)`.
|
|
771
|
+
*
|
|
772
|
+
* Suitable for: post-process passes, 2D canvas overlays, analytics.
|
|
773
|
+
*/
|
|
774
|
+
onAfterRender() {
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Called by `stop()` to release any custom resources held by the subclass
|
|
778
|
+
* (event listeners, WebSocket connections, audio contexts, etc.).
|
|
779
|
+
*
|
|
780
|
+
* The base `Scene` and engine objects are **not** auto-disposed here;
|
|
781
|
+
* call `scene.dispose()` explicitly if needed.
|
|
782
|
+
*/
|
|
783
|
+
onDispose() {
|
|
784
|
+
}
|
|
785
|
+
};
|
|
345
786
|
export {
|
|
787
|
+
Actor,
|
|
346
788
|
AnimationController,
|
|
347
789
|
Arm,
|
|
348
790
|
Character,
|
|
791
|
+
GameApplication,
|
|
792
|
+
GameLoopRunner,
|
|
349
793
|
Head,
|
|
794
|
+
InteractiveActor,
|
|
350
795
|
KeyboardInput,
|
|
351
|
-
Leg
|
|
796
|
+
Leg,
|
|
797
|
+
PhysicsBody
|
|
352
798
|
};
|
|
353
799
|
//# sourceMappingURL=index.js.map
|