@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,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
|
+
}
|
|
@@ -4,19 +4,15 @@ const DEFAULT_PARAMS = {
|
|
|
4
4
|
onState: "default",
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export function
|
|
7
|
+
export function collidable(params) {
|
|
8
8
|
params = extend(DEFAULT_PARAMS, params)
|
|
9
9
|
|
|
10
10
|
return (type) =>
|
|
11
11
|
extend(type, {
|
|
12
12
|
states: {
|
|
13
13
|
[params.onState]: {
|
|
14
|
-
|
|
15
|
-
type.states?.[params.onState]
|
|
16
|
-
instance,
|
|
17
|
-
event,
|
|
18
|
-
options,
|
|
19
|
-
)
|
|
14
|
+
update(entity, dt, api) {
|
|
15
|
+
type.states?.[params.onState].update?.(entity, dt, api)
|
|
20
16
|
},
|
|
21
17
|
},
|
|
22
18
|
},
|
|
@@ -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,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 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
|
+
}
|
package/src/core/api.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createSelector as _createSelector } from "./select.js"
|
|
2
|
+
|
|
3
|
+
export function createApi(store) {
|
|
4
|
+
const createSelector = (inputSelectors, resultFunc) => {
|
|
5
|
+
const selector = _createSelector(inputSelectors, resultFunc)
|
|
6
|
+
return () => selector(store.getState())
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const getTypes = () => store.getTypes()
|
|
10
|
+
|
|
11
|
+
const getEntities = () => store.getState().entities
|
|
12
|
+
|
|
13
|
+
const getEntity = (id) => getEntities()[id]
|
|
14
|
+
|
|
15
|
+
const notify = (type, payload) => {
|
|
16
|
+
store.notify(type, payload)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dispatch = (action) => {
|
|
20
|
+
store.dispatch(action)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const getType = (id) => store.getOriginalTypes()?.[id]
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
createSelector,
|
|
27
|
+
getTypes,
|
|
28
|
+
getEntities,
|
|
29
|
+
getEntity,
|
|
30
|
+
getType,
|
|
31
|
+
notify,
|
|
32
|
+
dispatch,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export const ACTION_BLACKLIST = [
|
|
2
|
+
"update",
|
|
3
|
+
"gamepadAxis",
|
|
4
|
+
"gamepadPress",
|
|
5
|
+
"gamepadRelease",
|
|
6
|
+
"keyboardKeyDown",
|
|
7
|
+
"keyboardKeyUp",
|
|
8
|
+
"inputAxis",
|
|
9
|
+
"inputPress",
|
|
10
|
+
"inputRelease",
|
|
11
|
+
"mouseMove",
|
|
12
|
+
"mouseClick",
|
|
13
|
+
"spriteAnimationEnd",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const LAST_STATE = 1
|
|
17
|
+
|
|
18
|
+
let devToolsInstance = null
|
|
19
|
+
let unsubscribe = null
|
|
20
|
+
|
|
21
|
+
export function initDevTools(store) {
|
|
22
|
+
// Prevent multiple connections
|
|
23
|
+
if (devToolsInstance) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof window === "undefined" || !window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
devToolsInstance = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
|
|
32
|
+
name: "Inglorious Engine",
|
|
33
|
+
predicate: (state, action) => !ACTION_BLACKLIST.includes(action.type),
|
|
34
|
+
actionCreators: {
|
|
35
|
+
jump: () => ({ type: "jump", payload: { inputId: "input0" } }),
|
|
36
|
+
},
|
|
37
|
+
// @see https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#features
|
|
38
|
+
features: {
|
|
39
|
+
pause: true, // start/pause recording of dispatched actions
|
|
40
|
+
lock: true, // lock/unlock dispatching actions and side effects
|
|
41
|
+
persist: true, // persist states on page reloading
|
|
42
|
+
export: true, // export history of actions in a file
|
|
43
|
+
import: "custom", // import history of actions from a file
|
|
44
|
+
jump: false, // jump back and forth (time travelling)
|
|
45
|
+
skip: false, // skip (cancel) actions
|
|
46
|
+
reorder: false, // drag and drop actions in the history list
|
|
47
|
+
dispatch: true, // dispatch custom actions or action creators
|
|
48
|
+
test: false, // generate tests for the selected actions
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
unsubscribe = devToolsInstance.subscribe((message) => {
|
|
53
|
+
switch (message.type) {
|
|
54
|
+
case "DISPATCH":
|
|
55
|
+
handleDispatch(message, store)
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
case "ACTION":
|
|
59
|
+
handleAction(message, store)
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
devToolsInstance.init(store.getState())
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function disconnectDevTools() {
|
|
68
|
+
// The `disconnect` method on the devToolsInstance is not available in all
|
|
69
|
+
// environments or versions of the extension.
|
|
70
|
+
// The safest way to "disconnect" is to unsubscribe from any listeners
|
|
71
|
+
// and release our reference to the instance, which prevents any further
|
|
72
|
+
// actions from being sent.
|
|
73
|
+
if (unsubscribe) {
|
|
74
|
+
unsubscribe()
|
|
75
|
+
unsubscribe = null
|
|
76
|
+
devToolsInstance = null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function sendAction(action, state) {
|
|
81
|
+
if (devToolsInstance) {
|
|
82
|
+
devToolsInstance.send(action, state)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleDispatch(message, store) {
|
|
87
|
+
switch (message.payload.type) {
|
|
88
|
+
// reset button
|
|
89
|
+
case "RESET": {
|
|
90
|
+
store.reset()
|
|
91
|
+
devToolsInstance.init(store.getState())
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// revert button
|
|
96
|
+
case "ROLLBACK": {
|
|
97
|
+
const newState = JSON.parse(message.state)
|
|
98
|
+
store.setState(newState)
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// commit button
|
|
103
|
+
case "COMMIT": {
|
|
104
|
+
devToolsInstance.init(store.getState())
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// import from file button
|
|
109
|
+
case "IMPORT_STATE": {
|
|
110
|
+
const { computedStates, actionsById } = message.payload.nextLiftedState
|
|
111
|
+
|
|
112
|
+
const [firstComputedState] = computedStates
|
|
113
|
+
const lastComputedState =
|
|
114
|
+
computedStates[computedStates.length - LAST_STATE]
|
|
115
|
+
if (lastComputedState) {
|
|
116
|
+
store.setState(lastComputedState.state)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const flattenedActions = Object.values(actionsById)
|
|
120
|
+
.flatMap(({ action }) => action.payload ?? action)
|
|
121
|
+
.map((action, index) => [index, action])
|
|
122
|
+
|
|
123
|
+
devToolsInstance.init(
|
|
124
|
+
firstComputedState.state,
|
|
125
|
+
Object.fromEntries(flattenedActions),
|
|
126
|
+
)
|
|
127
|
+
break
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function handleAction(message, store) {
|
|
133
|
+
const action = JSON.parse(message.payload)
|
|
134
|
+
store.dispatch(action)
|
|
135
|
+
}
|