@inglorious/engine 0.1.1 → 0.3.0

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 (201) hide show
  1. package/README.md +75 -72
  2. package/package.json +15 -37
  3. package/src/{engine/ai → ai}/movement/dynamic/align.js +9 -9
  4. package/src/{engine/ai → ai}/movement/dynamic/arrive.js +9 -10
  5. package/src/{engine/ai → ai}/movement/dynamic/evade.js +9 -9
  6. package/src/{engine/ai → ai}/movement/dynamic/face.js +5 -6
  7. package/src/{engine/ai/movement/dynamic/seek.js → ai/movement/dynamic/flee.js} +8 -7
  8. package/src/ai/movement/dynamic/look-where-youre-going.js +16 -0
  9. package/src/{engine/ai → ai}/movement/dynamic/match-velocity.js +9 -8
  10. package/src/{engine/ai → ai}/movement/dynamic/pursue.js +9 -9
  11. package/src/{engine/ai/movement/dynamic/flee.js → ai/movement/dynamic/seek.js} +7 -8
  12. package/src/{engine/ai → ai}/movement/dynamic/wander.js +9 -10
  13. package/src/{engine/ai → ai}/movement/kinematic/align.js +7 -7
  14. package/src/{engine/ai → ai}/movement/kinematic/arrive.js +8 -8
  15. package/src/{engine/ai → ai}/movement/kinematic/face.js +5 -6
  16. package/src/{engine/ai → ai}/movement/kinematic/flee.js +5 -5
  17. package/src/{engine/ai → ai}/movement/kinematic/seek.js +5 -5
  18. package/src/{engine/ai → ai}/movement/kinematic/seek.test.js +10 -10
  19. package/src/{engine/ai → ai}/movement/kinematic/wander-as-seek.js +9 -9
  20. package/src/{engine/ai → ai}/movement/kinematic/wander.js +5 -5
  21. package/src/animation/sprite.js +101 -0
  22. package/src/animation/ticker.js +38 -0
  23. package/src/behaviors/camera.js +68 -0
  24. package/src/behaviors/controls/dynamic/modern.js +76 -0
  25. package/src/behaviors/controls/dynamic/shooter.js +84 -0
  26. package/src/behaviors/controls/dynamic/tank.js +69 -0
  27. package/src/behaviors/controls/event-handlers.js +17 -0
  28. package/src/behaviors/controls/kinematic/modern.js +76 -0
  29. package/src/behaviors/controls/kinematic/shooter.js +82 -0
  30. package/src/behaviors/controls/kinematic/tank.js +67 -0
  31. package/src/behaviors/debug/collision.js +29 -0
  32. package/src/behaviors/fps.js +29 -0
  33. package/src/behaviors/fsm.js +33 -0
  34. package/src/{game/decorators → behaviors}/fsm.test.js +15 -22
  35. package/src/behaviors/game.js +15 -0
  36. package/src/behaviors/input/controls.js +37 -0
  37. package/src/behaviors/input/gamepad.js +114 -0
  38. package/src/behaviors/input/input.js +48 -0
  39. package/src/behaviors/input/keyboard.js +64 -0
  40. package/src/behaviors/input/mouse.js +91 -0
  41. package/src/behaviors/physics/bouncy.js +25 -0
  42. package/src/behaviors/physics/clamped.js +36 -0
  43. package/src/{game/decorators/collisions.js → behaviors/physics/collidable.js} +3 -7
  44. package/src/behaviors/physics/jumpable.js +145 -0
  45. package/src/behaviors/ui/button.js +17 -0
  46. package/src/collision/detection.js +110 -0
  47. package/src/core/api.js +34 -0
  48. package/src/core/dev-tools.js +135 -0
  49. package/src/core/engine.js +119 -0
  50. package/src/core/loop.js +15 -0
  51. package/src/{engine/loop → core/loops}/animation-frame.js +1 -2
  52. package/src/{engine/loop → core/loops}/elapsed.js +1 -2
  53. package/src/{engine/loop → core/loops}/fixed.js +1 -2
  54. package/src/{engine/loop → core/loops}/flash.js +1 -2
  55. package/src/{engine/loop → core/loops}/lag.js +1 -2
  56. package/src/core/select.js +26 -0
  57. package/src/core/store.js +178 -0
  58. package/src/core/store.test.js +110 -0
  59. package/src/main.js +7 -2
  60. package/src/{engine/movement → movement}/dynamic/modern.js +3 -6
  61. package/src/{engine/movement → movement}/dynamic/tank.js +9 -9
  62. package/src/{engine/movement → movement}/kinematic/modern.js +3 -3
  63. package/src/movement/kinematic/modern.test.js +27 -0
  64. package/src/{engine/movement → movement}/kinematic/tank.js +5 -5
  65. package/src/physics/bounds.js +138 -0
  66. package/src/physics/position.js +43 -0
  67. package/src/physics/position.test.js +80 -0
  68. package/src/systems/sprite-animation.js +27 -0
  69. package/src/engine/ai/movement/dynamic/look-where-youre-going.js +0 -17
  70. package/src/engine/collision/detection.js +0 -115
  71. package/src/engine/loop.js +0 -15
  72. package/src/engine/movement/kinematic/modern.test.js +0 -27
  73. package/src/engine/store.js +0 -174
  74. package/src/engine/store.test.js +0 -256
  75. package/src/engine.js +0 -74
  76. package/src/game/animation.js +0 -26
  77. package/src/game/bounds.js +0 -66
  78. package/src/game/decorators/character.js +0 -5
  79. package/src/game/decorators/clamp-to-bounds.js +0 -15
  80. package/src/game/decorators/controls/dynamic/modern.js +0 -48
  81. package/src/game/decorators/controls/dynamic/shooter.js +0 -47
  82. package/src/game/decorators/controls/dynamic/tank.js +0 -55
  83. package/src/game/decorators/controls/kinematic/modern.js +0 -49
  84. package/src/game/decorators/controls/kinematic/shooter.js +0 -45
  85. package/src/game/decorators/controls/kinematic/tank.js +0 -52
  86. package/src/game/decorators/debug/collisions.js +0 -32
  87. package/src/game/decorators/double-jump.js +0 -70
  88. package/src/game/decorators/fps.js +0 -30
  89. package/src/game/decorators/fsm.js +0 -27
  90. package/src/game/decorators/game.js +0 -11
  91. package/src/game/decorators/image/image.js +0 -5
  92. package/src/game/decorators/image/sprite.js +0 -5
  93. package/src/game/decorators/image/tilemap.js +0 -5
  94. package/src/game/decorators/input/controls.js +0 -27
  95. package/src/game/decorators/input/gamepad.js +0 -74
  96. package/src/game/decorators/input/input.js +0 -41
  97. package/src/game/decorators/input/keyboard.js +0 -49
  98. package/src/game/decorators/input/mouse.js +0 -65
  99. package/src/game/decorators/jump.js +0 -72
  100. package/src/game/decorators/platform.js +0 -5
  101. package/src/game/decorators/ui/button.js +0 -21
  102. package/src/game/sprite.js +0 -119
  103. package/src/ui/canvas/absolute-position.js +0 -17
  104. package/src/ui/canvas/character.js +0 -35
  105. package/src/ui/canvas/form/button.js +0 -25
  106. package/src/ui/canvas/fps.js +0 -18
  107. package/src/ui/canvas/image/hitmask.js +0 -37
  108. package/src/ui/canvas/image/image.js +0 -37
  109. package/src/ui/canvas/image/sprite.js +0 -49
  110. package/src/ui/canvas/image/tilemap.js +0 -64
  111. package/src/ui/canvas/mouse.js +0 -37
  112. package/src/ui/canvas/shapes/circle.js +0 -31
  113. package/src/ui/canvas/shapes/rectangle.js +0 -31
  114. package/src/ui/canvas.js +0 -81
  115. package/src/ui/react/game/character/character.module.scss +0 -17
  116. package/src/ui/react/game/character/index.jsx +0 -30
  117. package/src/ui/react/game/cursor/cursor.module.scss +0 -47
  118. package/src/ui/react/game/cursor/index.jsx +0 -20
  119. package/src/ui/react/game/form/fields/field/field.module.scss +0 -5
  120. package/src/ui/react/game/form/fields/field/index.jsx +0 -56
  121. package/src/ui/react/game/form/fields/fields.module.scss +0 -48
  122. package/src/ui/react/game/form/fields/index.jsx +0 -12
  123. package/src/ui/react/game/form/form.module.scss +0 -18
  124. package/src/ui/react/game/form/index.jsx +0 -22
  125. package/src/ui/react/game/fps/index.jsx +0 -16
  126. package/src/ui/react/game/game.jsx +0 -71
  127. package/src/ui/react/game/index.jsx +0 -29
  128. package/src/ui/react/game/platform/index.jsx +0 -30
  129. package/src/ui/react/game/platform/platform.module.scss +0 -7
  130. package/src/ui/react/game/scene/index.jsx +0 -25
  131. package/src/ui/react/game/scene/scene.module.scss +0 -9
  132. package/src/ui/react/game/sprite/index.jsx +0 -58
  133. package/src/ui/react/game/sprite/sprite.module.css +0 -3
  134. package/src/ui/react/game/stats/index.jsx +0 -22
  135. package/src/ui/react/hocs/with-absolute-position/index.jsx +0 -20
  136. package/src/ui/react/hocs/with-absolute-position/with-absolute-position.module.scss +0 -5
  137. package/src/ui/react/index.jsx +0 -9
  138. package/src/utils/algorithms/decision-tree.js +0 -24
  139. package/src/utils/algorithms/decision-tree.test.js +0 -102
  140. package/src/utils/algorithms/path-finding.js +0 -155
  141. package/src/utils/algorithms/path-finding.test.js +0 -151
  142. package/src/utils/algorithms/types.d.ts +0 -28
  143. package/src/utils/data-structures/array.js +0 -83
  144. package/src/utils/data-structures/array.test.js +0 -173
  145. package/src/utils/data-structures/board.js +0 -159
  146. package/src/utils/data-structures/board.test.js +0 -242
  147. package/src/utils/data-structures/boolean.js +0 -9
  148. package/src/utils/data-structures/heap.js +0 -164
  149. package/src/utils/data-structures/heap.test.js +0 -103
  150. package/src/utils/data-structures/object.js +0 -102
  151. package/src/utils/data-structures/object.test.js +0 -121
  152. package/src/utils/data-structures/objects.js +0 -48
  153. package/src/utils/data-structures/objects.test.js +0 -99
  154. package/src/utils/data-structures/tree.js +0 -36
  155. package/src/utils/data-structures/tree.test.js +0 -33
  156. package/src/utils/data-structures/types.d.ts +0 -4
  157. package/src/utils/functions/functions.js +0 -19
  158. package/src/utils/functions/functions.test.js +0 -23
  159. package/src/utils/math/geometry/circle.js +0 -117
  160. package/src/utils/math/geometry/circle.test.js +0 -97
  161. package/src/utils/math/geometry/hitmask.js +0 -39
  162. package/src/utils/math/geometry/hitmask.test.js +0 -84
  163. package/src/utils/math/geometry/line.js +0 -35
  164. package/src/utils/math/geometry/line.test.js +0 -49
  165. package/src/utils/math/geometry/platform.js +0 -42
  166. package/src/utils/math/geometry/platform.test.js +0 -133
  167. package/src/utils/math/geometry/point.js +0 -71
  168. package/src/utils/math/geometry/point.test.js +0 -81
  169. package/src/utils/math/geometry/rectangle.js +0 -45
  170. package/src/utils/math/geometry/rectangle.test.js +0 -42
  171. package/src/utils/math/geometry/segment.js +0 -80
  172. package/src/utils/math/geometry/segment.test.js +0 -183
  173. package/src/utils/math/geometry/triangle.js +0 -15
  174. package/src/utils/math/geometry/triangle.test.js +0 -11
  175. package/src/utils/math/geometry/types.d.ts +0 -23
  176. package/src/utils/math/linear-algebra/2d.js +0 -28
  177. package/src/utils/math/linear-algebra/2d.test.js +0 -17
  178. package/src/utils/math/linear-algebra/quaternion.js +0 -22
  179. package/src/utils/math/linear-algebra/quaternion.test.js +0 -25
  180. package/src/utils/math/linear-algebra/quaternions.js +0 -20
  181. package/src/utils/math/linear-algebra/quaternions.test.js +0 -29
  182. package/src/utils/math/linear-algebra/types.d.ts +0 -4
  183. package/src/utils/math/linear-algebra/vector.js +0 -302
  184. package/src/utils/math/linear-algebra/vector.test.js +0 -257
  185. package/src/utils/math/linear-algebra/vectors.js +0 -122
  186. package/src/utils/math/linear-algebra/vectors.test.js +0 -65
  187. package/src/utils/math/numbers.js +0 -90
  188. package/src/utils/math/numbers.test.js +0 -137
  189. package/src/utils/math/rng.js +0 -44
  190. package/src/utils/math/rng.test.js +0 -39
  191. package/src/utils/math/statistics.js +0 -43
  192. package/src/utils/math/statistics.test.js +0 -47
  193. package/src/utils/math/trigonometry.js +0 -89
  194. package/src/utils/math/trigonometry.test.js +0 -52
  195. package/src/utils/physics/acceleration.js +0 -63
  196. package/src/utils/physics/friction.js +0 -30
  197. package/src/utils/physics/friction.test.js +0 -44
  198. package/src/utils/physics/gravity.js +0 -71
  199. package/src/utils/physics/gravity.test.js +0 -80
  200. package/src/utils/physics/jump.js +0 -41
  201. package/src/utils/physics/velocity.js +0 -38
@@ -0,0 +1,138 @@
1
+ import {
2
+ angle,
3
+ clamp,
4
+ createVector,
5
+ fromAngle,
6
+ multiply,
7
+ zero,
8
+ } from "@inglorious/utils/math/linear-algebra/vector.js"
9
+ import { sum } from "@inglorious/utils/math/linear-algebra/vectors.js"
10
+ import { abs } from "@inglorious/utils/math/numbers.js"
11
+
12
+ const DOUBLE = 2
13
+ const HALF = 2
14
+ const X = 0
15
+ const Z = 2
16
+
17
+ export function bounce(entity, dt, [minX, minZ, maxX, maxZ]) {
18
+ const [x, , z] = entity.position
19
+
20
+ const velocity = createVector(entity.maxSpeed, entity.orientation)
21
+ if (x < minX || x >= maxX) {
22
+ velocity[X] = -velocity[X]
23
+ }
24
+
25
+ if (z < minZ || z >= maxZ) {
26
+ velocity[Z] = -velocity[Z]
27
+ }
28
+
29
+ const position = sum(entity.position, multiply(velocity, dt))
30
+ const orientation = angle(velocity)
31
+
32
+ return { velocity, position, orientation }
33
+ }
34
+
35
+ const ClampToBoundsByShape = {
36
+ rectangle(entity, [minX, minZ, maxX, maxZ], collisionGroup) {
37
+ const [width, height, depth] =
38
+ entity.collisions[collisionGroup].size ?? entity.size
39
+
40
+ const halfWidth = width / HALF
41
+ const halfHeight = height / HALF
42
+ const halfDepth = depth / HALF
43
+
44
+ return clamp(
45
+ entity.position,
46
+ [minX + halfWidth, minZ + halfHeight, minZ + halfDepth],
47
+ [maxX - halfWidth, maxZ - halfHeight, maxZ - halfDepth],
48
+ )
49
+ },
50
+
51
+ circle(entity, [minX, minY, maxX, maxY], collisionGroup, depthAxis = "y") {
52
+ const radius = entity.collisions[collisionGroup].radius ?? entity.radius
53
+
54
+ if (depthAxis === "z") {
55
+ return clamp(
56
+ entity.position,
57
+ [minX + radius, minY + radius, minY],
58
+ [maxX - radius, maxY - radius, maxY],
59
+ )
60
+ }
61
+
62
+ return clamp(
63
+ entity.position,
64
+ [minX + radius, minY, minY + radius],
65
+ [maxX - radius, maxY, maxY - radius],
66
+ )
67
+ },
68
+
69
+ point(entity, [minX, minZ, maxX, maxZ]) {
70
+ return clamp(entity.position, [minX, minZ, minZ], [maxX, maxZ, maxZ])
71
+ },
72
+ }
73
+
74
+ export function clampToBounds(
75
+ entity,
76
+ bounds,
77
+ collisionGroup = "bounds",
78
+ depthAxis,
79
+ ) {
80
+ const shape = entity.collisions[collisionGroup].shape || "rectangle"
81
+ const handler = ClampToBoundsByShape[shape] || ClampToBoundsByShape.point
82
+ return handler(entity, bounds, collisionGroup, depthAxis)
83
+ }
84
+
85
+ export function flip(entity, [minX, minZ, maxX, maxZ]) {
86
+ const [x, , z] = entity.position
87
+
88
+ entity.collisions ??= {}
89
+ entity.collisions.bounds ??= {}
90
+ entity.collisions.bounds.shape ??= "rectangle"
91
+
92
+ let width, height, depth
93
+ if (entity.collisions.bounds.shape === "circle") {
94
+ width = entity.collisions.bounds.radius * DOUBLE
95
+ height = entity.collisions.bounds.radius * DOUBLE
96
+ depth = entity.collisions.bounds.radius * DOUBLE
97
+ } else {
98
+ ;[width, height, depth] = entity.collisions.bounds.size ?? entity.size
99
+ }
100
+ const halfWidth = width / HALF
101
+ const halfHeight = height / HALF
102
+ const halfDepth = depth / HALF
103
+
104
+ const left = x - halfWidth
105
+ const right = x + halfWidth
106
+ const bottom = z - halfHeight
107
+ const top = z + halfHeight
108
+ const back = z - halfDepth
109
+ const front = z + halfDepth
110
+
111
+ const direction = fromAngle(entity.orientation)
112
+
113
+ if (
114
+ left < minX ||
115
+ right >= maxX ||
116
+ bottom < minZ ||
117
+ top >= maxZ ||
118
+ back < minZ ||
119
+ front >= maxZ
120
+ ) {
121
+ if (left < minX) {
122
+ direction[X] = abs(direction[X])
123
+ } else if (right >= maxX) {
124
+ direction[X] = -abs(direction[X])
125
+ }
126
+
127
+ if (back < minZ) {
128
+ direction[Z] = abs(direction[Z])
129
+ } else if (front >= maxZ) {
130
+ direction[Z] = -abs(direction[Z])
131
+ }
132
+
133
+ entity.acceleration = zero()
134
+ entity.velocity = zero()
135
+ }
136
+
137
+ entity.orientation = angle(direction)
138
+ }
@@ -0,0 +1,43 @@
1
+ const HALF = 2
2
+
3
+ export function calculateLandingPosition(
4
+ entity,
5
+ target,
6
+ collisionGroup = "platform",
7
+ ) {
8
+ const entityShape = entity.collisions[collisionGroup]?.shape
9
+
10
+ if (CalculatePY[entityShape]) {
11
+ return CalculatePY[entityShape](entity, target, collisionGroup)
12
+ }
13
+
14
+ return calculatePYForPoint(entity, target, collisionGroup)
15
+ }
16
+
17
+ function calculatePYForPoint(entity, target, collisionGroup) {
18
+ const [, targetY] = target.position
19
+ const [, targetHeight] = target.collisions[collisionGroup].size ?? target.size
20
+ return targetY + targetHeight / HALF
21
+ }
22
+
23
+ function calculatePYForCircle(entity, target, collisionGroup) {
24
+ const entityRadius = entity.collisions[collisionGroup].radius ?? entity.radius
25
+
26
+ const [, targetY] = target.position
27
+ const [, targetHeight] = target.collisions[collisionGroup].size ?? target.size
28
+ return targetY + targetHeight / HALF + entityRadius
29
+ }
30
+
31
+ function calculatePYForRectangle(entity, target, collisionGroup) {
32
+ const [, entityHeight] = entity.collisions[collisionGroup].size ?? entity.size
33
+
34
+ const [, targetY] = target.position
35
+ const [, targetHeight] = target.collisions[collisionGroup].size ?? target.size
36
+ return targetY + targetHeight / HALF + entityHeight / HALF
37
+ }
38
+
39
+ const CalculatePY = {
40
+ point: calculatePYForPoint,
41
+ circle: calculatePYForCircle,
42
+ rectangle: calculatePYForRectangle,
43
+ }
@@ -0,0 +1,80 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { calculateLandingPosition } from "./position.js"
4
+
5
+ test("it should calculate the landing position for a point entity on a platform", () => {
6
+ const entity = {
7
+ collisions: {
8
+ platform: { shape: "point" },
9
+ },
10
+ }
11
+ const target = {
12
+ position: [0, 0, 0],
13
+ collisions: {
14
+ platform: { size: [20, 10, 0] },
15
+ },
16
+ }
17
+ const collisionGroup = "platform"
18
+
19
+ const py = calculateLandingPosition(entity, target, collisionGroup)
20
+
21
+ expect(py).toBe(5)
22
+ })
23
+
24
+ test("it should calculate the landing position for a circular entity on a platform", () => {
25
+ const entity = {
26
+ collisions: {
27
+ platform: { shape: "circle", radius: 5 },
28
+ },
29
+ }
30
+ const target = {
31
+ position: [0, 0, 0],
32
+ collisions: {
33
+ platform: { size: [20, 10, 0] },
34
+ },
35
+ }
36
+ const collisionGroup = "platform"
37
+
38
+ const py = calculateLandingPosition(entity, target, collisionGroup)
39
+
40
+ expect(py).toBe(10)
41
+ })
42
+
43
+ test("it should calculate the landing position for a rectangular entity on a platform", () => {
44
+ const entity = {
45
+ size: [10, 10, 0],
46
+ collisions: {
47
+ platform: { shape: "rectangle" },
48
+ },
49
+ }
50
+ const target = {
51
+ position: [0, 0, 0],
52
+ collisions: {
53
+ platform: { size: [20, 10, 0] },
54
+ },
55
+ }
56
+ const collisionGroup = "platform"
57
+
58
+ const py = calculateLandingPosition(entity, target, collisionGroup)
59
+
60
+ expect(py).toBe(10)
61
+ })
62
+
63
+ test("it should fallback to a rectangular calculation for an unknown entity shape", () => {
64
+ const entity = {
65
+ collisions: {
66
+ platform: { shape: "triangle" },
67
+ },
68
+ }
69
+ const target = {
70
+ position: [0, 0, 0],
71
+ collisions: {
72
+ platform: { size: [20, 10, 0] },
73
+ },
74
+ }
75
+ const collisionGroup = "platform"
76
+
77
+ const py = calculateLandingPosition(entity, target, collisionGroup)
78
+
79
+ expect(py).toBe(5)
80
+ })
@@ -0,0 +1,27 @@
1
+ import { Sprite } from "@inglorious/engine/animation/sprite.js"
2
+
3
+ /**
4
+ * Advances sprite animations for entities that have a sprite component
5
+ * with an active animation state.
6
+ *
7
+ * @param {Object} state - The game state, containing all entities.
8
+ * @param {Object<string, Object>} state.entities - A map of entity IDs to entity objects.
9
+ * @param {number} dt - The delta time since the last update.
10
+ * @param {Object} api - The game API.
11
+ * @param {Function} api.notify - A function to notify about events.
12
+ */
13
+ export function spriteAnimationSystem() {
14
+ return {
15
+ update(state, dt, api) {
16
+ for (const id in state.entities) {
17
+ const entity = state.entities[id]
18
+
19
+ if (!entity.sprite || !entity.sprite.state) {
20
+ continue
21
+ }
22
+
23
+ Sprite.play(entity.sprite.state, { entity, dt, notify: api.notify })
24
+ }
25
+ },
26
+ }
27
+ }
@@ -1,17 +0,0 @@
1
- import {
2
- angle,
3
- magnitude,
4
- zero,
5
- } from "@inglorious/utils/math/linear-algebra/vector.js"
6
-
7
- import align from "./align.js"
8
-
9
- export default function lookWhereYoureGoing(instance, options) {
10
- const velocity = instance.velocity ?? zero()
11
-
12
- if (!magnitude(velocity)) {
13
- return instance
14
- }
15
-
16
- return align(instance, { orientation: angle(velocity) }, options)
17
- }
@@ -1,115 +0,0 @@
1
- import { filter } from "@inglorious/utils/data-structures/object.js"
2
- import * as circle from "@inglorious/utils/math/geometry/circle.js"
3
- import * as hitmask from "@inglorious/utils/math/geometry/hitmask.js"
4
- import * as line from "@inglorious/utils/math/geometry/line.js"
5
- import * as platform from "@inglorious/utils/math/geometry/platform.js"
6
- import * as point from "@inglorious/utils/math/geometry/point.js"
7
- import * as rectangle from "@inglorious/utils/math/geometry/rectangle.js"
8
- import * as segment from "@inglorious/utils/math/geometry/segment.js"
9
- import { zero } from "@inglorious/utils/math/linear-algebra/vector.js"
10
- import { add } from "@inglorious/utils/math/linear-algebra/vectors.js"
11
-
12
- const Z = 2 // Z-axis index.
13
-
14
- const Shape = {
15
- circle,
16
- line,
17
- platform,
18
- point,
19
- rectangle,
20
- segment,
21
- hitmask,
22
- }
23
-
24
- /**
25
- * Finds the first collision between a point and a list of instances.
26
- *
27
- * @param {Point} instance - The point to check for collisions.
28
- * @param {Options} options - Options for collision detection.
29
- * @returns {Instance | undefined} The first instance that collides with the point, or undefined if none are found.
30
- */
31
- export function findCollision(instance, options = {}) {
32
- const { instances, collisionType = "hitbox" } = options
33
-
34
- const otherInstances = filter(
35
- instances,
36
- (id, { collisions }) => id !== instance.id && collisions?.[collisionType],
37
- )
38
-
39
- return Object.values(otherInstances)
40
- .toSorted((a, b) => a.position[Z] - b.position[Z])
41
- .find((target) => collidesWith(instance, target, collisionType))
42
- }
43
-
44
- export function collidesWith(instance, target, collisionType = "hitbox") {
45
- const instanceCollision = instance.collisions[collisionType]
46
- const instanceShape = {
47
- ...instanceCollision,
48
- position: add(instance.position, instanceCollision.position ?? zero()),
49
- size: instanceCollision.size ?? instance.size,
50
- radius: instanceCollision.radius ?? instance.radius,
51
- }
52
-
53
- const targetCollision = target.collisions[collisionType]
54
- const targetShape = {
55
- ...targetCollision,
56
- position: add(target.position, targetCollision.position ?? zero()),
57
- size: targetCollision.size ?? target.size,
58
- radius: targetCollision.radius ?? target.radius,
59
- }
60
-
61
- return shapeCollidesWith(instanceShape, targetShape)
62
- }
63
-
64
- function shapeCollidesWith(instance, target) {
65
- const shapeFns = Shape[instance.shape]
66
-
67
- switch (target.shape) {
68
- case "circle":
69
- return shapeFns.intersectsCircle(instance, target)
70
-
71
- case "line":
72
- return shapeFns.intersectsLine(instance, target)
73
-
74
- case "platform":
75
- return shapeFns.intersectsPlatform(instance, target)
76
-
77
- case "point":
78
- return shapeFns.intersectsPoint(instance, target)
79
-
80
- case "rectangle":
81
- return shapeFns.intersectsRectangle(instance, target)
82
-
83
- case "segment":
84
- return shapeFns.intersectsSegment(instance, target)
85
- }
86
- }
87
-
88
- export function findCollisions(instance, target, collisionType = "hitbox") {
89
- const instanceCollision = instance.collisions[collisionType]
90
- const shapeFns = Shape[instanceCollision.shape]
91
- const instanceShape = {
92
- ...instanceCollision,
93
- position: add(instance.position, instanceCollision.offset ?? zero()),
94
- heights: flipUpsideDown(
95
- instanceCollision.heights,
96
- instanceCollision.columns,
97
- ),
98
- }
99
-
100
- const targetCollision = target.collisions[collisionType]
101
- const targetShape = {
102
- ...targetCollision,
103
- position: add(target.position, targetCollision.offset ?? zero()),
104
- }
105
-
106
- return shapeFns.findCollisions(instanceShape, targetShape)
107
- }
108
-
109
- export function flipUpsideDown(grid, columns) {
110
- const rows = []
111
- for (let i = 0; i < grid.length; i += columns) {
112
- rows.push(grid.slice(i, i + columns))
113
- }
114
- return rows.reverse().flat()
115
- }
@@ -1,15 +0,0 @@
1
- import AnimationFrameLoop from "./loop/animation-frame.js"
2
- import ElapsedLoop from "./loop/elapsed.js"
3
- import FixedLoop from "./loop/fixed.js"
4
- import FlashLoop from "./loop/flash.js"
5
- import LagLoop from "./loop/lag.js"
6
-
7
- // @see https://gameprogrammingpatterns.com/game-loop.html
8
-
9
- export default {
10
- flash: FlashLoop,
11
- fixed: FixedLoop,
12
- elapsed: ElapsedLoop,
13
- lag: LagLoop,
14
- animationFrame: AnimationFrameLoop,
15
- }
@@ -1,27 +0,0 @@
1
- import { expect, test } from "vitest"
2
-
3
- import modernMove from "./modern.js"
4
-
5
- test("it should move following its velocity", () => {
6
- const instance = { maxSpeed: 1, velocity: [1, 0, 0], position: [0, 0, 0] }
7
- const options = { dt: 1 }
8
- const expectedResult = {
9
- velocity: [1, 0, 0],
10
- position: [1, 0, 0],
11
- orientation: 0,
12
- }
13
-
14
- expect(modernMove(instance, options)).toStrictEqual(expectedResult)
15
- })
16
-
17
- test("it should limit the velocity to the max speed", () => {
18
- const instance = { maxSpeed: 1, velocity: [10, 0, 0], position: [0, 0, 0] }
19
- const options = { dt: 1 }
20
- const expectedResult = {
21
- velocity: [1, 0, 0],
22
- position: [1, 0, 0],
23
- orientation: 0,
24
- }
25
-
26
- expect(modernMove(instance, options)).toStrictEqual(expectedResult)
27
- })
@@ -1,174 +0,0 @@
1
- import { enableGame } from "@inglorious/game/decorators/game.js"
2
- import { map } from "@inglorious/utils/data-structures/object.js"
3
- import { extend } from "@inglorious/utils/data-structures/objects.js"
4
- import { pipe } from "@inglorious/utils/functions/functions.js"
5
- import { produce } from "immer"
6
-
7
- const DEFAULT_TYPES = {
8
- game: [enableGame()],
9
- }
10
-
11
- const DEFAULT_INSTANCES = {
12
- // eslint-disable-next-line no-magic-numbers
13
- game: { type: "game", bounds: [0, 0, 800, 600] }, // Default game instance with bounds.
14
- }
15
-
16
- const DEFAULT_LAYER = 0
17
-
18
- /**
19
- * Creates a store to manage state and events.
20
- * @param {Object} options - Configuration options for the store.
21
- * @param {Object} options.instances - Initial instances to include in the store.
22
- * @param {Object} options.originalConfig - Additional configuration for the store.
23
- * @returns {Object} The store with methods to interact with state and events.
24
- */
25
- export function createStore({
26
- types: originalTypes,
27
- instances: originalInstances,
28
- }) {
29
- const listeners = new Set()
30
- let incomingEvents = []
31
-
32
- let types = extend(DEFAULT_TYPES, originalTypes)
33
- types = augmentTypes(types)
34
-
35
- let instances = extend(DEFAULT_INSTANCES, originalInstances)
36
- instances = augmentInstances(instances)
37
-
38
- let state = { events: [], instances }
39
-
40
- return {
41
- subscribe,
42
- update,
43
- notify,
44
- dispatch: notify, // needed for react-redux
45
- getTypes,
46
- getState,
47
- }
48
-
49
- /**
50
- * Subscribes a listener to state updates.
51
- * @param {Function} listener - The listener function to call on updates.
52
- * @returns {Function} A function to unsubscribe the listener.
53
- */
54
- function subscribe(listener) {
55
- listeners.add(listener)
56
-
57
- return function unsubscribe() {
58
- listeners.delete(listener)
59
- }
60
- }
61
-
62
- /**
63
- * Updates the state based on elapsed time and processes events.
64
- * @param {number} dt - The delta time since the last update.
65
- */
66
- function update(dt) {
67
- state = { ...state }
68
-
69
- state.events.push(...incomingEvents, { id: "game:update" })
70
- incomingEvents = []
71
-
72
- while (state.events.length) {
73
- const event = state.events.shift()
74
-
75
- if (event.id === "instance:add") {
76
- add(event.payload.id, event.payload)
77
- }
78
-
79
- state.instances = map(state.instances, (_, instance, instances) => {
80
- const type = types[instance.type]
81
- const handle = type[event.id]
82
- return (
83
- handle?.(instance, event, {
84
- dt,
85
- type: originalTypes[instance.type],
86
- instances,
87
- notify,
88
- }) ?? instance
89
- )
90
- })
91
-
92
- if (event.id === "instance:remove") {
93
- remove(event.payload)
94
- }
95
- }
96
-
97
- listeners.forEach((onUpdate) => onUpdate())
98
- }
99
-
100
- /**
101
- * Adds a new instance to the state.
102
- * @param {string} id - The ID of the instance to add.
103
- * @param {Object} instance - The instance object to add.
104
- */
105
- function add(id, instance) {
106
- state = { ...state }
107
- state.instances[id] = augmentInstance(id, instance)
108
- }
109
-
110
- /**
111
- * Removes an instance from the state.
112
- * @param {string} id - The ID of the instance to remove.
113
- */
114
- function remove(id) {
115
- state = { ...state }
116
- delete state.instances[id]
117
- }
118
-
119
- /**
120
- * Notifies the store of a new event.
121
- * @param {Object} event - The event object to notify.
122
- */
123
- function notify(event) {
124
- incomingEvents.push(event)
125
- }
126
-
127
- /**
128
- * Retrieves the types configuration.
129
- * @returns {Object} The types configuration.
130
- */
131
- function getTypes() {
132
- return types
133
- }
134
-
135
- /**
136
- * Retrieves the current state.
137
- * @returns {Object} The current state.
138
- */
139
- function getState() {
140
- return state
141
- }
142
- }
143
-
144
- function augmentTypes(types) {
145
- return pipe(applyDecorators, enableMutability)(types)
146
- }
147
-
148
- function applyDecorators(types) {
149
- return map(types, (_, type) => {
150
- if (!Array.isArray(type)) {
151
- return type
152
- }
153
-
154
- const decorators = type.map((fn) =>
155
- typeof fn !== "function" ? (type) => extend(type, fn) : fn,
156
- )
157
- return pipe(...decorators)({})
158
- })
159
- }
160
-
161
- function enableMutability(types) {
162
- return map(types, (_, { draw, ...events }) => ({
163
- draw,
164
- ...map(events, (_, event) => produce(event)),
165
- }))
166
- }
167
-
168
- function augmentInstances(instances) {
169
- return map(instances, augmentInstance)
170
- }
171
-
172
- function augmentInstance(id, instance) {
173
- return { ...instance, layer: instance.layer ?? DEFAULT_LAYER, id }
174
- }