@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
@@ -0,0 +1,147 @@
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
+ key;
42
+ /** Ordered list of spritesheet cells that make up the animation. */
43
+ frames;
44
+ /** Width of a single frame in pixels. */
45
+ frameW;
46
+ /** Height of a single frame in pixels. */
47
+ frameH;
48
+ /**
49
+ * Index into {@link Animate.frames} of the frame currently being
50
+ * displayed.
51
+ *
52
+ * @defaultValue `0`
53
+ */
54
+ currentFrame = 0;
55
+ /**
56
+ * Milliseconds accumulated since the last frame advance.
57
+ *
58
+ * @defaultValue `0`
59
+ */
60
+ elapsed = 0;
61
+ /** Computed time between frames in milliseconds (`1000 / fps`). */
62
+ interval;
63
+ /** Playback speed in frames per second. */
64
+ fps;
65
+ /**
66
+ * Creates a new animation sequence.
67
+ *
68
+ * @param key - A unique name for this animation (used as a look-up key).
69
+ * @param frames - An ordered array of `{ col, row }` cells from the
70
+ * spritesheet.
71
+ * @param frameW - Width of each frame in pixels.
72
+ * @param frameH - Height of each frame in pixels.
73
+ * @param fps - Playback speed in frames per second.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const idle = new Animate(
78
+ * "idle",
79
+ * [{ col: 0, row: 1 }, { col: 1, row: 1 }],
80
+ * 64, 64,
81
+ * 8,
82
+ * );
83
+ * ```
84
+ */
85
+ constructor(key, frames, frameW, frameH, fps) {
86
+ this.key = key;
87
+ this.frames = frames;
88
+ this.frameW = frameW;
89
+ this.frameH = frameH;
90
+ this.fps = fps;
91
+ this.interval = 1000 / this.fps;
92
+ }
93
+ /**
94
+ * Advances the animation clock and moves to the next frame when the
95
+ * interval has elapsed.
96
+ *
97
+ * @param delta - Time elapsed since the last call, **in milliseconds**.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * // Inside a requestAnimationFrame loop:
102
+ * anim.update(deltaMs);
103
+ * ```
104
+ */
105
+ update(delta) {
106
+ this.elapsed += delta;
107
+ if (this.elapsed >= this.interval) {
108
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
109
+ this.elapsed = 0;
110
+ }
111
+ }
112
+ /**
113
+ * Draws the current animation frame to the canvas.
114
+ *
115
+ * @remarks
116
+ * This method is currently a **stub**. It resolves the correct
117
+ * `{ col, row }` cell but does not perform the actual
118
+ * `ctx.drawImage()` call. Wire it to {@link Asset} or use
119
+ * {@link SpriteRender} for full rendering.
120
+ *
121
+ * @param _ctx - The canvas 2-D rendering context.
122
+ * @param _destX - Destination X coordinate on the canvas.
123
+ * @param _destY - Destination Y coordinate on the canvas.
124
+ */
125
+ draw(_ctx, _destX, _destY) {
126
+ const frame = this.frames[this.currentFrame];
127
+ if (!frame)
128
+ return;
129
+ const { col: _col, row: _row } = frame;
130
+ // Asset.drawFrame(ctx, key, col, row, frameW, frameH, destX, destY)
131
+ }
132
+ /**
133
+ * Resets the animation to its first frame.
134
+ *
135
+ * Call this when switching animations or restarting a sequence.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * anim.reset();
140
+ * anim.update(0); // ensures frame 0 is active
141
+ * ```
142
+ */
143
+ reset() {
144
+ this.currentFrame = 0;
145
+ this.elapsed = 0;
146
+ }
147
+ }
@@ -0,0 +1,74 @@
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
+ static cache = new Map();
34
+ /**
35
+ * Loads an image from the given URL.
36
+ *
37
+ * If the image has been loaded before, the cached `HTMLImageElement`
38
+ * is returned immediately (the `Promise` resolves synchronously on
39
+ * the microtask queue).
40
+ *
41
+ * @param src - URL or relative path of the image to load.
42
+ * @returns A `Promise` that resolves with the loaded
43
+ * `HTMLImageElement`.
44
+ *
45
+ * @throws {Error} If the image fails to load (e.g. 404 or network
46
+ * error). The error message includes the failing `src`.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * try {
51
+ * const img = await Asset.load("missing.png");
52
+ * } catch (err) {
53
+ * console.error(err); // "Failed to load image: missing.png"
54
+ * }
55
+ * ```
56
+ */
57
+ static async load(src) {
58
+ const cache = Asset.cache.get(src);
59
+ if (cache) {
60
+ return cache;
61
+ }
62
+ return new Promise((resolve, reject) => {
63
+ const image = new Image();
64
+ image.onload = () => {
65
+ Asset.cache.set(src, image);
66
+ resolve(image);
67
+ };
68
+ image.onerror = (_error) => {
69
+ reject(new Error(`Failed to load image: ${src}`));
70
+ };
71
+ image.src = src;
72
+ });
73
+ }
74
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Abstract base class for all entity behaviours in the GameFoo engine.
3
+ *
4
+ * A **behaviour** is a self-contained unit of logic (input handling,
5
+ * collision response, health tracking, rendering, etc.) that can be
6
+ * attached to any {@link Entity} at runtime via
7
+ * {@link Entity.attachBehaviour}.
8
+ *
9
+ * Subclasses **must** implement:
10
+ * - {@link Behaviour.type | type} — a unique string identifier (e.g. `"control"`, `"healthkit"`).
11
+ * - {@link Behaviour.update | update} — called once per frame with `deltaTime`.
12
+ *
13
+ * Subclasses **may** override:
14
+ * - {@link Behaviour.render | render} — draw debug visuals or overlays.
15
+ * - {@link Behaviour.onAttach | onAttach} — setup hook when added to an entity.
16
+ * - {@link Behaviour.onDetach | onDetach} — teardown hook when removed.
17
+ *
18
+ * @typeParam T - The entity type this behaviour operates on.
19
+ * Defaults to {@link Entity}; narrow it to {@link DynamicEntity} or
20
+ * {@link Player} when the behaviour needs velocity, speed, etc.
21
+ *
22
+ * @category Behaviours
23
+ * @since 0.1.0
24
+ *
25
+ * @example Creating a custom behaviour
26
+ * ```ts
27
+ * import { Behaviour, type Entity } from "gamefoo";
28
+ *
29
+ * class Gravity extends Behaviour<Entity> {
30
+ * readonly type = "gravity";
31
+ *
32
+ * update(deltaTime: number): void {
33
+ * this.owner.y += 9.8 * 60 * deltaTime;
34
+ * }
35
+ * }
36
+ * ```
37
+ *
38
+ * @example Attaching to an entity
39
+ * ```ts
40
+ * const entity = new Player("hero", 100, 100, 32, 32);
41
+ * entity.attachBehaviour(new Gravity(entity));
42
+ * ```
43
+ *
44
+ * @see {@link Entity.attachBehaviour}
45
+ * @see {@link Entity.detachBehaviour}
46
+ */
47
+ export class Behaviour {
48
+ /**
49
+ * Reference to the entity that owns this behaviour.
50
+ * Available to subclasses for reading and mutating entity state.
51
+ */
52
+ owner;
53
+ /**
54
+ * Execution priority — lower numbers run first.
55
+ *
56
+ * When an entity has multiple behaviours, they are sorted by priority
57
+ * before each update/render pass.
58
+ *
59
+ * @defaultValue `1`
60
+ */
61
+ priority = 1;
62
+ /**
63
+ * Whether this behaviour is currently active.
64
+ *
65
+ * Disabled behaviours are skipped during both
66
+ * {@link Entity.updateBehaviours} and {@link Entity.renderBehaviours}.
67
+ *
68
+ * @defaultValue `true`
69
+ */
70
+ enabled = true;
71
+ /**
72
+ * Derived look-up key, equal to {@link Behaviour.type} in lowercase.
73
+ *
74
+ * Used internally by the entity's behaviour map so that look-ups are
75
+ * case-insensitive.
76
+ */
77
+ get key() {
78
+ return this.type.toLowerCase();
79
+ }
80
+ /**
81
+ * Creates a new behaviour bound to the given entity.
82
+ *
83
+ * @param owner - The entity this behaviour will operate on.
84
+ */
85
+ constructor(owner) {
86
+ this.owner = owner;
87
+ }
88
+ }
@@ -0,0 +1,186 @@
1
+ import { Behaviour } from "../behaviour";
2
+ /**
3
+ * Collision behaviour that can be attached to any {@link Entity}.
4
+ *
5
+ * When attached, the `Collidable` automatically registers itself with
6
+ * the engine's {@link World} (via {@link Collidable.onAttach}) and
7
+ * unregisters on detach. Each frame the `World` queries the collider's
8
+ * shape, bounds, and tags to determine intersections.
9
+ *
10
+ * @category Behaviours
11
+ * @since 0.1.0
12
+ *
13
+ * @example Creating and attaching a box collider
14
+ * ```ts
15
+ * import { Collidable, Entity, type CollisionInfo } from "gamefoo";
16
+ *
17
+ * const entity = new Enemy("goblin", 100, 200, 30, 30);
18
+ *
19
+ * entity.attachBehaviour(
20
+ * new Collidable(entity, engine.collisions, {
21
+ * shape: { type: "aabb", width: 30, height: 30 },
22
+ * layer: 0,
23
+ * tags: new Set(["enemy"]),
24
+ * solid: true,
25
+ * collidesWith: new Set(["player"]),
26
+ * onCollision: (info: CollisionInfo) => {
27
+ * console.log(`${info.self.id} hit ${info.other.id}`);
28
+ * },
29
+ * }),
30
+ * );
31
+ * ```
32
+ *
33
+ * @example Circle collider for a projectile
34
+ * ```ts
35
+ * entity.attachBehaviour(
36
+ * new Collidable(bullet, engine.collisions, {
37
+ * shape: { type: "circle", radius: 4 },
38
+ * tags: new Set(["bullet"]),
39
+ * collidesWith: new Set(["enemy"]),
40
+ * }),
41
+ * );
42
+ * ```
43
+ *
44
+ * @see {@link World} — the collision detection system
45
+ * @see {@link ColliderShape} — supported shape types
46
+ * @see {@link CollisionInfo} — payload delivered to callbacks
47
+ * @see {@link Behaviour} — abstract base class
48
+ */
49
+ export class Collidable extends Behaviour {
50
+ /** @inheritDoc */
51
+ type = "collidable";
52
+ /**
53
+ * Geometric shape used for intersection tests.
54
+ *
55
+ * @see {@link ColliderShape}
56
+ */
57
+ shape;
58
+ /**
59
+ * Collision layer. Only colliders sharing the same layer value are
60
+ * tested.
61
+ *
62
+ * @defaultValue `0`
63
+ */
64
+ layer = 0;
65
+ /**
66
+ * Tags identifying this collider (e.g. `"player"`, `"enemy"`).
67
+ *
68
+ * @defaultValue empty `Set`
69
+ */
70
+ tags = new Set();
71
+ /**
72
+ * Tags this collider wants to be notified about.
73
+ *
74
+ * @defaultValue empty `Set`
75
+ */
76
+ collidesWith = new Set();
77
+ /**
78
+ * Whether this collider participates in overlap resolution.
79
+ *
80
+ * @defaultValue `false`
81
+ */
82
+ solid = false;
83
+ /**
84
+ * Whether the owning entity is immovable during overlap resolution.
85
+ *
86
+ * @defaultValue `false`
87
+ */
88
+ fixed = false;
89
+ /**
90
+ * User-supplied callback invoked when a tag-matched collision is
91
+ * detected.
92
+ */
93
+ onCollision;
94
+ /** Reference to the {@link World} this collider is registered with. */
95
+ world;
96
+ /**
97
+ * Creates a new collidable behaviour.
98
+ *
99
+ * @param owner - The game object entity that owns this collider.
100
+ * @param world - The collision {@link World} to register with.
101
+ * @param options - Configuration for shape, tags, solidity, and
102
+ * callbacks. See {@link CollidableOptions}.
103
+ */
104
+ constructor(owner, world, options) {
105
+ super(owner);
106
+ this.world = world;
107
+ const size = owner.getSize();
108
+ this.shape = options.shape ?? {
109
+ type: "aabb",
110
+ width: size.width,
111
+ height: size.height,
112
+ };
113
+ this.layer = options.layer ?? 0;
114
+ this.tags = options.tags ?? new Set();
115
+ this.solid = options.solid ?? false;
116
+ this.fixed = options.fixed ?? false;
117
+ this.collidesWith = options.collidesWith ?? new Set();
118
+ this.onCollision = options.onCollision || (() => { });
119
+ }
120
+ /**
121
+ * No-op — collision logic lives in {@link World.detect}.
122
+ *
123
+ * @param _deltaTime - Unused.
124
+ */
125
+ update(_deltaTime) { }
126
+ /**
127
+ * Lifecycle hook: registers this collider with the {@link World}
128
+ * when the behaviour is attached to an entity.
129
+ *
130
+ * @see {@link Behaviour.onAttach}
131
+ */
132
+ onAttach() {
133
+ this.world.register(this);
134
+ }
135
+ /**
136
+ * Lifecycle hook: removes this collider from the {@link World}
137
+ * when the behaviour is detached.
138
+ *
139
+ * @see {@link Behaviour.onDetach}
140
+ */
141
+ onDetach() {
142
+ this.world.unregister(this);
143
+ }
144
+ /**
145
+ * Returns the {@link Entity} that owns this behaviour.
146
+ *
147
+ * Used by the {@link World} to read and mutate entity position
148
+ * during overlap resolution.
149
+ *
150
+ * @returns The owning entity.
151
+ */
152
+ getOwner() {
153
+ return this.owner;
154
+ }
155
+ /**
156
+ * Computes this collider's axis-aligned bounding rectangle in
157
+ * world-space, accounting for the shape's optional offset.
158
+ *
159
+ * @returns A {@link WorldBounds} rectangle.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const bounds = collidable.getWorldBounds();
164
+ * // { x: 100, y: 200, width: 30, height: 30 }
165
+ * ```
166
+ */
167
+ getWorldBounds() {
168
+ const pos = this.owner.getPosition();
169
+ const offset = "offset" in this.shape && this.shape.offset ? this.shape.offset : { x: 0, y: 0 };
170
+ if (this.shape.type === "aabb") {
171
+ return {
172
+ x: pos.x + offset.x,
173
+ y: pos.y + offset.y,
174
+ width: this.shape.width,
175
+ height: this.shape.height,
176
+ };
177
+ }
178
+ const r = this.shape.radius;
179
+ return {
180
+ x: pos.x + offset.x - r,
181
+ y: pos.y + offset.y - r,
182
+ width: r * 2,
183
+ height: r * 2,
184
+ };
185
+ }
186
+ }
@@ -0,0 +1,75 @@
1
+ import { Behaviour } from "../behaviour";
2
+ /**
3
+ * Keyboard-driven movement behaviour for a {@link Entity}.
4
+ *
5
+ * `Control` reads the current keyboard state from an {@link Input}
6
+ * instance every frame and translates WASD / arrow-key presses into
7
+ * entity position changes. Diagonal movement is normalised so the
8
+ * entity moves at a consistent speed in all directions.
9
+ *
10
+ * @category Behaviours
11
+ * @since 0.1.0
12
+ *
13
+ * @example Attaching to a player
14
+ * ```ts
15
+ * import { Control, Input, Player } from "gamefoo";
16
+ *
17
+ * const input = new Input();
18
+ * const player = new Player("hero", 400, 300, 50, 50);
19
+ *
20
+ * player.attachBehaviour(new Control(player, input));
21
+ * ```
22
+ *
23
+ * @see {@link Input} — the polling input manager consumed by this behaviour
24
+ * @see {@link Behaviour} — abstract base class
25
+ */
26
+ export class Control extends Behaviour {
27
+ /** @inheritDoc */
28
+ type = "control";
29
+ /** The input manager to poll each frame. */
30
+ input;
31
+ /**
32
+ * Movement speed in pixels per second.
33
+ *
34
+ * @defaultValue `500`
35
+ */
36
+ speed = 500;
37
+ /**
38
+ * Creates a new keyboard control behaviour.
39
+ *
40
+ * @param owner - The game object entity whose position will be updated.
41
+ * @param input - The {@link Input} instance to read key state from.
42
+ */
43
+ constructor(owner, input) {
44
+ super(owner);
45
+ this.input = input;
46
+ }
47
+ /**
48
+ * Reads the current key state and moves the owner entity.
49
+ *
50
+ * Supported keys: `W` / `ArrowUp`, `S` / `ArrowDown`,
51
+ * `A` / `ArrowLeft`, `D` / `ArrowRight`.
52
+ *
53
+ * Diagonal input is normalised so the effective speed remains
54
+ * constant regardless of direction.
55
+ *
56
+ * @param deltaTime - Seconds elapsed since the previous frame.
57
+ */
58
+ update(deltaTime) {
59
+ let dx = 0;
60
+ let dy = 0;
61
+ if (this.input.isKeyDown("a") || this.input.isKeyDown("arrowleft"))
62
+ dx -= 1;
63
+ if (this.input.isKeyDown("d") || this.input.isKeyDown("arrowright"))
64
+ dx += 1;
65
+ if (this.input.isKeyDown("w") || this.input.isKeyDown("arrowup"))
66
+ dy -= 1;
67
+ if (this.input.isKeyDown("s") || this.input.isKeyDown("arrowdown"))
68
+ dy += 1;
69
+ const len = Math.sqrt(dx * dx + dy * dy);
70
+ if (len > 0) {
71
+ this.owner.x += (dx / len) * this.speed * deltaTime;
72
+ this.owner.y += (dy / len) * this.speed * deltaTime;
73
+ }
74
+ }
75
+ }