@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.
- package/README.md +75 -72
- package/package.json +15 -37
- package/src/{engine/ai → ai}/movement/dynamic/align.js +9 -9
- package/src/{engine/ai → ai}/movement/dynamic/arrive.js +9 -10
- package/src/{engine/ai → ai}/movement/dynamic/evade.js +9 -9
- package/src/{engine/ai → ai}/movement/dynamic/face.js +5 -6
- package/src/{engine/ai/movement/dynamic/seek.js → ai/movement/dynamic/flee.js} +8 -7
- package/src/ai/movement/dynamic/look-where-youre-going.js +16 -0
- package/src/{engine/ai → ai}/movement/dynamic/match-velocity.js +9 -8
- package/src/{engine/ai → ai}/movement/dynamic/pursue.js +9 -9
- package/src/{engine/ai/movement/dynamic/flee.js → ai/movement/dynamic/seek.js} +7 -8
- package/src/{engine/ai → ai}/movement/dynamic/wander.js +9 -10
- package/src/{engine/ai → ai}/movement/kinematic/align.js +7 -7
- package/src/{engine/ai → ai}/movement/kinematic/arrive.js +8 -8
- package/src/{engine/ai → ai}/movement/kinematic/face.js +5 -6
- package/src/{engine/ai → ai}/movement/kinematic/flee.js +5 -5
- package/src/{engine/ai → ai}/movement/kinematic/seek.js +5 -5
- package/src/{engine/ai → ai}/movement/kinematic/seek.test.js +10 -10
- package/src/{engine/ai → ai}/movement/kinematic/wander-as-seek.js +9 -9
- package/src/{engine/ai → ai}/movement/kinematic/wander.js +5 -5
- package/src/animation/sprite.js +101 -0
- package/src/animation/ticker.js +38 -0
- package/src/behaviors/camera.js +68 -0
- package/src/behaviors/controls/dynamic/modern.js +76 -0
- package/src/behaviors/controls/dynamic/shooter.js +84 -0
- package/src/behaviors/controls/dynamic/tank.js +69 -0
- package/src/behaviors/controls/event-handlers.js +17 -0
- package/src/behaviors/controls/kinematic/modern.js +76 -0
- package/src/behaviors/controls/kinematic/shooter.js +82 -0
- package/src/behaviors/controls/kinematic/tank.js +67 -0
- package/src/behaviors/debug/collision.js +29 -0
- package/src/behaviors/fps.js +29 -0
- package/src/behaviors/fsm.js +33 -0
- package/src/{game/decorators → behaviors}/fsm.test.js +15 -22
- package/src/behaviors/game.js +15 -0
- package/src/behaviors/input/controls.js +37 -0
- package/src/behaviors/input/gamepad.js +114 -0
- package/src/behaviors/input/input.js +48 -0
- package/src/behaviors/input/keyboard.js +64 -0
- package/src/behaviors/input/mouse.js +91 -0
- package/src/behaviors/physics/bouncy.js +25 -0
- package/src/behaviors/physics/clamped.js +36 -0
- package/src/{game/decorators/collisions.js → behaviors/physics/collidable.js} +3 -7
- package/src/behaviors/physics/jumpable.js +145 -0
- package/src/behaviors/ui/button.js +17 -0
- package/src/collision/detection.js +110 -0
- package/src/core/api.js +34 -0
- package/src/core/dev-tools.js +135 -0
- package/src/core/engine.js +119 -0
- package/src/core/loop.js +15 -0
- package/src/{engine/loop → core/loops}/animation-frame.js +1 -2
- package/src/{engine/loop → core/loops}/elapsed.js +1 -2
- package/src/{engine/loop → core/loops}/fixed.js +1 -2
- package/src/{engine/loop → core/loops}/flash.js +1 -2
- package/src/{engine/loop → core/loops}/lag.js +1 -2
- package/src/core/select.js +26 -0
- package/src/core/store.js +178 -0
- package/src/core/store.test.js +110 -0
- package/src/main.js +7 -2
- package/src/{engine/movement → movement}/dynamic/modern.js +3 -6
- package/src/{engine/movement → movement}/dynamic/tank.js +9 -9
- package/src/{engine/movement → movement}/kinematic/modern.js +3 -3
- package/src/movement/kinematic/modern.test.js +27 -0
- package/src/{engine/movement → movement}/kinematic/tank.js +5 -5
- package/src/physics/bounds.js +138 -0
- package/src/physics/position.js +43 -0
- package/src/physics/position.test.js +80 -0
- package/src/systems/sprite-animation.js +27 -0
- package/src/engine/ai/movement/dynamic/look-where-youre-going.js +0 -17
- package/src/engine/collision/detection.js +0 -115
- package/src/engine/loop.js +0 -15
- package/src/engine/movement/kinematic/modern.test.js +0 -27
- package/src/engine/store.js +0 -174
- package/src/engine/store.test.js +0 -256
- package/src/engine.js +0 -74
- package/src/game/animation.js +0 -26
- package/src/game/bounds.js +0 -66
- package/src/game/decorators/character.js +0 -5
- package/src/game/decorators/clamp-to-bounds.js +0 -15
- package/src/game/decorators/controls/dynamic/modern.js +0 -48
- package/src/game/decorators/controls/dynamic/shooter.js +0 -47
- package/src/game/decorators/controls/dynamic/tank.js +0 -55
- package/src/game/decorators/controls/kinematic/modern.js +0 -49
- package/src/game/decorators/controls/kinematic/shooter.js +0 -45
- package/src/game/decorators/controls/kinematic/tank.js +0 -52
- package/src/game/decorators/debug/collisions.js +0 -32
- package/src/game/decorators/double-jump.js +0 -70
- package/src/game/decorators/fps.js +0 -30
- package/src/game/decorators/fsm.js +0 -27
- package/src/game/decorators/game.js +0 -11
- package/src/game/decorators/image/image.js +0 -5
- package/src/game/decorators/image/sprite.js +0 -5
- package/src/game/decorators/image/tilemap.js +0 -5
- package/src/game/decorators/input/controls.js +0 -27
- package/src/game/decorators/input/gamepad.js +0 -74
- package/src/game/decorators/input/input.js +0 -41
- package/src/game/decorators/input/keyboard.js +0 -49
- package/src/game/decorators/input/mouse.js +0 -65
- package/src/game/decorators/jump.js +0 -72
- package/src/game/decorators/platform.js +0 -5
- package/src/game/decorators/ui/button.js +0 -21
- package/src/game/sprite.js +0 -119
- package/src/ui/canvas/absolute-position.js +0 -17
- package/src/ui/canvas/character.js +0 -35
- package/src/ui/canvas/form/button.js +0 -25
- package/src/ui/canvas/fps.js +0 -18
- package/src/ui/canvas/image/hitmask.js +0 -37
- package/src/ui/canvas/image/image.js +0 -37
- package/src/ui/canvas/image/sprite.js +0 -49
- package/src/ui/canvas/image/tilemap.js +0 -64
- package/src/ui/canvas/mouse.js +0 -37
- package/src/ui/canvas/shapes/circle.js +0 -31
- package/src/ui/canvas/shapes/rectangle.js +0 -31
- package/src/ui/canvas.js +0 -81
- package/src/ui/react/game/character/character.module.scss +0 -17
- package/src/ui/react/game/character/index.jsx +0 -30
- package/src/ui/react/game/cursor/cursor.module.scss +0 -47
- package/src/ui/react/game/cursor/index.jsx +0 -20
- package/src/ui/react/game/form/fields/field/field.module.scss +0 -5
- package/src/ui/react/game/form/fields/field/index.jsx +0 -56
- package/src/ui/react/game/form/fields/fields.module.scss +0 -48
- package/src/ui/react/game/form/fields/index.jsx +0 -12
- package/src/ui/react/game/form/form.module.scss +0 -18
- package/src/ui/react/game/form/index.jsx +0 -22
- package/src/ui/react/game/fps/index.jsx +0 -16
- package/src/ui/react/game/game.jsx +0 -71
- package/src/ui/react/game/index.jsx +0 -29
- package/src/ui/react/game/platform/index.jsx +0 -30
- package/src/ui/react/game/platform/platform.module.scss +0 -7
- package/src/ui/react/game/scene/index.jsx +0 -25
- package/src/ui/react/game/scene/scene.module.scss +0 -9
- package/src/ui/react/game/sprite/index.jsx +0 -58
- package/src/ui/react/game/sprite/sprite.module.css +0 -3
- package/src/ui/react/game/stats/index.jsx +0 -22
- package/src/ui/react/hocs/with-absolute-position/index.jsx +0 -20
- package/src/ui/react/hocs/with-absolute-position/with-absolute-position.module.scss +0 -5
- package/src/ui/react/index.jsx +0 -9
- package/src/utils/algorithms/decision-tree.js +0 -24
- package/src/utils/algorithms/decision-tree.test.js +0 -102
- package/src/utils/algorithms/path-finding.js +0 -155
- package/src/utils/algorithms/path-finding.test.js +0 -151
- package/src/utils/algorithms/types.d.ts +0 -28
- package/src/utils/data-structures/array.js +0 -83
- package/src/utils/data-structures/array.test.js +0 -173
- package/src/utils/data-structures/board.js +0 -159
- package/src/utils/data-structures/board.test.js +0 -242
- package/src/utils/data-structures/boolean.js +0 -9
- package/src/utils/data-structures/heap.js +0 -164
- package/src/utils/data-structures/heap.test.js +0 -103
- package/src/utils/data-structures/object.js +0 -102
- package/src/utils/data-structures/object.test.js +0 -121
- package/src/utils/data-structures/objects.js +0 -48
- package/src/utils/data-structures/objects.test.js +0 -99
- package/src/utils/data-structures/tree.js +0 -36
- package/src/utils/data-structures/tree.test.js +0 -33
- package/src/utils/data-structures/types.d.ts +0 -4
- package/src/utils/functions/functions.js +0 -19
- package/src/utils/functions/functions.test.js +0 -23
- package/src/utils/math/geometry/circle.js +0 -117
- package/src/utils/math/geometry/circle.test.js +0 -97
- package/src/utils/math/geometry/hitmask.js +0 -39
- package/src/utils/math/geometry/hitmask.test.js +0 -84
- package/src/utils/math/geometry/line.js +0 -35
- package/src/utils/math/geometry/line.test.js +0 -49
- package/src/utils/math/geometry/platform.js +0 -42
- package/src/utils/math/geometry/platform.test.js +0 -133
- package/src/utils/math/geometry/point.js +0 -71
- package/src/utils/math/geometry/point.test.js +0 -81
- package/src/utils/math/geometry/rectangle.js +0 -45
- package/src/utils/math/geometry/rectangle.test.js +0 -42
- package/src/utils/math/geometry/segment.js +0 -80
- package/src/utils/math/geometry/segment.test.js +0 -183
- package/src/utils/math/geometry/triangle.js +0 -15
- package/src/utils/math/geometry/triangle.test.js +0 -11
- package/src/utils/math/geometry/types.d.ts +0 -23
- package/src/utils/math/linear-algebra/2d.js +0 -28
- package/src/utils/math/linear-algebra/2d.test.js +0 -17
- package/src/utils/math/linear-algebra/quaternion.js +0 -22
- package/src/utils/math/linear-algebra/quaternion.test.js +0 -25
- package/src/utils/math/linear-algebra/quaternions.js +0 -20
- package/src/utils/math/linear-algebra/quaternions.test.js +0 -29
- package/src/utils/math/linear-algebra/types.d.ts +0 -4
- package/src/utils/math/linear-algebra/vector.js +0 -302
- package/src/utils/math/linear-algebra/vector.test.js +0 -257
- package/src/utils/math/linear-algebra/vectors.js +0 -122
- package/src/utils/math/linear-algebra/vectors.test.js +0 -65
- package/src/utils/math/numbers.js +0 -90
- package/src/utils/math/numbers.test.js +0 -137
- package/src/utils/math/rng.js +0 -44
- package/src/utils/math/rng.test.js +0 -39
- package/src/utils/math/statistics.js +0 -43
- package/src/utils/math/statistics.test.js +0 -47
- package/src/utils/math/trigonometry.js +0 -89
- package/src/utils/math/trigonometry.test.js +0 -52
- package/src/utils/physics/acceleration.js +0 -63
- package/src/utils/physics/friction.js +0 -30
- package/src/utils/physics/friction.test.js +0 -44
- package/src/utils/physics/gravity.js +0 -71
- package/src/utils/physics/gravity.test.js +0 -80
- package/src/utils/physics/jump.js +0 -41
- 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
|
-
}
|
package/src/engine/loop.js
DELETED
|
@@ -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
|
-
})
|
package/src/engine/store.js
DELETED
|
@@ -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
|
-
}
|