@dryanovski/gamefoo 0.2.3 → 0.2.5

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.
Files changed (84) hide show
  1. package/dist/core/animate.js +147 -0
  2. package/dist/core/asset.js +74 -0
  3. package/dist/core/behaviour.js +88 -0
  4. package/dist/core/behaviours/collidable.js +186 -0
  5. package/dist/core/behaviours/control.js +75 -0
  6. package/dist/core/behaviours/healtkit.js +153 -0
  7. package/dist/core/behaviours/sprite_render.js +193 -0
  8. package/dist/core/camera.js +134 -0
  9. package/dist/core/engine.d.ts +1 -1
  10. package/dist/core/engine.d.ts.map +1 -1
  11. package/dist/core/engine.js +527 -0
  12. package/dist/core/fonts/font_bitmap.js +205 -0
  13. package/dist/core/fonts/font_bitmap_prebuild.js +137 -0
  14. package/dist/core/fonts/internal/font_3x5.js +169 -0
  15. package/dist/core/fonts/internal/font_4x6.js +171 -0
  16. package/dist/core/fonts/internal/font_5x5.js +129 -0
  17. package/dist/core/fonts/internal/font_6x8.js +171 -0
  18. package/dist/core/fonts/internal/font_8x13.js +171 -0
  19. package/dist/core/fonts/internal/font_8x8.js +171 -0
  20. package/dist/core/game_object_register.js +134 -0
  21. package/dist/core/input.js +170 -0
  22. package/dist/core/sprite.js +222 -0
  23. package/dist/core/utils/perlin_noise.js +183 -0
  24. package/dist/core/world.js +304 -0
  25. package/dist/debug/monitor.js +47 -0
  26. package/dist/decorators/index.js +1 -0
  27. package/dist/decorators/log.js +42 -0
  28. package/dist/entities/dynamic_entity.js +99 -0
  29. package/dist/entities/entity.js +283 -0
  30. package/dist/entities/player.js +93 -0
  31. package/dist/entities/text.js +62 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +47 -29
  35. package/dist/subsystems/camera_system.d.ts +2 -2
  36. package/dist/subsystems/camera_system.d.ts.map +1 -1
  37. package/dist/subsystems/camera_system.js +41 -0
  38. package/dist/subsystems/collision_system.js +17 -0
  39. package/dist/subsystems/monitor_system.js +20 -0
  40. package/dist/subsystems/object_system.d.ts +1 -1
  41. package/dist/subsystems/object_system.d.ts.map +1 -1
  42. package/dist/subsystems/object_system.js +29 -0
  43. package/dist/subsystems/types.d.ts +1 -1
  44. package/dist/subsystems/types.d.ts.map +1 -1
  45. package/dist/subsystems/types.js +0 -0
  46. package/dist/types.js +10 -0
  47. package/package.json +17 -6
  48. package/src/core/animate.ts +159 -0
  49. package/src/core/asset.ts +76 -0
  50. package/src/core/behaviour.ts +145 -0
  51. package/src/core/behaviours/collidable.ts +296 -0
  52. package/src/core/behaviours/control.ts +80 -0
  53. package/src/core/behaviours/healtkit.ts +166 -0
  54. package/src/core/behaviours/sprite_render.ts +216 -0
  55. package/src/core/camera.ts +145 -0
  56. package/src/core/engine.ts +607 -0
  57. package/src/core/fonts/font_bitmap.ts +232 -0
  58. package/src/core/fonts/font_bitmap_prebuild.ts +141 -0
  59. package/src/core/fonts/internal/font_3x5.ts +178 -0
  60. package/src/core/fonts/internal/font_4x6.ts +180 -0
  61. package/src/core/fonts/internal/font_5x5.ts +137 -0
  62. package/src/core/fonts/internal/font_6x8.ts +180 -0
  63. package/src/core/fonts/internal/font_8x13.ts +180 -0
  64. package/src/core/fonts/internal/font_8x8.ts +180 -0
  65. package/src/core/game_object_register.ts +146 -0
  66. package/src/core/input.ts +182 -0
  67. package/src/core/sprite.ts +339 -0
  68. package/src/core/utils/perlin_noise.ts +196 -0
  69. package/src/core/world.ts +331 -0
  70. package/src/debug/monitor.ts +60 -0
  71. package/src/decorators/index.ts +1 -0
  72. package/src/decorators/log.ts +45 -0
  73. package/src/entities/dynamic_entity.ts +106 -0
  74. package/src/entities/entity.ts +322 -0
  75. package/src/entities/player.ts +99 -0
  76. package/src/entities/text.ts +72 -0
  77. package/src/index.ts +51 -0
  78. package/src/subsystems/camera_system.ts +52 -0
  79. package/src/subsystems/collision_system.ts +21 -0
  80. package/src/subsystems/monitor_system.ts +26 -0
  81. package/src/subsystems/object_system.ts +37 -0
  82. package/src/subsystems/types.ts +46 -0
  83. package/src/types.ts +178 -0
  84. package/dist/index.js.map +0 -9
@@ -1,4 +1,4 @@
1
- import type Engine from "@/core/engine";
1
+ import type Engine from "../core/engine";
2
2
  /**
3
3
  * SubSystem is a modular component of the game engine that can be added or removed as needed.
4
4
  * It provides hooks for initialization, updating, rendering, and destruction.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/subsystems/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B,SAAS,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,MAAM,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAErC,SAAS,CAAC,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAChD,MAAM,CAAC,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC7C,UAAU,CAAC,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAEjD,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/subsystems/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B,SAAS,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,MAAM,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAErC,SAAS,CAAC,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAChD,MAAM,CAAC,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC7C,UAAU,CAAC,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAEjD,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB"}
File without changes
package/dist/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared type definitions used throughout the GameFoo engine.
3
+ *
4
+ * This module contains the foundational interfaces and type aliases that
5
+ * form the contract between the engine core, entities, and behaviours.
6
+ *
7
+ * @category Types
8
+ * @module types
9
+ * @since 0.1.0
10
+ */
package/package.json CHANGED
@@ -1,28 +1,38 @@
1
1
  {
2
2
  "name": "@dryanovski/gamefoo",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "A lightweight 2D game engine with behavior-based component system",
5
5
  "license": "MIT",
6
6
  "files": [
7
- "dist"
7
+ "dist",
8
+ "src",
9
+ "README.md"
8
10
  ],
9
11
  "type": "module",
10
12
  "sideEffects": false,
13
+ "source": "./src/index.ts",
11
14
  "main": "./dist/index.js",
12
15
  "module": "./dist/index.js",
13
16
  "types": "./dist/index.d.ts",
14
17
  "exports": {
15
18
  ".": {
16
19
  "types": "./dist/index.d.ts",
17
- "import": "./dist/index.js"
18
- }
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./source": {
24
+ "types": "./src/index.ts",
25
+ "import": "./src/index.ts",
26
+ "default": "./src/index.ts"
27
+ },
28
+ "./package.json": "./package.json"
19
29
  },
20
30
  "publishConfig": {
21
31
  "access": "public"
22
32
  },
23
33
  "scripts": {
24
34
  "dev": "bun --watch demos/server.ts",
25
- "build": "rm -rf dist && bun build ./src/index.ts --outdir dist --format esm --target browser --sourcemap=external --no-bundler && tsc -p tsconfig.build.json",
35
+ "build": "rm -rf dist && bunx tsc -p tsconfig.build.json",
26
36
  "typecheck": "tsc --noEmit",
27
37
  "typecheck:watch": "tsc --noEmit --watch",
28
38
  "lint": "bunx biome check src/",
@@ -36,7 +46,8 @@
36
46
  "docs:collect": "tsx scripts/collect-docs.ts",
37
47
  "docs:dev": "npm run docs:generate && astro dev --root ./docs",
38
48
  "docs:preview": "astro preview --root ./docs",
39
- "docs:watch": "chokidar 'src/**/*.ts' -c 'bun run docs:generate' --initial"
49
+ "docs:watch": "chokidar 'src/**/*.ts' -c 'bun run docs:generate' --initial",
50
+ "test:publish": "bun run --cwd publish-test sanity"
40
51
  },
41
52
  "dependencies": {
42
53
  "@microsoft/tsdoc": "^0.16.0",
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Frame-based sprite animation controller.
3
+ *
4
+ * `Animate` steps through a sequence of spritesheet cells at a given
5
+ * frame rate. It tracks elapsed time internally and advances the
6
+ * current frame index each time the interval elapses.
7
+ *
8
+ * > **Note:** The {@link Animate.draw} method is currently a stub —
9
+ * > it resolves the correct frame but does not yet perform the actual
10
+ * > `drawImage` call. Use {@link SpriteRender} for production
11
+ * > sprite rendering.
12
+ *
13
+ * @category Core
14
+ * @since 0.1.0
15
+ *
16
+ * @example Creating a walk animation
17
+ * ```ts
18
+ * const walkFrames = [
19
+ * { col: 0, row: 0 },
20
+ * { col: 1, row: 0 },
21
+ * { col: 2, row: 0 },
22
+ * { col: 3, row: 0 },
23
+ * ];
24
+ *
25
+ * const anim = new Animate("walk", walkFrames, 32, 32, 12);
26
+ * ```
27
+ *
28
+ * @example Updating in a game loop
29
+ * ```ts
30
+ * function update(delta: number) {
31
+ * anim.update(delta);
32
+ * anim.draw(ctx, entity.x, entity.y);
33
+ * }
34
+ * ```
35
+ *
36
+ * @see {@link SpriteRender} — behaviour-based alternative for entity rendering
37
+ * @see {@link Sprite} — spritesheet metadata container
38
+ */
39
+ export default class Animate {
40
+ /** Identifier for this animation (e.g. `"walk"`, `"idle"`). */
41
+ private key: string;
42
+
43
+ /** Ordered list of spritesheet cells that make up the animation. */
44
+ private frames: { col: number; row: number }[];
45
+
46
+ /** Width of a single frame in pixels. */
47
+ private frameW: number;
48
+
49
+ /** Height of a single frame in pixels. */
50
+ private frameH: number;
51
+
52
+ /**
53
+ * Index into {@link Animate.frames} of the frame currently being
54
+ * displayed.
55
+ *
56
+ * @defaultValue `0`
57
+ */
58
+ private currentFrame: number = 0;
59
+
60
+ /**
61
+ * Milliseconds accumulated since the last frame advance.
62
+ *
63
+ * @defaultValue `0`
64
+ */
65
+ private elapsed = 0;
66
+
67
+ /** Computed time between frames in milliseconds (`1000 / fps`). */
68
+ private interval: number;
69
+
70
+ /** Playback speed in frames per second. */
71
+ private fps: number;
72
+
73
+ /**
74
+ * Creates a new animation sequence.
75
+ *
76
+ * @param key - A unique name for this animation (used as a look-up key).
77
+ * @param frames - An ordered array of `{ col, row }` cells from the
78
+ * spritesheet.
79
+ * @param frameW - Width of each frame in pixels.
80
+ * @param frameH - Height of each frame in pixels.
81
+ * @param fps - Playback speed in frames per second.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const idle = new Animate(
86
+ * "idle",
87
+ * [{ col: 0, row: 1 }, { col: 1, row: 1 }],
88
+ * 64, 64,
89
+ * 8,
90
+ * );
91
+ * ```
92
+ */
93
+ constructor(key: string, frames: { col: number; row: number }[], frameW: number, frameH: number, fps: number) {
94
+ this.key = key;
95
+ this.frames = frames;
96
+ this.frameW = frameW;
97
+ this.frameH = frameH;
98
+ this.fps = fps;
99
+
100
+ this.interval = 1000 / this.fps;
101
+ }
102
+
103
+ /**
104
+ * Advances the animation clock and moves to the next frame when the
105
+ * interval has elapsed.
106
+ *
107
+ * @param delta - Time elapsed since the last call, **in milliseconds**.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * // Inside a requestAnimationFrame loop:
112
+ * anim.update(deltaMs);
113
+ * ```
114
+ */
115
+ public update(delta: number) {
116
+ this.elapsed += delta;
117
+
118
+ if (this.elapsed >= this.interval) {
119
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
120
+ this.elapsed = 0;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Draws the current animation frame to the canvas.
126
+ *
127
+ * @remarks
128
+ * This method is currently a **stub**. It resolves the correct
129
+ * `{ col, row }` cell but does not perform the actual
130
+ * `ctx.drawImage()` call. Wire it to {@link Asset} or use
131
+ * {@link SpriteRender} for full rendering.
132
+ *
133
+ * @param _ctx - The canvas 2-D rendering context.
134
+ * @param _destX - Destination X coordinate on the canvas.
135
+ * @param _destY - Destination Y coordinate on the canvas.
136
+ */
137
+ public draw(_ctx: CanvasRenderingContext2D, _destX: number, _destY: number) {
138
+ const frame = this.frames[this.currentFrame];
139
+ if (!frame) return;
140
+ const { col: _col, row: _row } = frame;
141
+ // Asset.drawFrame(ctx, key, col, row, frameW, frameH, destX, destY)
142
+ }
143
+
144
+ /**
145
+ * Resets the animation to its first frame.
146
+ *
147
+ * Call this when switching animations or restarting a sequence.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * anim.reset();
152
+ * anim.update(0); // ensures frame 0 is active
153
+ * ```
154
+ */
155
+ public reset() {
156
+ this.currentFrame = 0;
157
+ this.elapsed = 0;
158
+ }
159
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Static image asset loader with an in-memory cache.
3
+ *
4
+ * `Asset` wraps the native `Image` constructor with a `Promise`-based
5
+ * API and caches loaded images by URL so repeated requests for the same
6
+ * source resolve instantly.
7
+ *
8
+ * @category Core
9
+ * @since 0.1.0
10
+ *
11
+ * @example Loading an image
12
+ * ```ts
13
+ * const image = await Asset.load("sprites/hero.png");
14
+ * ctx.drawImage(image, 0, 0);
15
+ * ```
16
+ *
17
+ * @example Pre-loading multiple assets
18
+ * ```ts
19
+ * await Promise.all([
20
+ * Asset.load("sprites/hero.png"),
21
+ * Asset.load("sprites/enemy.png"),
22
+ * Asset.load("tiles/grass.png"),
23
+ * ]);
24
+ * ```
25
+ *
26
+ * @see {@link Sprite} — consumes loaded images for spritesheet slicing
27
+ */
28
+ export default class Asset {
29
+ /**
30
+ * Internal cache mapping source URLs to their loaded
31
+ * `HTMLImageElement` instances.
32
+ */
33
+ private static cache: Map<string, HTMLImageElement> = new Map();
34
+
35
+ /**
36
+ * Loads an image from the given URL.
37
+ *
38
+ * If the image has been loaded before, the cached `HTMLImageElement`
39
+ * is returned immediately (the `Promise` resolves synchronously on
40
+ * the microtask queue).
41
+ *
42
+ * @param src - URL or relative path of the image to load.
43
+ * @returns A `Promise` that resolves with the loaded
44
+ * `HTMLImageElement`.
45
+ *
46
+ * @throws {Error} If the image fails to load (e.g. 404 or network
47
+ * error). The error message includes the failing `src`.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * try {
52
+ * const img = await Asset.load("missing.png");
53
+ * } catch (err) {
54
+ * console.error(err); // "Failed to load image: missing.png"
55
+ * }
56
+ * ```
57
+ */
58
+ static async load(src: string): Promise<HTMLImageElement> {
59
+ const cache = Asset.cache.get(src);
60
+
61
+ if (cache) {
62
+ return cache;
63
+ }
64
+ return new Promise((resolve, reject) => {
65
+ const image = new Image();
66
+ image.onload = () => {
67
+ Asset.cache.set(src, image);
68
+ resolve(image);
69
+ };
70
+ image.onerror = (_error) => {
71
+ reject(new Error(`Failed to load image: ${src}`));
72
+ };
73
+ image.src = src;
74
+ });
75
+ }
76
+ }
@@ -0,0 +1,145 @@
1
+ import type Entity from "../entities/entity";
2
+
3
+ /**
4
+ * Abstract base class for all entity behaviours in the GameFoo engine.
5
+ *
6
+ * A **behaviour** is a self-contained unit of logic (input handling,
7
+ * collision response, health tracking, rendering, etc.) that can be
8
+ * attached to any {@link Entity} at runtime via
9
+ * {@link Entity.attachBehaviour}.
10
+ *
11
+ * Subclasses **must** implement:
12
+ * - {@link Behaviour.type | type} — a unique string identifier (e.g. `"control"`, `"healthkit"`).
13
+ * - {@link Behaviour.update | update} — called once per frame with `deltaTime`.
14
+ *
15
+ * Subclasses **may** override:
16
+ * - {@link Behaviour.render | render} — draw debug visuals or overlays.
17
+ * - {@link Behaviour.onAttach | onAttach} — setup hook when added to an entity.
18
+ * - {@link Behaviour.onDetach | onDetach} — teardown hook when removed.
19
+ *
20
+ * @typeParam T - The entity type this behaviour operates on.
21
+ * Defaults to {@link Entity}; narrow it to {@link DynamicEntity} or
22
+ * {@link Player} when the behaviour needs velocity, speed, etc.
23
+ *
24
+ * @category Behaviours
25
+ * @since 0.1.0
26
+ *
27
+ * @example Creating a custom behaviour
28
+ * ```ts
29
+ * import { Behaviour, type Entity } from "gamefoo";
30
+ *
31
+ * class Gravity extends Behaviour<Entity> {
32
+ * readonly type = "gravity";
33
+ *
34
+ * update(deltaTime: number): void {
35
+ * this.owner.y += 9.8 * 60 * deltaTime;
36
+ * }
37
+ * }
38
+ * ```
39
+ *
40
+ * @example Attaching to an entity
41
+ * ```ts
42
+ * const entity = new Player("hero", 100, 100, 32, 32);
43
+ * entity.attachBehaviour(new Gravity(entity));
44
+ * ```
45
+ *
46
+ * @see {@link Entity.attachBehaviour}
47
+ * @see {@link Entity.detachBehaviour}
48
+ */
49
+ export abstract class Behaviour<T extends Entity = Entity> {
50
+ /**
51
+ * Reference to the entity that owns this behaviour.
52
+ * Available to subclasses for reading and mutating entity state.
53
+ */
54
+ protected owner: T;
55
+
56
+ /**
57
+ * Unique string identifier for this behaviour type.
58
+ *
59
+ * Used as the look-up key in {@link Entity.getBehaviour} and
60
+ * {@link Entity.hasBehaviour}. Must be a compile-time constant
61
+ * (`readonly`).
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * class Gravity extends Behaviour {
66
+ * readonly type = "gravity";
67
+ * // ...
68
+ * }
69
+ * ```
70
+ */
71
+ abstract readonly type: string;
72
+
73
+ /**
74
+ * Execution priority — lower numbers run first.
75
+ *
76
+ * When an entity has multiple behaviours, they are sorted by priority
77
+ * before each update/render pass.
78
+ *
79
+ * @defaultValue `1`
80
+ */
81
+ public priority: number = 1;
82
+
83
+ /**
84
+ * Whether this behaviour is currently active.
85
+ *
86
+ * Disabled behaviours are skipped during both
87
+ * {@link Entity.updateBehaviours} and {@link Entity.renderBehaviours}.
88
+ *
89
+ * @defaultValue `true`
90
+ */
91
+ public enabled: boolean = true;
92
+
93
+ /**
94
+ * Derived look-up key, equal to {@link Behaviour.type} in lowercase.
95
+ *
96
+ * Used internally by the entity's behaviour map so that look-ups are
97
+ * case-insensitive.
98
+ */
99
+ get key(): string {
100
+ return this.type.toLowerCase();
101
+ }
102
+
103
+ /**
104
+ * Creates a new behaviour bound to the given entity.
105
+ *
106
+ * @param owner - The entity this behaviour will operate on.
107
+ */
108
+ constructor(owner: T) {
109
+ this.owner = owner;
110
+ }
111
+
112
+ /**
113
+ * Called once per frame to advance this behaviour's logic.
114
+ *
115
+ * @param deltaTime - Seconds elapsed since the previous frame.
116
+ */
117
+ abstract update(deltaTime: number): void;
118
+
119
+ /**
120
+ * Optional rendering hook invoked after the entity's own
121
+ * {@link Entity.render} call.
122
+ *
123
+ * Override this to draw debug shapes, health bars, status effects, etc.
124
+ *
125
+ * @param ctx - The canvas 2-D rendering context.
126
+ */
127
+ render?(ctx: CanvasRenderingContext2D): void;
128
+
129
+ /**
130
+ * Lifecycle hook called immediately after the behaviour is attached
131
+ * to an entity via {@link Entity.attachBehaviour}.
132
+ *
133
+ * Use this for one-time setup such as registering with the
134
+ * collision {@link World}.
135
+ */
136
+ onAttach?(): void;
137
+
138
+ /**
139
+ * Lifecycle hook called when the behaviour is removed from an entity
140
+ * via {@link Entity.detachBehaviour}.
141
+ *
142
+ * Use this to unregister from external systems or release resources.
143
+ */
144
+ onDetach?(): void;
145
+ }