@inglorious/engine 0.1.0 → 0.2.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 (292) hide show
  1. package/README.md +39 -36
  2. package/package.json +20 -33
  3. package/src/engine/ai/movement/dynamic/align.js +63 -63
  4. package/src/engine/ai/movement/dynamic/arrive.js +42 -43
  5. package/src/engine/ai/movement/dynamic/evade.js +38 -38
  6. package/src/engine/ai/movement/dynamic/face.js +19 -20
  7. package/src/engine/ai/movement/dynamic/flee.js +45 -45
  8. package/src/engine/ai/movement/dynamic/look-where-youre-going.js +16 -17
  9. package/src/engine/ai/movement/dynamic/match-velocity.js +51 -50
  10. package/src/engine/ai/movement/dynamic/pursue.js +38 -38
  11. package/src/engine/ai/movement/dynamic/seek.js +44 -44
  12. package/src/engine/ai/movement/dynamic/wander.js +31 -32
  13. package/src/engine/ai/movement/kinematic/align.js +37 -37
  14. package/src/engine/ai/movement/kinematic/arrive.js +42 -42
  15. package/src/engine/ai/movement/kinematic/face.js +19 -20
  16. package/src/engine/ai/movement/kinematic/flee.js +26 -26
  17. package/src/engine/ai/movement/kinematic/seek.js +26 -26
  18. package/src/engine/ai/movement/kinematic/seek.test.js +42 -42
  19. package/src/engine/ai/movement/kinematic/wander-as-seek.js +31 -31
  20. package/src/engine/ai/movement/kinematic/wander.js +27 -27
  21. package/src/engine/animation/sprite.js +101 -0
  22. package/src/engine/animation/ticker.js +38 -0
  23. package/src/engine/behaviors/camera.js +68 -0
  24. package/src/engine/behaviors/controls/dynamic/modern.js +76 -0
  25. package/src/engine/behaviors/controls/dynamic/shooter.js +84 -0
  26. package/src/engine/behaviors/controls/dynamic/tank.js +69 -0
  27. package/src/engine/behaviors/controls/event-handlers.js +17 -0
  28. package/src/engine/behaviors/controls/kinematic/modern.js +76 -0
  29. package/src/engine/behaviors/controls/kinematic/shooter.js +82 -0
  30. package/src/engine/behaviors/controls/kinematic/tank.js +67 -0
  31. package/src/engine/behaviors/debug/collision.js +35 -0
  32. package/src/engine/behaviors/fps.js +29 -0
  33. package/src/engine/behaviors/fsm.js +33 -0
  34. package/src/{game/decorators → engine/behaviors}/fsm.test.js +49 -56
  35. package/src/engine/behaviors/game.js +15 -0
  36. package/src/engine/behaviors/input/controls.js +37 -0
  37. package/src/engine/behaviors/input/gamepad.js +114 -0
  38. package/src/engine/behaviors/input/input.js +48 -0
  39. package/src/engine/behaviors/input/keyboard.js +64 -0
  40. package/src/engine/behaviors/input/mouse.js +91 -0
  41. package/src/engine/behaviors/physics/bouncy.js +25 -0
  42. package/src/engine/behaviors/physics/clamped.js +36 -0
  43. package/src/{game/decorators/collisions.js → engine/behaviors/physics/collidable.js} +20 -24
  44. package/src/engine/behaviors/physics/jumpable.js +145 -0
  45. package/src/engine/behaviors/ui/button.js +17 -0
  46. package/src/engine/collision/detection.js +110 -115
  47. package/src/engine/core/api.js +34 -0
  48. package/src/engine/core/dev-tools.js +135 -0
  49. package/src/engine/core/engine.js +119 -0
  50. package/src/engine/core/loop.js +15 -0
  51. package/src/engine/{loop → core/loops}/animation-frame.js +25 -26
  52. package/src/engine/{loop → core/loops}/elapsed.js +22 -23
  53. package/src/engine/{loop → core/loops}/fixed.js +27 -28
  54. package/src/engine/{loop → core/loops}/flash.js +13 -14
  55. package/src/engine/{loop → core/loops}/lag.js +26 -27
  56. package/src/engine/core/select.js +26 -0
  57. package/src/engine/core/store.js +178 -0
  58. package/src/engine/core/store.test.js +110 -0
  59. package/src/engine/movement/dynamic/modern.js +21 -24
  60. package/src/engine/movement/dynamic/tank.js +43 -43
  61. package/src/engine/movement/kinematic/modern.js +16 -16
  62. package/src/engine/movement/kinematic/modern.test.js +27 -27
  63. package/src/engine/movement/kinematic/tank.js +27 -27
  64. package/src/engine/physics/bounds.js +138 -0
  65. package/src/engine/physics/position.js +43 -0
  66. package/src/engine/physics/position.test.js +80 -0
  67. package/src/engine/systems/sprite-animation.js +27 -0
  68. package/src/main.js +10 -5
  69. package/src/renderers/canvas/absolute-position.js +18 -0
  70. package/src/renderers/canvas/camera.js +13 -0
  71. package/src/renderers/canvas/canvas-renderer.js +68 -0
  72. package/src/{ui → renderers}/canvas/character.js +38 -35
  73. package/src/{ui → renderers}/canvas/form/button.js +25 -25
  74. package/src/{ui → renderers}/canvas/fps.js +18 -18
  75. package/src/renderers/canvas/image/hitmask.js +51 -0
  76. package/src/{ui → renderers}/canvas/image/image.js +34 -37
  77. package/src/{ui → renderers}/canvas/image/sprite.js +49 -49
  78. package/src/{ui → renderers}/canvas/image/tilemap.js +66 -64
  79. package/src/{ui → renderers}/canvas/mouse.js +37 -37
  80. package/src/renderers/canvas/rendering-system.js +79 -0
  81. package/src/{ui → renderers}/canvas/shapes/circle.js +29 -31
  82. package/src/{ui → renderers}/canvas/shapes/rectangle.js +27 -31
  83. package/src/renderers/react/game/character/index.jsx +20 -0
  84. package/src/{ui → renderers}/react/game/cursor/index.jsx +20 -20
  85. package/src/{ui → renderers}/react/game/form/fields/field/index.jsx +56 -56
  86. package/src/{ui → renderers}/react/game/form/fields/index.jsx +12 -12
  87. package/src/{ui → renderers}/react/game/form/index.jsx +22 -22
  88. package/src/{ui → renderers}/react/game/fps/index.jsx +16 -16
  89. package/src/{ui → renderers}/react/game/game.jsx +72 -71
  90. package/src/{ui → renderers}/react/game/index.jsx +29 -29
  91. package/src/{ui → renderers}/react/game/platform/index.jsx +30 -30
  92. package/src/{ui → renderers}/react/game/scene/index.jsx +27 -25
  93. package/src/{ui → renderers}/react/game/sprite/index.jsx +60 -58
  94. package/src/{ui → renderers}/react/game/stats/index.jsx +22 -22
  95. package/src/{ui → renderers}/react/hocs/with-absolute-position/index.jsx +20 -20
  96. package/src/{ui → renderers}/react/index.jsx +9 -9
  97. package/src/utils/algorithms/decision-tree.js +24 -24
  98. package/src/utils/algorithms/decision-tree.test.js +153 -102
  99. package/src/utils/algorithms/path-finding.js +155 -155
  100. package/src/utils/algorithms/path-finding.test.js +151 -151
  101. package/src/utils/data-structures/array.js +83 -83
  102. package/src/utils/data-structures/array.test.js +173 -173
  103. package/src/utils/data-structures/board.js +159 -159
  104. package/src/utils/data-structures/board.test.js +242 -242
  105. package/src/utils/data-structures/boolean.js +9 -9
  106. package/src/utils/data-structures/heap.js +164 -164
  107. package/src/utils/data-structures/heap.test.js +103 -103
  108. package/src/utils/data-structures/object.js +138 -102
  109. package/src/utils/data-structures/object.test.js +218 -121
  110. package/src/utils/data-structures/objects.js +66 -48
  111. package/src/utils/data-structures/objects.test.js +99 -99
  112. package/src/utils/data-structures/tree.js +36 -36
  113. package/src/utils/data-structures/tree.test.js +33 -33
  114. package/src/utils/functions/functions.js +19 -19
  115. package/src/utils/functions/functions.test.js +23 -23
  116. package/src/utils/math/geometry/circle.js +70 -117
  117. package/src/utils/math/geometry/circle.test.js +97 -97
  118. package/src/utils/math/geometry/hitmask.js +70 -39
  119. package/src/utils/math/geometry/hitmask.test.js +155 -84
  120. package/src/utils/math/geometry/line.js +35 -35
  121. package/src/utils/math/geometry/line.test.js +49 -49
  122. package/src/utils/math/geometry/point.js +78 -71
  123. package/src/utils/math/geometry/point.test.js +81 -81
  124. package/src/utils/math/geometry/rectangle.js +76 -45
  125. package/src/utils/math/geometry/rectangle.test.js +42 -42
  126. package/src/utils/math/geometry/segment.js +80 -80
  127. package/src/utils/math/geometry/segment.test.js +183 -183
  128. package/src/utils/math/geometry/triangle.js +15 -15
  129. package/src/utils/math/geometry/triangle.test.js +11 -11
  130. package/src/utils/math/linear-algebra/2d.js +28 -28
  131. package/src/utils/math/linear-algebra/2d.test.js +17 -17
  132. package/src/utils/math/linear-algebra/quaternion.js +22 -22
  133. package/src/utils/math/linear-algebra/quaternion.test.js +25 -25
  134. package/src/utils/math/linear-algebra/quaternions.js +20 -20
  135. package/src/utils/math/linear-algebra/quaternions.test.js +29 -29
  136. package/src/utils/math/linear-algebra/vector.js +327 -302
  137. package/src/utils/math/linear-algebra/vector.test.js +265 -257
  138. package/src/utils/math/linear-algebra/vectors.js +122 -122
  139. package/src/utils/math/linear-algebra/vectors.test.js +65 -65
  140. package/src/utils/math/linear-interpolation.js +9 -0
  141. package/src/utils/math/numbers.js +90 -90
  142. package/src/utils/math/numbers.test.js +137 -137
  143. package/src/utils/math/rng.js +44 -44
  144. package/src/utils/math/rng.test.js +39 -39
  145. package/src/utils/math/statistics.js +43 -43
  146. package/src/utils/math/statistics.test.js +47 -47
  147. package/src/utils/math/trigonometry.js +89 -89
  148. package/src/utils/math/trigonometry.test.js +52 -52
  149. package/src/utils/physics/acceleration.js +61 -63
  150. package/src/utils/physics/friction.js +28 -30
  151. package/src/utils/physics/friction.test.js +42 -44
  152. package/src/utils/physics/gravity.js +69 -71
  153. package/src/utils/physics/gravity.test.js +77 -80
  154. package/src/utils/physics/jump.js +31 -41
  155. package/src/utils/physics/velocity.js +36 -38
  156. package/src/docs/ai/movement/dynamic/align.js +0 -131
  157. package/src/docs/ai/movement/dynamic/arrive.js +0 -88
  158. package/src/docs/ai/movement/dynamic/dynamic.mdx +0 -99
  159. package/src/docs/ai/movement/dynamic/dynamic.stories.js +0 -58
  160. package/src/docs/ai/movement/dynamic/evade.js +0 -72
  161. package/src/docs/ai/movement/dynamic/face.js +0 -90
  162. package/src/docs/ai/movement/dynamic/flee.js +0 -38
  163. package/src/docs/ai/movement/dynamic/look-where-youre-going.js +0 -114
  164. package/src/docs/ai/movement/dynamic/match-velocity.js +0 -92
  165. package/src/docs/ai/movement/dynamic/pursue.js +0 -72
  166. package/src/docs/ai/movement/dynamic/seek.js +0 -37
  167. package/src/docs/ai/movement/dynamic/wander.js +0 -71
  168. package/src/docs/ai/movement/kinematic/align.js +0 -122
  169. package/src/docs/ai/movement/kinematic/arrive.js +0 -78
  170. package/src/docs/ai/movement/kinematic/face.js +0 -82
  171. package/src/docs/ai/movement/kinematic/flee.js +0 -36
  172. package/src/docs/ai/movement/kinematic/kinematic.mdx +0 -67
  173. package/src/docs/ai/movement/kinematic/kinematic.stories.js +0 -42
  174. package/src/docs/ai/movement/kinematic/seek.js +0 -34
  175. package/src/docs/ai/movement/kinematic/wander-as-seek.js +0 -62
  176. package/src/docs/ai/movement/kinematic/wander.js +0 -28
  177. package/src/docs/bounds.js +0 -7
  178. package/src/docs/code-reuse.js +0 -35
  179. package/src/docs/collision/circles.js +0 -58
  180. package/src/docs/collision/collision.mdx +0 -27
  181. package/src/docs/collision/collision.stories.js +0 -22
  182. package/src/docs/collision/platform.js +0 -76
  183. package/src/docs/collision/tilemap.js +0 -181
  184. package/src/docs/empty.js +0 -1
  185. package/src/docs/engine.mdx +0 -81
  186. package/src/docs/engine.stories.js +0 -37
  187. package/src/docs/event-handlers.js +0 -68
  188. package/src/docs/framerate.js +0 -37
  189. package/src/docs/game.jsx +0 -15
  190. package/src/docs/image/image.js +0 -19
  191. package/src/docs/image/image.stories.js +0 -22
  192. package/src/docs/image/sprite.js +0 -39
  193. package/src/docs/image/tilemap.js +0 -84
  194. package/src/docs/input/controls.js +0 -67
  195. package/src/docs/input/gamepad.js +0 -67
  196. package/src/docs/input/input.mdx +0 -55
  197. package/src/docs/input/input.stories.js +0 -27
  198. package/src/docs/input/keyboard.js +0 -58
  199. package/src/docs/input/mouse.js +0 -32
  200. package/src/docs/instances.js +0 -49
  201. package/src/docs/player/dynamic/double-jump.js +0 -90
  202. package/src/docs/player/dynamic/dynamic.stories.js +0 -32
  203. package/src/docs/player/dynamic/jump.js +0 -83
  204. package/src/docs/player/dynamic/modern-controls.js +0 -57
  205. package/src/docs/player/dynamic/shooter-controls.js +0 -51
  206. package/src/docs/player/dynamic/tank-controls.js +0 -44
  207. package/src/docs/player/kinematic/double-jump.js +0 -90
  208. package/src/docs/player/kinematic/jump.js +0 -82
  209. package/src/docs/player/kinematic/kinematic.stories.js +0 -32
  210. package/src/docs/player/kinematic/modern-controls.js +0 -56
  211. package/src/docs/player/kinematic/shooter-controls.js +0 -48
  212. package/src/docs/player/kinematic/tank-controls.js +0 -42
  213. package/src/docs/quick-start/first-game.js +0 -49
  214. package/src/docs/quick-start/hello-world.js +0 -1
  215. package/src/docs/quick-start.mdx +0 -127
  216. package/src/docs/quick-start.stories.js +0 -17
  217. package/src/docs/recipes/add-and-remove.js +0 -71
  218. package/src/docs/recipes/add-instance.js +0 -42
  219. package/src/docs/recipes/decision-tree.js +0 -169
  220. package/src/docs/recipes/random-instances.js +0 -25
  221. package/src/docs/recipes/recipes.mdx +0 -81
  222. package/src/docs/recipes/recipes.stories.js +0 -37
  223. package/src/docs/recipes/remove-instance.js +0 -52
  224. package/src/docs/recipes/states.js +0 -64
  225. package/src/docs/ui/button.js +0 -28
  226. package/src/docs/ui/form.stories.js +0 -55
  227. package/src/docs/ui-chooser.jsx +0 -6
  228. package/src/docs/utils/data-structures/object.mdx +0 -47
  229. package/src/docs/utils/data-structures/objects.mdx +0 -30
  230. package/src/docs/utils/functions/functions.mdx +0 -34
  231. package/src/docs/utils/math/geometry/circle.mdx +0 -55
  232. package/src/docs/utils/math/geometry/point.mdx +0 -38
  233. package/src/docs/utils/math/geometry/rectangle.mdx +0 -24
  234. package/src/docs/utils/math/geometry/segment.mdx +0 -55
  235. package/src/docs/utils/math/geometry/triangle.mdx +0 -22
  236. package/src/docs/utils/math/linear-algebra/2d.mdx +0 -22
  237. package/src/docs/utils/math/linear-algebra/quaternion.mdx +0 -21
  238. package/src/docs/utils/math/linear-algebra/quaternions.mdx +0 -22
  239. package/src/docs/utils/math/linear-algebra/vector.mdx +0 -177
  240. package/src/docs/utils/math/linear-algebra/vectors.mdx +0 -58
  241. package/src/docs/utils/math/numbers.mdx +0 -76
  242. package/src/docs/utils/math/random.mdx +0 -35
  243. package/src/docs/utils/math/statistics.mdx +0 -38
  244. package/src/docs/utils/math/trigonometry.mdx +0 -85
  245. package/src/docs/utils/physics/friction.mdx +0 -20
  246. package/src/docs/utils/physics/gravity.mdx +0 -28
  247. package/src/engine/loop.js +0 -15
  248. package/src/engine/store.js +0 -174
  249. package/src/engine/store.test.js +0 -256
  250. package/src/engine.js +0 -74
  251. package/src/game/animation.js +0 -26
  252. package/src/game/bounds.js +0 -66
  253. package/src/game/decorators/character.js +0 -5
  254. package/src/game/decorators/clamp-to-bounds.js +0 -15
  255. package/src/game/decorators/controls/dynamic/modern.js +0 -48
  256. package/src/game/decorators/controls/dynamic/shooter.js +0 -47
  257. package/src/game/decorators/controls/dynamic/tank.js +0 -55
  258. package/src/game/decorators/controls/kinematic/modern.js +0 -49
  259. package/src/game/decorators/controls/kinematic/shooter.js +0 -45
  260. package/src/game/decorators/controls/kinematic/tank.js +0 -52
  261. package/src/game/decorators/debug/collisions.js +0 -32
  262. package/src/game/decorators/double-jump.js +0 -70
  263. package/src/game/decorators/fps.js +0 -30
  264. package/src/game/decorators/fsm.js +0 -27
  265. package/src/game/decorators/game.js +0 -11
  266. package/src/game/decorators/image/image.js +0 -5
  267. package/src/game/decorators/image/sprite.js +0 -5
  268. package/src/game/decorators/image/tilemap.js +0 -5
  269. package/src/game/decorators/input/controls.js +0 -27
  270. package/src/game/decorators/input/gamepad.js +0 -74
  271. package/src/game/decorators/input/input.js +0 -41
  272. package/src/game/decorators/input/keyboard.js +0 -49
  273. package/src/game/decorators/input/mouse.js +0 -65
  274. package/src/game/decorators/jump.js +0 -72
  275. package/src/game/decorators/platform.js +0 -5
  276. package/src/game/decorators/ui/button.js +0 -21
  277. package/src/game/sprite.js +0 -119
  278. package/src/ui/canvas/absolute-position.js +0 -17
  279. package/src/ui/canvas/image/hitmask.js +0 -37
  280. package/src/ui/canvas.js +0 -81
  281. package/src/ui/react/game/character/index.jsx +0 -30
  282. package/src/utils/math/geometry/platform.js +0 -42
  283. package/src/utils/math/geometry/platform.test.js +0 -133
  284. /package/src/{ui → renderers}/react/game/character/character.module.scss +0 -0
  285. /package/src/{ui → renderers}/react/game/cursor/cursor.module.scss +0 -0
  286. /package/src/{ui → renderers}/react/game/form/fields/field/field.module.scss +0 -0
  287. /package/src/{ui → renderers}/react/game/form/fields/fields.module.scss +0 -0
  288. /package/src/{ui → renderers}/react/game/form/form.module.scss +0 -0
  289. /package/src/{ui → renderers}/react/game/platform/platform.module.scss +0 -0
  290. /package/src/{ui → renderers}/react/game/scene/scene.module.scss +0 -0
  291. /package/src/{ui → renderers}/react/game/sprite/sprite.module.css +0 -0
  292. /package/src/{ui → renderers}/react/hocs/with-absolute-position/with-absolute-position.module.scss +0 -0
@@ -0,0 +1,64 @@
1
+ const DEFAULT_PARAMS = {
2
+ name: "keyboard0",
3
+ }
4
+
5
+ export function keyboard() {
6
+ let handleKeyDown, handleKeyUp
7
+ let currentDocument = null
8
+
9
+ return {
10
+ start(_, api) {
11
+ currentDocument = document.body.ownerDocument || document
12
+
13
+ handleKeyDown = createKeyboardHandler("keyboardKeyDown", api)
14
+ handleKeyUp = createKeyboardHandler("keyboardKeyUp", api)
15
+
16
+ currentDocument.addEventListener("keydown", handleKeyDown)
17
+ currentDocument.addEventListener("keyup", handleKeyUp)
18
+ },
19
+
20
+ stop() {
21
+ currentDocument.removeEventListener("keydown", handleKeyDown)
22
+ currentDocument.removeEventListener("keyup", handleKeyUp)
23
+ },
24
+
25
+ keyboardKeyDown(entity, keyCode, api) {
26
+ const action = entity.mapping[keyCode]
27
+ if (!action) {
28
+ return
29
+ }
30
+
31
+ if (!entity[action]) {
32
+ entity[action] = true
33
+ api.notify("inputPress", { controlId: entity.id, action })
34
+ }
35
+ },
36
+
37
+ keyboardKeyUp(entity, keyCode, api) {
38
+ const action = entity.mapping[keyCode]
39
+ if (!action) {
40
+ return
41
+ }
42
+
43
+ if (entity[action]) {
44
+ entity[action] = false
45
+ api.notify("inputRelease", { controlId: entity.id, action })
46
+ }
47
+ },
48
+ }
49
+ }
50
+
51
+ export function createKeyboard(
52
+ name = DEFAULT_PARAMS.name,
53
+ targetInput,
54
+ mapping = {},
55
+ ) {
56
+ return { id: name, type: "keyboard", targetInput, mapping }
57
+ }
58
+
59
+ function createKeyboardHandler(id, api) {
60
+ return (event) => {
61
+ event.stopPropagation()
62
+ api.notify(id, event.code)
63
+ }
64
+ }
@@ -0,0 +1,91 @@
1
+ import { findCollision } from "@inglorious/engine/collision/detection.js"
2
+ import { clampToBounds } from "@inglorious/engine/physics/bounds.js"
3
+ import { zero } from "@inglorious/utils/math/linear-algebra/vector.js"
4
+
5
+ const DEFAULT_PARAMS = {
6
+ name: "mouse",
7
+ }
8
+
9
+ const NO_Y = 0
10
+
11
+ export function mouse() {
12
+ return {
13
+ start(entity) {
14
+ entity.collisions ??= {}
15
+ entity.collisions.bounds ??= { shape: "point" }
16
+ },
17
+
18
+ mouseMove(entity, position, api) {
19
+ const game = api.getEntity("game")
20
+
21
+ entity.position = position
22
+
23
+ clampToBounds(entity, game.bounds)
24
+ },
25
+
26
+ mouseClick(entity, position, api) {
27
+ const entities = api.getEntities()
28
+ const clickedEntity = findCollision(entity, entities)
29
+ if (clickedEntity) {
30
+ api.notify("entityClick", clickedEntity.id)
31
+ } else {
32
+ api.notify("sceneClick", position)
33
+ }
34
+ },
35
+ }
36
+ }
37
+
38
+ export function track(parent, options) {
39
+ const handleMouseMove = createHandler("mouseMove", parent, options)
40
+ const handleClick = createHandler("mouseClick", parent, options)
41
+ const handleWheel = createHandler("mouseWheel", parent, options)
42
+
43
+ return {
44
+ onMouseMove: handleMouseMove,
45
+ onClick: handleClick,
46
+ onWheel: handleWheel,
47
+ }
48
+ }
49
+
50
+ export function createMouse(name = DEFAULT_PARAMS.name, overrides = {}) {
51
+ return {
52
+ id: name,
53
+ type: "mouse",
54
+ layer: 999, // A high layer value to ensure it's always rendered on top
55
+ position: zero(),
56
+ ...overrides,
57
+ }
58
+ }
59
+
60
+ function createHandler(type, parent, api) {
61
+ return (event) => {
62
+ event.stopPropagation()
63
+
64
+ if (parent == null) {
65
+ return
66
+ }
67
+
68
+ // For wheel events, the payload is different from other mouse events.
69
+ if (type === "mouseWheel") {
70
+ api.notify(type, { deltaY: event.deltaY })
71
+ return
72
+ }
73
+
74
+ // For move and click events, the payload is the calculated position.
75
+ const payload = calculatePosition({
76
+ clientX: event.clientX,
77
+ clientY: event.clientY,
78
+ parent,
79
+ })
80
+ api.notify(type, payload)
81
+ }
82
+ }
83
+
84
+ function calculatePosition({ clientX, clientY, parent }) {
85
+ const bounds = parent.getBoundingClientRect()
86
+
87
+ const x = clientX - bounds.left
88
+ const z = bounds.bottom - clientY
89
+
90
+ return [x, NO_Y, z]
91
+ }
@@ -0,0 +1,25 @@
1
+ import { defaults, extend } from "@inglorious/utils/data-structures/objects.js"
2
+ import { jump } from "@inglorious/utils/physics/jump.js"
3
+
4
+ const DEFAULT_PARAMS = {
5
+ bounciness: 1,
6
+ }
7
+
8
+ export function bouncy(params) {
9
+ params = extend(DEFAULT_PARAMS, params)
10
+
11
+ return (type) =>
12
+ extend(type, {
13
+ start(entity) {
14
+ type.start?.(entity)
15
+ defaults(entity, params)
16
+ },
17
+
18
+ landed(entity, { entityId }) {
19
+ if (entity.id === entityId) {
20
+ entity.vy = jump(entity) * entity.bounciness
21
+ entity.groundObject = undefined
22
+ }
23
+ },
24
+ })
25
+ }
@@ -0,0 +1,36 @@
1
+ import { clampToBounds } from "@inglorious/engine/physics/bounds.js"
2
+ import { extend, merge } from "@inglorious/utils/data-structures/objects.js"
3
+
4
+ const DEFAULT_PARAMS = {
5
+ collisionGroup: "bounds",
6
+ depthAxis: "y",
7
+ }
8
+
9
+ export function clamped(params) {
10
+ params = extend(DEFAULT_PARAMS, params)
11
+
12
+ return (type) =>
13
+ extend(type, {
14
+ start(entity, api) {
15
+ type.start?.(entity, api)
16
+
17
+ entity.collisions ??= {}
18
+ entity.collisions[params.collisionGroup] ??= {}
19
+ entity.collisions[params.collisionGroup].shape ??= "rectangle"
20
+ },
21
+
22
+ update(entity, dt, api) {
23
+ type.update?.(entity, dt, api)
24
+
25
+ const game = api.getEntity("game")
26
+ merge(entity, {
27
+ position: clampToBounds(
28
+ entity,
29
+ game.bounds,
30
+ params.collisionGroup,
31
+ params.depthAxis,
32
+ ),
33
+ })
34
+ },
35
+ })
36
+ }
@@ -1,24 +1,20 @@
1
- import { extend } from "@inglorious/utils/data-structures/objects.js"
2
-
3
- const DEFAULT_PARAMS = {
4
- onState: "default",
5
- }
6
-
7
- export function enableCollisions(params) {
8
- params = extend(DEFAULT_PARAMS, params)
9
-
10
- return (type) =>
11
- extend(type, {
12
- states: {
13
- [params.onState]: {
14
- "game:update"(instance, event, options) {
15
- type.states?.[params.onState]["game:update"]?.(
16
- instance,
17
- event,
18
- options,
19
- )
20
- },
21
- },
22
- },
23
- })
24
- }
1
+ import { extend } from "@inglorious/utils/data-structures/objects.js"
2
+
3
+ const DEFAULT_PARAMS = {
4
+ onState: "default",
5
+ }
6
+
7
+ export function collidable(params) {
8
+ params = extend(DEFAULT_PARAMS, params)
9
+
10
+ return (type) =>
11
+ extend(type, {
12
+ states: {
13
+ [params.onState]: {
14
+ update(entity, dt, api) {
15
+ type.states?.[params.onState].update?.(entity, dt, api)
16
+ },
17
+ },
18
+ },
19
+ })
20
+ }
@@ -0,0 +1,145 @@
1
+ import { findCollision } from "@inglorious/engine/collision/detection.js"
2
+ import { defaults, extend } from "@inglorious/utils/data-structures/objects.js"
3
+ import {
4
+ angle,
5
+ magnitude,
6
+ zero,
7
+ } from "@inglorious/utils/math/linear-algebra/vector.js"
8
+ import { applyGravity } from "@inglorious/utils/physics/gravity.js"
9
+ import { jump } from "@inglorious/utils/physics/jump.js"
10
+
11
+ const DEFAULT_PARAMS = {
12
+ maxSpeed: 250,
13
+ maxJump: 100,
14
+ maxLeap: 100,
15
+ maxJumps: 1,
16
+ }
17
+
18
+ const DOUBLE = 2
19
+ const HALF = 2
20
+ const X = 0
21
+ const Y = 1
22
+ const NO_VELOCITY = 0
23
+ const NO_PENETRAION = 0
24
+
25
+ export function jumpable(params) {
26
+ params = extend(DEFAULT_PARAMS, params)
27
+
28
+ return (type) =>
29
+ extend(type, {
30
+ start(entity, api) {
31
+ type.start?.(entity, api)
32
+ defaults(entity, params)
33
+ entity.jumpsLeft ??= entity.maxJumps
34
+
35
+ entity.velocity ??= zero()
36
+ entity.vy ??= 0
37
+ },
38
+
39
+ jump(entity, { entityId }) {
40
+ if (entityId === entity.id && entity.jumpsLeft) {
41
+ entity.vy = jump(entity)
42
+ entity.groundObject = undefined
43
+ entity.jumpsLeft--
44
+ }
45
+ },
46
+
47
+ update(entity, dt, api) {
48
+ type.update?.(entity, dt, api)
49
+
50
+ const entities = api.getEntities()
51
+
52
+ entity.collisions ??= {}
53
+ entity.collisions.platform ??= {}
54
+ entity.collisions.platform.shape ??= "rectangle"
55
+
56
+ let width, height
57
+ if (entity.collisions.platform.shape === "circle") {
58
+ width = entity.collisions.platform.radius * DOUBLE
59
+ height = entity.collisions.platform.radius * DOUBLE
60
+ } else {
61
+ ;[width, height] = entity.size
62
+ }
63
+ const [prevX, prevY] = [...entity.position]
64
+
65
+ // 1. HORIZONTAL MOVEMENT & RESOLUTION
66
+ entity.position[X] += entity.velocity[X] * dt
67
+ const entityLeft = entity.position[X] - width / HALF
68
+ const entityRight = entity.position[X] + width / HALF
69
+
70
+ const collisionX = findCollision(entity, entities, "platform")
71
+ if (collisionX) {
72
+ const vx = entity.velocity[X]
73
+
74
+ // Check if moving right and crossing the platform's left edge
75
+ const prevRight = prevX + width / HALF
76
+ const platformLeft =
77
+ collisionX.position[X] - collisionX.size[X] / HALF
78
+ if (vx > NO_VELOCITY && prevRight <= platformLeft) {
79
+ const penetration = entityRight - platformLeft
80
+ if (penetration > NO_PENETRAION) entity.position[X] -= penetration
81
+ entity.velocity[X] = 0
82
+ }
83
+
84
+ // Check if moving left and crossing the platform's right edge
85
+ const prevLeft = prevX - width / HALF
86
+ const platformRight =
87
+ collisionX.position[X] + collisionX.size[X] / HALF
88
+ if (vx < NO_VELOCITY && prevLeft >= platformRight) {
89
+ const penetration = platformRight - entityLeft
90
+ if (penetration > NO_PENETRAION) entity.position[X] += penetration
91
+ entity.velocity[X] = 0
92
+ }
93
+ }
94
+
95
+ // 2. VERTICAL MOVEMENT & RESOLUTION
96
+ const wasOnGround = entity.groundObject
97
+ entity.groundObject = undefined
98
+
99
+ const { vy, position: nextGravityPosition } = applyGravity(entity, dt)
100
+ entity.vy = vy
101
+ entity.position[Y] = nextGravityPosition[Y]
102
+
103
+ const collisionY = findCollision(entity, entities, "platform")
104
+ if (collisionY) {
105
+ const prevBottom = prevY - height / HALF
106
+ const platformTop = collisionY.position[Y] + collisionY.size[Y] / HALF
107
+
108
+ // Landing on top of a platform (one-way platform logic)
109
+ if (entity.vy <= NO_VELOCITY && prevBottom >= platformTop) {
110
+ const entityBottom = entity.position[Y] - height / HALF
111
+ const penetration = platformTop - entityBottom
112
+ if (penetration > NO_PENETRAION) entity.position[Y] += penetration
113
+ entity.vy = 0
114
+ entity.groundObject = collisionY
115
+ entity.jumpsLeft = entity.maxJumps
116
+
117
+ // Only notify on the frame we actually land, not every frame we're on the ground.
118
+ if (!wasOnGround) {
119
+ api.notify("landed", {
120
+ entityId: entity.id,
121
+ targetId: collisionY.id,
122
+ })
123
+ }
124
+ }
125
+
126
+ // Hitting head on bottom of a platform
127
+ // else if (entity.vy > 0) {
128
+ // const prevTop = prevY + height / HALF
129
+ // const platformBottom =
130
+ // collisionY.position[Y] - collisionY.size[Y] / HALF
131
+ // if (prevTop <= platformBottom) {
132
+ // const entityTop = entity.position[Y] + height / HALF
133
+ // const penetration = entityTop - platformBottom
134
+ // if (penetration > 0) entity.position[Y] -= penetration
135
+ // entity.vy = 0
136
+ // }
137
+ // }
138
+ }
139
+
140
+ entity.orientation = magnitude(entity.velocity)
141
+ ? angle(entity.velocity)
142
+ : entity.orientation
143
+ },
144
+ })
145
+ }
@@ -0,0 +1,17 @@
1
+ export function button() {
2
+ return {
3
+ states: {
4
+ default: {
5
+ entityClick(entity) {
6
+ entity.state = "pressed"
7
+ },
8
+ },
9
+
10
+ pressed: {
11
+ entityRelease(entity) {
12
+ entity.state = "default"
13
+ },
14
+ },
15
+ },
16
+ }
17
+ }
@@ -1,115 +1,110 @@
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
+ 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 point from "@inglorious/utils/math/geometry/point.js"
6
+ import * as rectangle from "@inglorious/utils/math/geometry/rectangle.js"
7
+ import * as segment from "@inglorious/utils/math/geometry/segment.js"
8
+ import { zero } from "@inglorious/utils/math/linear-algebra/vector.js"
9
+ import { add } from "@inglorious/utils/math/linear-algebra/vectors.js"
10
+
11
+ const Z = 2 // Z-axis index.
12
+
13
+ const Shape = {
14
+ circle,
15
+ line,
16
+ point,
17
+ rectangle,
18
+ segment,
19
+ hitmask,
20
+ }
21
+
22
+ /**
23
+ * Finds the first collision between a point and a list of entities.
24
+ *
25
+ * @param {Point} entity - The point to check for collisions.
26
+ * @param {Options} options - Options for collision detection.
27
+ * @returns {Entity | undefined} The first entity that collides with the point, or undefined if none are found.
28
+ */
29
+ export function findCollision(entity, entities, collisionGroup = "hitbox") {
30
+ const otherEntities = filter(
31
+ entities,
32
+ (id, { collisions }) => id !== entity.id && collisions?.[collisionGroup],
33
+ )
34
+
35
+ return Object.values(otherEntities)
36
+ .toSorted((a, b) => a.position[Z] - b.position[Z])
37
+ .find((target) => collidesWith(entity, target, collisionGroup))
38
+ }
39
+
40
+ export function collidesWith(entity, target, collisionGroup = "hitbox") {
41
+ const entityShape = getCollisionShape(entity, collisionGroup)
42
+ const targetShape = getCollisionShape(target, collisionGroup)
43
+
44
+ if (!entityShape || !targetShape) {
45
+ return false
46
+ }
47
+
48
+ return shapeCollidesWith(entityShape, targetShape)
49
+ }
50
+
51
+ function shapeCollidesWith(entity, target) {
52
+ const shapeFns = Shape[entity.shape]
53
+
54
+ switch (target.shape) {
55
+ case "circle":
56
+ return shapeFns.intersectsCircle(entity, target)
57
+
58
+ case "line":
59
+ return shapeFns.intersectsLine(entity, target)
60
+
61
+ case "point":
62
+ return shapeFns.intersectsPoint(entity, target)
63
+
64
+ case "rectangle":
65
+ return shapeFns.intersectsRectangle(entity, target)
66
+
67
+ case "segment":
68
+ return shapeFns.intersectsSegment(entity, target)
69
+ }
70
+ }
71
+
72
+ export function findCollisions(entity, target, collisionGroup = "hitbox") {
73
+ const entityShape = getCollisionShape(entity, collisionGroup)
74
+ const targetShape = getCollisionShape(target, collisionGroup)
75
+
76
+ if (!entityShape || !targetShape) {
77
+ return false
78
+ }
79
+
80
+ const shapeFns = Shape[entityShape.shape]
81
+ if (!shapeFns || !shapeFns.findCollisions) {
82
+ return false
83
+ }
84
+
85
+ return shapeFns.findCollisions(entityShape, targetShape)
86
+ }
87
+
88
+ /**
89
+ * Correctly calculates the absolute position and size of an entity's
90
+ * collision shape, including any offsets.
91
+ */
92
+ function getCollisionShape(entity, collisionGroup = "hitbox") {
93
+ const collision = entity.collisions[collisionGroup]
94
+ if (!collision) {
95
+ return null
96
+ }
97
+
98
+ const position = add(
99
+ entity.position,
100
+ collision.offset ?? zero(),
101
+ entity.offset ?? zero(),
102
+ )
103
+
104
+ return {
105
+ ...collision,
106
+ position,
107
+ size: collision.size ?? entity.size,
108
+ radius: collision.radius ?? entity.radius,
109
+ }
110
+ }