dino-ge 1.3.2 → 1.3.7

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/src/Engine.ts DELETED
@@ -1,467 +0,0 @@
1
- import Canvas from './Canvas.js';
2
- import GameObject from './GameObject.js';
3
- import Input from './Input.js';
4
- import Scene from './Scene.js';
5
- import Camera from './Camera.js';
6
- import System from './System.js';
7
- import PhysicsSystem from './PhysicsSystem.js';
8
- import RenderingSystem from './RenderingSystem.js';
9
- import PhysicsComponent from './PhysicsComponent.js';
10
- import TagComponent from './TagComponent.js';
11
-
12
- /**
13
- * Options for initializing the Engine.
14
- */
15
- export interface EngineOpts {
16
- /** Width of the game window (e.g., '800px' or '100%'). */
17
- width: string;
18
- /** Height of the game window (e.g., '600px' or '100%'). */
19
- height: string;
20
- /** Title of the game, set in the document head. */
21
- title: string;
22
- /** Background colour of the canvas. */
23
- backgroundColour: string;
24
- }
25
-
26
- /**
27
- * Lifecycle and update callbacks for the engine.
28
- */
29
- export interface EngineCallbacks {
30
- /** Called once after the engine is initialized. */
31
- onLoad: () => void;
32
- /** Called every frame for game logic and rendering. */
33
- update: () => void;
34
- /** Optional callback for physics or fixed-step logic. */
35
- fixedUpdate?: () => void;
36
- }
37
-
38
- /**
39
- * Specialized Set for managing game objects with helper methods.
40
- */
41
- export class ObjectSet extends Set<GameObject> {
42
- /**
43
- * Find all objects with a specific tag.
44
- * @param tag The tag to search for.
45
- */
46
- findAll: (tag: string) => GameObject[] = () => [];
47
- }
48
-
49
- /**
50
- * Interface for the shared global state of the Engine.
51
- */
52
- interface EngineState {
53
- objects: ObjectSet;
54
- paused: boolean;
55
- debug: boolean;
56
- selectedObject: GameObject | null;
57
- camera: Camera;
58
- systems: System[];
59
- renderingSystem?: RenderingSystem;
60
- events: EventTarget;
61
- }
62
-
63
- /**
64
- * Shared state for the engine to ensure singletons work across different module loads.
65
- */
66
- const _globalState: EngineState = (globalThis as unknown as { __DINO_ENGINE_STATE__: EngineState; }).__DINO_ENGINE_STATE__ || {
67
- objects: new ObjectSet(),
68
- paused: false,
69
- debug: false,
70
- selectedObject: null,
71
- camera: new Camera(),
72
- systems: [new PhysicsSystem()],
73
- events: new EventTarget()
74
- };
75
- (globalThis as unknown as { __DINO_ENGINE_STATE__: EngineState; }).__DINO_ENGINE_STATE__ = _globalState;
76
-
77
- /**
78
- * The core singleton class that manages the game loop, rendering, and scene state.
79
- */
80
- export default class Engine {
81
- /**
82
- * Global set of game objects if no scene is active.
83
- */
84
- public static get objects(): ObjectSet { return _globalState.objects; }
85
-
86
- /**
87
- * Global list of systems that process game objects.
88
- */
89
- private static get _systems(): System[] { return _globalState.systems; }
90
-
91
- /**
92
- * The global rendering system.
93
- */
94
- public static get renderingSystem(): RenderingSystem | undefined { return _globalState.renderingSystem; }
95
- public static set renderingSystem(val: RenderingSystem | undefined) { _globalState.renderingSystem = val; }
96
-
97
- /**
98
- * Whether the game loop is currently paused.
99
- */
100
- public static get paused(): boolean { return _globalState.paused; }
101
- public static set paused(val: boolean) {
102
- if (_globalState.paused !== val) {
103
- _globalState.paused = val;
104
- this.emit('paused', val);
105
- }
106
- }
107
-
108
- /**
109
- * Toggle for visual debug mode (hitboxes and stats).
110
- */
111
- public static get debug(): boolean { return _globalState.debug; }
112
- public static set debug(val: boolean) {
113
- if (_globalState.debug !== val) {
114
- _globalState.debug = val;
115
- this.emit('debug', val);
116
- }
117
- }
118
-
119
- /**
120
- * Internal event bus for engine events.
121
- */
122
- private static get events(): EventTarget { return _globalState.events; }
123
-
124
- /**
125
- * Listens for an event on the global engine bus.
126
- * @param type The event type (e.g., 'paused', 'PLAYER_DIED').
127
- * @param callback The function to run when the event occurs.
128
- */
129
- public static on(type: string, callback: (event: CustomEvent) => void) {
130
- this.events.addEventListener(type, callback as (e: Event) => void);
131
- }
132
-
133
- /**
134
- * Stops listening for an event on the global engine bus.
135
- * @param type The event type.
136
- * @param callback The function to remove.
137
- */
138
- public static off(type: string, callback: (event: CustomEvent) => void) {
139
- this.events.removeEventListener(type, callback as (e: Event) => void);
140
- }
141
-
142
- /**
143
- * Emits a custom event on the global engine bus.
144
- * @param type The event type.
145
- * @param detail Optional data to pass with the event.
146
- */
147
- public static emit(type: string, detail?: unknown) {
148
- this.events.dispatchEvent(new CustomEvent(type, { detail }));
149
- }
150
-
151
- /**
152
- * The currently selected object in debug mode.
153
- */
154
- public static get selectedObject(): GameObject | null { return _globalState.selectedObject; }
155
- public static set selectedObject(val: GameObject | null) { _globalState.selectedObject = val; }
156
-
157
- /**
158
- * The global camera instance.
159
- */
160
- public static get camera(): Camera { return _globalState.camera; }
161
-
162
- private static _currentScene: Scene;
163
-
164
- /**
165
- * Gets the currently active scene.
166
- */
167
- public static get currentScene(): Scene {
168
- return this._currentScene;
169
- }
170
-
171
- /**
172
- * Sets the active scene, resetting the camera and calling the scene's onLoad.
173
- */
174
- public static set currentScene(scene: Scene) {
175
- this._currentScene = scene;
176
- this.camera.reset();
177
- scene.onLoad();
178
- }
179
-
180
- #canvas: Canvas;
181
- #ctx!: CanvasRenderingContext2D;
182
- #window: HTMLDivElement;
183
- #title: HTMLTitleElement;
184
-
185
- /** Current background colour. */
186
- backgroundColour: string;
187
- /** Registered engine callbacks. */
188
- callbacks: EngineCallbacks;
189
-
190
- /** Pixel width of the canvas. */
191
- width: number;
192
- /** Pixel height of the canvas. */
193
- height: number;
194
-
195
- /** Current mouse x position in world space. */
196
- get mouseX() {
197
- return Input.mouseX;
198
- }
199
-
200
- /** Current mouse y position in world space. */
201
- get mouseY() {
202
- return Input.mouseY;
203
- }
204
-
205
- /** Current frames per second. */
206
- fps: number = 0;
207
- #oldTimestamp: number = 0;
208
- #secondsPassed: number = 0;
209
- #accumulator: number = 0;
210
- #fixedDelta: number = 1 / 60;
211
-
212
- constructor(callbacks: EngineCallbacks, opts: Partial<EngineOpts> = {}) {
213
- const defaultedOpts = {
214
- width: '100%',
215
- height: '100%',
216
- title: 'Example',
217
- backgroundColour: 'white',
218
- ...opts,
219
- };
220
-
221
-
222
- Engine.objects.findAll = this.#findAllObjects.bind(this);
223
-
224
- this.#title = document.createElement('title');
225
- this.#title.innerHTML = defaultedOpts.title;
226
- document.getElementsByTagName('head')[0].appendChild(this.#title);
227
-
228
- this.backgroundColour = defaultedOpts.backgroundColour;
229
-
230
- this.callbacks = {
231
- onLoad: () => {
232
- callbacks.onLoad();
233
- this.#onLoad();
234
- },
235
- update: callbacks.update,
236
- };
237
-
238
- this.#canvas = new Canvas();
239
- this.#ctx = this.#canvas.canvas.getContext('2d')!;
240
- this.width = this.#canvas.canvas.width;
241
- this.height = this.#canvas.canvas.height;
242
-
243
- if (Engine.renderingSystem) {
244
- Engine.renderingSystem.setContext(this.#ctx);
245
- } else {
246
- Engine.renderingSystem = new RenderingSystem(this.#ctx);
247
- }
248
-
249
- this.#window = document.createElement('div');
250
- this.#window.id = 'canvas-container';
251
- this.#window.style.width = defaultedOpts.width;
252
- this.#window.style.height = defaultedOpts.height;
253
-
254
- Input.init();
255
-
256
- window.addEventListener('resize', () => {
257
- this.#canvas.resize();
258
- this.width = this.#canvas.canvas.width;
259
- this.height = this.#canvas.canvas.height;
260
- });
261
-
262
- document.getElementsByTagName('body')[0].appendChild(this.#window);
263
- this.#window.appendChild(this.#canvas.canvas);
264
-
265
- setTimeout(() => this.callbacks.onLoad(), 0);
266
- }
267
-
268
- #onLoad() {
269
- this.#draw();
270
-
271
- window.requestAnimationFrame(this.#update.bind(this));
272
- }
273
-
274
- #update(timestamp: number) {
275
- if (!Engine.paused) {
276
- this.#secondsPassed = (timestamp - this.#oldTimestamp) / 1000;
277
- this.#oldTimestamp = timestamp;
278
-
279
- this.fps = Math.round(1 / this.#secondsPassed);
280
-
281
- // Fixed update loop
282
- this.#accumulator += this.#secondsPassed;
283
- while (this.#accumulator >= this.#fixedDelta) {
284
- this.#fixedUpdate();
285
- this.#accumulator -= this.#fixedDelta;
286
- }
287
-
288
- if (Engine.currentScene) {
289
- Engine.currentScene.update();
290
- }
291
-
292
- this.callbacks.update();
293
- this.#draw();
294
- } else {
295
- // Still allow drawing in paused mode if debug is on or for initial frame
296
- this.#draw();
297
- }
298
-
299
- window.requestAnimationFrame(this.#update.bind(this));
300
- }
301
-
302
- #fixedUpdate() {
303
- const objects = Engine.currentScene ? Engine.currentScene.objects : Engine.objects;
304
-
305
- Engine._systems.forEach((system) => {
306
- if (system.fixedUpdate) {
307
- system.fixedUpdate(objects, this.#fixedDelta);
308
- }
309
- });
310
-
311
- if (this.callbacks.fixedUpdate) {
312
- this.callbacks.fixedUpdate();
313
- }
314
- }
315
-
316
- #setBackground() {
317
- this.#ctx.fillStyle = this.backgroundColour;
318
- this.#ctx.fillRect(0, 0, this.#canvas.canvas.width, this.#canvas.canvas.height);
319
- }
320
-
321
- #draw() {
322
- this.#setBackground();
323
-
324
- const objects = Engine.currentScene ? Engine.currentScene.objects : Engine.objects;
325
-
326
- if (Engine.renderingSystem) {
327
- Engine.renderingSystem.update(objects, 0, Engine.debug);
328
- }
329
-
330
- if (Engine.debug) {
331
- this.#drawDebug(objects);
332
- }
333
- }
334
- #drawDebug(objects: Set<GameObject>) {
335
- // Draw Stats Overlay (Top Right)
336
- this.#ctx.save();
337
- this.#ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transform for UI
338
-
339
- const overlayWidth = 180;
340
- const statsHeight = 80;
341
- const padding = 10;
342
- const x = this.#canvas.canvas.width - overlayWidth - padding;
343
- let y = padding;
344
-
345
- // Background for Stats
346
- this.#ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
347
- this.#ctx.fillRect(x, y, overlayWidth, statsHeight);
348
-
349
- this.#ctx.fillStyle = 'white';
350
- this.#ctx.textAlign = 'left';
351
- this.#ctx.textBaseline = 'top';
352
-
353
- this.#ctx.fillText(`FPS: ${this.fps}`, x + 10, y + 10);
354
- this.#ctx.fillText(`Objects: ${objects.size}`, x + 10, y + 25);
355
- this.#ctx.fillText(`Mouse X: ${Math.round(this.mouseX)}`, x + 10, y + 45);
356
- this.#ctx.fillText(`Mouse Y: ${Math.round(this.mouseY)}`, x + 10, y + 60);
357
-
358
- // Inspector Panel (If object selected)
359
- if (Engine.selectedObject) {
360
- y += statsHeight + 10;
361
- const obj = Engine.selectedObject;
362
- const properties: [string, string | number][] = [
363
- ['Pos', `${Math.round(obj.position.x)}, ${Math.round(obj.position.y)}`],
364
- ['Size', `${Math.round(obj.width)}x${Math.round(obj.height)}`]
365
- ];
366
-
367
- const tagComp = obj.getComponent(TagComponent);
368
- if (tagComp) {
369
- properties.unshift(['Tag', tagComp.tag]);
370
- properties.push(['Z-Index', tagComp.zIndex]);
371
- }
372
-
373
- const physComp = obj.getComponent(PhysicsComponent);
374
- if (physComp) {
375
- properties.push(['Vel', `${Math.round(physComp.velocity.x)}, ${Math.round(physComp.velocity.y)}`]);
376
- properties.push(['Acc', `${Math.round(physComp.acceleration.x)}, ${Math.round(physComp.acceleration.y)}`]);
377
- }
378
-
379
- const inspectorHeight = 20 + (properties.length * 15);
380
- this.#ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
381
- this.#ctx.fillRect(x, y, overlayWidth, inspectorHeight);
382
- this.#ctx.strokeStyle = '#00ff00';
383
- this.#ctx.strokeRect(x, y, overlayWidth, inspectorHeight);
384
-
385
- this.#ctx.fillStyle = '#00ff00';
386
- this.#ctx.fillText('INSPECTOR', x + 10, y + 5);
387
-
388
- this.#ctx.fillStyle = 'white';
389
- properties.forEach(([key, val], i) => {
390
- this.#ctx.fillText(`${key}: ${val}`, x + 10, y + 20 + (i * 15));
391
- });
392
- }
393
-
394
- this.#ctx.restore();
395
- }
396
-
397
- #findAllObjects(tag: string = '') {
398
- return Array.from(Engine.objects).filter((obj) => obj.tag === tag);
399
- }
400
-
401
- /**
402
- * Promisified setTimeout.
403
- * @param timeoutFn The function to run after the timeout.
404
- * @param time The time in milliseconds to wait.
405
- */
406
- async setTimeout(timeoutFn: () => void, time: number) {
407
- await new Promise((resolve) => {
408
- setTimeout(resolve, time);
409
- });
410
- timeoutFn();
411
- }
412
-
413
- /**
414
- * Run a function repeatedly for a duration and then run a final function.
415
- * @param milliseconds Total duration.
416
- * @param fn Function to run every second.
417
- * @param onEnded Final function to run.
418
- */
419
- countdown(milliseconds: number, fn: () => void, onEnded: () => void) {
420
- setTimeout(onEnded, milliseconds);
421
-
422
- for (let i = 1; i <= milliseconds; i += 1) {
423
- if (i % 1000 === 0) {
424
- setTimeout(fn, i);
425
- }
426
- }
427
- }
428
-
429
- /** Sets the CSS cursor style for the game canvas. */
430
- set cursor(value: string) {
431
- const canvas = document.getElementById('canvas');
432
- if (canvas) canvas.style.cursor = value;
433
- }
434
-
435
- /**
436
- * Register a new game object with the active scene or global engine.
437
- * @param object The object to register.
438
- */
439
- static registerObject(object: GameObject) {
440
- if (Engine.currentScene) {
441
- Engine.currentScene.add(object);
442
- } else {
443
- this.objects.add(object);
444
- }
445
- }
446
-
447
- /**
448
- * Remove a game object from the active scene or global engine.
449
- * @param object The object to destroy.
450
- */
451
- static destroyObject(object: GameObject) {
452
- if (Engine.currentScene) {
453
- Engine.currentScene.remove(object);
454
- } else {
455
- this.objects.delete(object);
456
- }
457
- }
458
-
459
- /** Destroy all objects in the active scene or global engine. */
460
- static destroyAll() {
461
- if (Engine.currentScene) {
462
- Engine.currentScene.clear();
463
- } else {
464
- this.objects.clear();
465
- }
466
- }
467
- }
@@ -1,35 +0,0 @@
1
- import Component from './Component.js';
2
-
3
- /**
4
- * Component that allows an entity to emit and listen for local events.
5
- */
6
- export default class EventBusComponent extends Component {
7
- private events: EventTarget = new EventTarget();
8
-
9
- /**
10
- * Listens for a local event on this entity.
11
- * @param type The event type.
12
- * @param callback The function to run when the event occurs.
13
- */
14
- on(type: string, callback: (event: CustomEvent) => void) {
15
- this.events.addEventListener(type, callback as (e: Event) => void);
16
- }
17
-
18
- /**
19
- * Stops listening for a local event.
20
- * @param type The event type.
21
- * @param callback The function to remove.
22
- */
23
- off(type: string, callback: (event: CustomEvent) => void) {
24
- this.events.removeEventListener(type, callback as (e: Event) => void);
25
- }
26
-
27
- /**
28
- * Emits a local event on this entity.
29
- * @param type The event type.
30
- * @param detail Optional data to pass with the event.
31
- */
32
- emit(type: string, detail?: unknown) {
33
- this.events.dispatchEvent(new CustomEvent(type, { detail }));
34
- }
35
- }
package/src/GameObject.ts DELETED
@@ -1,204 +0,0 @@
1
- import Engine from './Engine.js';
2
- import Vector2 from './Vector2.js';
3
- import Component from './Component.js';
4
- import PhysicsComponent from './PhysicsComponent.js';
5
- import TransformComponent from './TransformComponent.js';
6
- import TagComponent from './TagComponent.js';
7
- import EventBusComponent from './EventBusComponent.js';
8
-
9
- /**
10
- * Base class for all entities in the game world.
11
- * Provides properties for physics, rendering, and lifecycle management.
12
- * Acts as the 'Entity' in our evolving Entity Component System.
13
- */
14
- export default abstract class GameObject {
15
- /** Collection of components attached to this entity. */
16
- private _components: Map<string, Component> = new Map();
17
-
18
- /** Internal components for backward compatibility and core logic. */
19
- private _physics: PhysicsComponent;
20
- private _transform: TransformComponent;
21
- private _tag: TagComponent;
22
- private _eventBus: EventBusComponent;
23
-
24
- constructor(tag: string, zIndex: number) {
25
- this._tag = new TagComponent(tag, zIndex);
26
- this.addComponent(this._tag);
27
-
28
- this._physics = new PhysicsComponent();
29
- this.addComponent(this._physics);
30
-
31
- this._transform = new TransformComponent();
32
- this.addComponent(this._transform);
33
-
34
- this._eventBus = new EventBusComponent();
35
- this.addComponent(this._eventBus);
36
- }
37
-
38
- /**
39
- * Listens for a local event on this entity.
40
- * @param type The event type.
41
- * @param callback The function to run when the event occurs.
42
- */
43
- on(type: string, callback: (event: CustomEvent) => void) {
44
- this._eventBus.on(type, callback);
45
- }
46
-
47
- /**
48
- * Stops listening for a local event.
49
- * @param type The event type.
50
- * @param callback The function to remove.
51
- */
52
- off(type: string, callback: (event: CustomEvent) => void) {
53
- this._eventBus.off(type, callback);
54
- }
55
-
56
- /**
57
- * Emits a local event on this entity.
58
- * @param type The event type.
59
- * @param detail Optional data to pass with the event.
60
- */
61
- emit(type: string, detail?: unknown) {
62
- this._eventBus.emit(type, detail);
63
- }
64
-
65
- /**
66
- * A unique identifier for the object type.
67
- * @deprecated Use getComponent(TagComponent).tag
68
- */
69
- get tag(): string { return this._tag.tag; }
70
- set tag(val: string) { this._tag.tag = val; }
71
-
72
- /**
73
- * Rendering order (lower is background, higher is foreground).
74
- * @deprecated Use getComponent(TagComponent).zIndex
75
- */
76
- get zIndex(): number { return this._tag.zIndex; }
77
- set zIndex(val: number) { this._tag.zIndex = val; }
78
-
79
- /**
80
- * The world-space position of the object.
81
- * Getter returns world position, setter sets local position.
82
- */
83
- get position(): Vector2 { return this._transform.worldPosition; }
84
- set position(val: Vector2) { this._transform.position = val; }
85
-
86
- /**
87
- * The local position relative to the parent.
88
- */
89
- get localPosition(): Vector2 { return this._transform.position; }
90
- set localPosition(val: Vector2) { this._transform.position = val; }
91
-
92
- /**
93
- * The world-space rotation in radians.
94
- */
95
- get rotation(): number { return this._transform.worldRotation; }
96
- set rotation(val: number) { this._transform.rotation = val; }
97
-
98
- /**
99
- * The local rotation in radians relative to the parent.
100
- */
101
- get localRotation(): number { return this._transform.rotation; }
102
- set localRotation(val: number) { this._transform.rotation = val; }
103
-
104
- /**
105
- * The local scale relative to the parent.
106
- */
107
- get scale(): Vector2 { return this._transform.scale; }
108
- set scale(val: Vector2) { this._transform.scale = val; }
109
-
110
- /**
111
- * Adds a child GameObject to this entity.
112
- * @param child The child entity to add.
113
- */
114
- addChild(child: GameObject) {
115
- child._transform.parent = this._transform;
116
- this._transform.children.add(child._transform);
117
- }
118
-
119
- /**
120
- * Removes a child GameObject from this entity.
121
- * @param child The child entity to remove.
122
- */
123
- removeChild(child: GameObject) {
124
- if (child._transform.parent === this._transform) {
125
- child._transform.parent = undefined;
126
- this._transform.children.delete(child._transform);
127
- }
128
- }
129
-
130
- /**
131
- * Gets or sets the parent GameObject.
132
- */
133
- get parent(): GameObject | undefined {
134
- return this._transform.parent?.gameObject;
135
- }
136
- set parent(val: GameObject | undefined) {
137
- if (this._transform.parent) {
138
- this._transform.parent.children.delete(this._transform);
139
- }
140
- this._transform.parent = val?._transform;
141
- val?._transform.children.add(this._transform);
142
- }
143
-
144
- /**
145
- * Adds a component to this entity.
146
- * @param component The component to add.
147
- */
148
- addComponent(component: Component) {
149
- const key = component.constructor.name;
150
- this._components.set(key, component);
151
-
152
- // Also index by RenderComponent if it is one, to allow abstract querying.
153
- // Use flag instead of instanceof to work across potential module duplications.
154
- if ((component as unknown as { isRenderComponent: boolean; }).isRenderComponent) {
155
- this._components.set('RenderComponent', component);
156
- }
157
-
158
- component.gameObject = this;
159
- }
160
-
161
- /**
162
- * Removes a component from this entity by its class.
163
- * @param componentClass The class of the component to remove.
164
- */
165
- removeComponent<T extends Component>(componentClass: { new(...args: unknown[]): T; }) {
166
- this._components.delete(componentClass.name);
167
- }
168
-
169
- /**
170
- * Checks if this entity has a component of the specified class.
171
- * @param componentClass The class of the component to check for.
172
- */
173
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
174
- hasComponent<T extends Component>(componentClass: Function & { prototype: T; }): boolean {
175
- return this._components.has(componentClass.name);
176
- }
177
-
178
- /** Gets a component from this entity by its class.
179
- * @param componentClass The class of the component to retrieve.
180
- */
181
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
182
- getComponent<T extends Component>(componentClass: Function & { prototype: T; }): T | undefined {
183
- return this._components.get(componentClass.name) as T;
184
- }
185
-
186
- /** The width of the object in pixels. */
187
- get width(): number { return 0; }
188
- /** The height of the object in pixels. */
189
- get height(): number { return 0; }
190
-
191
- /**
192
- * Registers the object with the active scene or global engine loop.
193
- */
194
- registerSelf() {
195
- Engine.registerObject(this);
196
- }
197
-
198
- /**
199
- * Removes the object from the active scene.
200
- */
201
- destroySelf() {
202
- Engine.destroyObject(this);
203
- }
204
- }