@inglorious/engine 0.1.1 → 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 (201) hide show
  1. package/README.md +39 -36
  2. package/package.json +10 -22
  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/engine/loop.js +0 -15
  157. package/src/engine/store.js +0 -174
  158. package/src/engine/store.test.js +0 -256
  159. package/src/engine.js +0 -74
  160. package/src/game/animation.js +0 -26
  161. package/src/game/bounds.js +0 -66
  162. package/src/game/decorators/character.js +0 -5
  163. package/src/game/decorators/clamp-to-bounds.js +0 -15
  164. package/src/game/decorators/controls/dynamic/modern.js +0 -48
  165. package/src/game/decorators/controls/dynamic/shooter.js +0 -47
  166. package/src/game/decorators/controls/dynamic/tank.js +0 -55
  167. package/src/game/decorators/controls/kinematic/modern.js +0 -49
  168. package/src/game/decorators/controls/kinematic/shooter.js +0 -45
  169. package/src/game/decorators/controls/kinematic/tank.js +0 -52
  170. package/src/game/decorators/debug/collisions.js +0 -32
  171. package/src/game/decorators/double-jump.js +0 -70
  172. package/src/game/decorators/fps.js +0 -30
  173. package/src/game/decorators/fsm.js +0 -27
  174. package/src/game/decorators/game.js +0 -11
  175. package/src/game/decorators/image/image.js +0 -5
  176. package/src/game/decorators/image/sprite.js +0 -5
  177. package/src/game/decorators/image/tilemap.js +0 -5
  178. package/src/game/decorators/input/controls.js +0 -27
  179. package/src/game/decorators/input/gamepad.js +0 -74
  180. package/src/game/decorators/input/input.js +0 -41
  181. package/src/game/decorators/input/keyboard.js +0 -49
  182. package/src/game/decorators/input/mouse.js +0 -65
  183. package/src/game/decorators/jump.js +0 -72
  184. package/src/game/decorators/platform.js +0 -5
  185. package/src/game/decorators/ui/button.js +0 -21
  186. package/src/game/sprite.js +0 -119
  187. package/src/ui/canvas/absolute-position.js +0 -17
  188. package/src/ui/canvas/image/hitmask.js +0 -37
  189. package/src/ui/canvas.js +0 -81
  190. package/src/ui/react/game/character/index.jsx +0 -30
  191. package/src/utils/math/geometry/platform.js +0 -42
  192. package/src/utils/math/geometry/platform.test.js +0 -133
  193. /package/src/{ui → renderers}/react/game/character/character.module.scss +0 -0
  194. /package/src/{ui → renderers}/react/game/cursor/cursor.module.scss +0 -0
  195. /package/src/{ui → renderers}/react/game/form/fields/field/field.module.scss +0 -0
  196. /package/src/{ui → renderers}/react/game/form/fields/fields.module.scss +0 -0
  197. /package/src/{ui → renderers}/react/game/form/form.module.scss +0 -0
  198. /package/src/{ui → renderers}/react/game/platform/platform.module.scss +0 -0
  199. /package/src/{ui → renderers}/react/game/scene/scene.module.scss +0 -0
  200. /package/src/{ui → renderers}/react/game/sprite/sprite.module.css +0 -0
  201. /package/src/{ui → renderers}/react/hocs/with-absolute-position/with-absolute-position.module.scss +0 -0
@@ -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
+ }
package/src/main.js CHANGED
@@ -1,5 +1,10 @@
1
- import { start } from "@inglorious/ui/canvas.js"
2
- import game from "game"
3
-
4
- const canvas = document.getElementById("canvas")
5
- window.addEventListener("load", () => start(game, canvas))
1
+ import { Engine } from "@inglorious/engine/core/engine.js"
2
+ import { CanvasRenderer } from "@inglorious/renderers/canvas/canvas-renderer.js"
3
+ import game from "game"
4
+
5
+ const canvas = document.getElementById("canvas")
6
+ window.addEventListener("load", () => {
7
+ const renderer = new CanvasRenderer(canvas)
8
+ const engine = new Engine({ ...game, renderer })
9
+ engine.start()
10
+ })
@@ -0,0 +1,18 @@
1
+ import { snap, zero } from "@inglorious/utils/math/linear-algebra/vector.js"
2
+
3
+ export function absolutePosition(render) {
4
+ return (entity, ctx, { api }) => {
5
+ const { position = zero() } = entity
6
+ const [x, y, z] = snap(position)
7
+
8
+ const game = api.getEntity("game")
9
+ const [, , , screenHeight] = game.bounds
10
+
11
+ ctx.save()
12
+
13
+ ctx.translate(x, screenHeight - y - z)
14
+ render(entity, ctx, api)
15
+
16
+ ctx.restore()
17
+ }
18
+ }
@@ -0,0 +1,13 @@
1
+ import { renderRectangle } from "./shapes/rectangle.js"
2
+
3
+ export function renderCamera(entity, ctx, api) {
4
+ const { devMode } = api.getEntity("game")
5
+
6
+ if (devMode) {
7
+ // In dev mode, we want to see the camera's viewport.
8
+ // We can reuse the rectangle renderer.
9
+ renderRectangle(entity, ctx)
10
+ }
11
+ // In non-dev mode, the camera itself is not rendered;
12
+ // its properties are used by the main CanvasRenderer to transform the scene.
13
+ }
@@ -0,0 +1,68 @@
1
+ import { track } from "@inglorious/engine/behaviors/input/mouse.js"
2
+
3
+ import { createRenderingSystem } from "./rendering-system.js"
4
+
5
+ export class CanvasRenderer {
6
+ _onKeyPress = null
7
+ _onMouseMove = null
8
+ _onClick = null
9
+ _onWheel = null
10
+
11
+ constructor(canvas) {
12
+ this.canvas = canvas
13
+ this.ctx = canvas.getContext("2d")
14
+ }
15
+
16
+ getSystems() {
17
+ return [createRenderingSystem(this.ctx)]
18
+ }
19
+
20
+ init(engine) {
21
+ const game = engine._api.getEntity("game")
22
+ const [, , width, height] = game.bounds
23
+
24
+ this.canvas.style.width = `${width}px`
25
+ this.canvas.style.height = `${height}px`
26
+ const dpi = window.devicePixelRatio
27
+ this.canvas.width = width * dpi
28
+ this.canvas.height = height * dpi
29
+ this.ctx.scale(dpi, dpi)
30
+
31
+ if (game.pixelated) {
32
+ this.canvas.style.imageRendering = "pixelated"
33
+ this.ctx.textRendering = "geometricPrecision"
34
+ this.ctx.imageSmoothingEnabled = false
35
+ }
36
+
37
+ this._onKeyPress = (event) => {
38
+ if (event.key === "p") {
39
+ engine.isRunning ? engine.stop() : engine.start()
40
+ }
41
+ }
42
+ document.addEventListener("keypress", this._onKeyPress)
43
+
44
+ const { onMouseMove, onClick, onWheel } = track(this.canvas, engine._api)
45
+ this._onMouseMove = onMouseMove
46
+ this._onClick = onClick
47
+ this._onWheel = onWheel
48
+
49
+ this.canvas.addEventListener("mousemove", this._onMouseMove)
50
+ this.canvas.addEventListener("click", this._onClick)
51
+ this.canvas.addEventListener("wheel", this._onWheel)
52
+ }
53
+
54
+ destroy() {
55
+ if (this._onKeyPress) {
56
+ document.removeEventListener("keypress", this._onKeyPress)
57
+ }
58
+ if (this._onMouseMove) {
59
+ this.canvas.removeEventListener("mousemove", this._onMouseMove)
60
+ }
61
+ if (this._onClick) {
62
+ this.canvas.removeEventListener("click", this._onClick)
63
+ }
64
+ if (this._onWheel) {
65
+ this.canvas.removeEventListener("wheel", this._onWheel)
66
+ }
67
+ }
68
+ }
@@ -1,35 +1,38 @@
1
- /* eslint-disable no-magic-numbers */
2
-
3
- import circle from "./shapes/circle.js"
4
-
5
- export default function draw(ctx, instance) {
6
- const { size = 24, orientation = 0 } = instance
7
-
8
- const radius = size * 0.5
9
-
10
- ctx.save()
11
-
12
- ctx.rotate(-orientation)
13
- ctx.translate(radius - 1, 0)
14
-
15
- ctx.fillStyle = "black"
16
-
17
- ctx.beginPath()
18
- ctx.moveTo(0, 6)
19
- ctx.lineTo(0, -6)
20
- ctx.lineTo(6, 0)
21
- ctx.fill()
22
- ctx.closePath()
23
- ctx.restore()
24
-
25
- ctx.save()
26
-
27
- circle(ctx, {
28
- ...instance,
29
- radius,
30
- position: undefined,
31
- backgroundColor: "lightgrey",
32
- })
33
-
34
- ctx.restore()
35
- }
1
+ /* eslint-disable no-magic-numbers */
2
+
3
+ import { renderCircle } from "./shapes/circle.js"
4
+
5
+ export function renderCharacter(entity, ctx) {
6
+ const { size = 24, orientation = 0 } = entity
7
+
8
+ const radius = size * 0.5
9
+
10
+ ctx.save()
11
+
12
+ ctx.rotate(-orientation)
13
+ ctx.translate(radius - 1, 0)
14
+
15
+ ctx.fillStyle = "black"
16
+
17
+ ctx.beginPath()
18
+ ctx.moveTo(0, 6)
19
+ ctx.lineTo(0, -6)
20
+ ctx.lineTo(6, 0)
21
+ ctx.fill()
22
+ ctx.closePath()
23
+ ctx.restore()
24
+
25
+ ctx.save()
26
+
27
+ renderCircle(
28
+ {
29
+ ...entity,
30
+ radius,
31
+ position: undefined,
32
+ backgroundColor: "lightgrey",
33
+ },
34
+ ctx,
35
+ )
36
+
37
+ ctx.restore()
38
+ }
@@ -1,25 +1,25 @@
1
- /* eslint-disable no-magic-numbers */
2
-
3
- export default function draw(ctx, instance) {
4
- const { size, color = "black", thickness = 1 } = instance
5
- const [width = 100, height = 50] = size
6
-
7
- ctx.save()
8
-
9
- ctx.lineWidth = thickness
10
- ctx.strokeStyle = color
11
-
12
- if (instance.state === "pressed") {
13
- ctx.fillStyle = "white"
14
- } else {
15
- ctx.fillStyle = "black"
16
- }
17
-
18
- ctx.beginPath()
19
- ctx.fillRect(0, 0, width, height)
20
- ctx.strokeRect(0, 0, width, height)
21
- ctx.stroke()
22
- ctx.closePath()
23
-
24
- ctx.restore()
25
- }
1
+ /* eslint-disable no-magic-numbers */
2
+
3
+ export function renderButton(entity, ctx) {
4
+ const { size, color = "black", thickness = 1 } = entity
5
+ const [width = 100, height = 50] = size
6
+
7
+ ctx.save()
8
+
9
+ ctx.lineWidth = thickness
10
+ ctx.strokeStyle = color
11
+
12
+ if (entity.state === "pressed") {
13
+ ctx.fillStyle = "white"
14
+ } else {
15
+ ctx.fillStyle = "black"
16
+ }
17
+
18
+ ctx.beginPath()
19
+ ctx.fillRect(-width / 2, -height / 2, width, height)
20
+ ctx.strokeRect(-width / 2, -height / 2, width, height)
21
+ ctx.stroke()
22
+ ctx.closePath()
23
+
24
+ ctx.restore()
25
+ }
@@ -1,18 +1,18 @@
1
- const DEFAULT_PADDING = 10
2
- const ONE_SECOND = 1
3
-
4
- export default function draw(ctx, instance) {
5
- const { accuracy, size, value } = instance.dt
6
-
7
- ctx.save()
8
-
9
- ctx.font = `${size}px sans serif`
10
- ctx.fillStyle = "black"
11
- ctx.fillText(
12
- (ONE_SECOND / value).toFixed(accuracy),
13
- DEFAULT_PADDING,
14
- DEFAULT_PADDING + size,
15
- )
16
-
17
- ctx.restore()
18
- }
1
+ const DEFAULT_PADDING = 10
2
+ const ONE_SECOND = 1
3
+
4
+ export function renderFps(entity, ctx) {
5
+ const { accuracy, size, value } = entity.dt
6
+
7
+ ctx.save()
8
+
9
+ ctx.font = `${size}px sans serif`
10
+ ctx.fillStyle = "black"
11
+ ctx.fillText(
12
+ (ONE_SECOND / value).toFixed(accuracy),
13
+ DEFAULT_PADDING,
14
+ DEFAULT_PADDING + size,
15
+ )
16
+
17
+ ctx.restore()
18
+ }
@@ -0,0 +1,51 @@
1
+ import { renderRectangle } from "@inglorious/renderers/canvas/shapes/rectangle.js"
2
+ import { max } from "@inglorious/utils/data-structures/array.js"
3
+
4
+ const HALF = 2
5
+ const NO_Y = 0
6
+ const MAX_HUE = 255
7
+
8
+ export function renderHitmask(entity, ctx) {
9
+ const { tileSize, columns, heights } = entity
10
+
11
+ const [tileWidth, tileHeight] = tileSize
12
+ const halfTileWidth = tileWidth / HALF
13
+ const halfTileHeight = tileHeight / HALF
14
+
15
+ const dRows = Math.ceil(heights.length / columns)
16
+ const tilemapWidth = columns * tileWidth
17
+ const tilemapHeight = dRows * tileHeight
18
+
19
+ const offsetX = -tilemapWidth / HALF
20
+ const offsetY = tilemapHeight / HALF
21
+
22
+ const minH = 0
23
+ const maxH = max(heights)
24
+
25
+ ctx.save()
26
+
27
+ ctx.translate(offsetX, offsetY)
28
+
29
+ heights.forEach((h, index) => {
30
+ const dx = (index % columns) * tileWidth
31
+ const dy = Math.floor(index / columns) * tileHeight - tilemapHeight
32
+
33
+ const normalizedH = (h - minH) / (maxH - minH)
34
+ const hue = MAX_HUE - normalizedH * MAX_HUE
35
+
36
+ ctx.save()
37
+
38
+ const entity = {
39
+ offset: [dx + halfTileWidth, NO_Y, -dy - halfTileHeight],
40
+ size: [tileWidth, NO_Y, tileHeight],
41
+ color: "transparent",
42
+ backgroundColor: `hsla(${hue}, 100%, 50%, 0.2)`,
43
+ }
44
+
45
+ renderRectangle(entity, ctx)
46
+
47
+ ctx.restore()
48
+ })
49
+
50
+ ctx.restore()
51
+ }