@inglorious/engine 0.8.0 → 0.10.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 +10 -11
- package/package.json +4 -4
- package/src/behaviors/audio.js +30 -3
- package/src/behaviors/game.js +8 -0
- package/src/behaviors/input/mouse.js +7 -10
- package/src/behaviors/physics/clamped.js +1 -1
- package/src/core/engine.js +26 -15
- package/src/main.js +4 -4
- package/src/physics/bounds.js +17 -16
package/README.md
CHANGED
|
@@ -31,9 +31,9 @@ npm install @inglorious/engine
|
|
|
31
31
|
|
|
32
32
|
## API
|
|
33
33
|
|
|
34
|
-
### `new Engine(
|
|
34
|
+
### `new Engine(...gameConfigs)`
|
|
35
35
|
|
|
36
|
-
Creates a new `Engine` instance.
|
|
36
|
+
Creates a new `Engine` instance, given one or more configuration objects.
|
|
37
37
|
|
|
38
38
|
**Parameters:**
|
|
39
39
|
|
|
@@ -41,10 +41,9 @@ Creates a new `Engine` instance.
|
|
|
41
41
|
- `loop` (object, optional): Configuration for the game loop.
|
|
42
42
|
- `type` (string, optional): The type of loop to use (`animationFrame` or `fixed`). Defaults to `animationFrame`.
|
|
43
43
|
- `fps` (number, optional): The target frames per second. Only used with the `fixed` loop type. Defaults to `60`.
|
|
44
|
-
- `systems` (array, optional): An array of system objects, which define behaviors for the whole state.
|
|
45
44
|
- `types` (object, optional): A map of entity types.
|
|
46
45
|
- `entities` (object, optional): A map of initial entities.
|
|
47
|
-
- `
|
|
46
|
+
- `systems` (array, optional): An array of system objects, which define behaviors for the whole state.
|
|
48
47
|
|
|
49
48
|
**Returns:**
|
|
50
49
|
|
|
@@ -72,7 +71,7 @@ Here is a complete example showing how to set up and run a game using the engine
|
|
|
72
71
|
<!DOCTYPE html>
|
|
73
72
|
<html lang="en">
|
|
74
73
|
<body>
|
|
75
|
-
<canvas id="canvas"></canvas>
|
|
74
|
+
<canvas id="canvas" width="800" height="600"></canvas>
|
|
76
75
|
|
|
77
76
|
<script type="text/javascript">
|
|
78
77
|
window.process = { env: "development" }
|
|
@@ -81,11 +80,11 @@ Here is a complete example showing how to set up and run a game using the engine
|
|
|
81
80
|
<script type="importmap">
|
|
82
81
|
{
|
|
83
82
|
"imports": {
|
|
84
|
-
"immer": "https://unpkg.com/immer@
|
|
85
|
-
"@inglorious/utils/": "https://unpkg.com/@inglorious%2Futils@
|
|
86
|
-
"@inglorious/store/": "https://unpkg.com/@inglorious%2Fstore@
|
|
87
|
-
"@inglorious/engine/": "https://unpkg.com/@inglorious%2Fengine@
|
|
88
|
-
"@inglorious/
|
|
83
|
+
"immer": "https://unpkg.com/immer@latest/dist/immer.mjs",
|
|
84
|
+
"@inglorious/utils/": "https://unpkg.com/@inglorious%2Futils@latest/src/",
|
|
85
|
+
"@inglorious/store/": "https://unpkg.com/@inglorious%2Fstore@latest/src/",
|
|
86
|
+
"@inglorious/engine/": "https://unpkg.com/@inglorious%2Fengine@latest/src/",
|
|
87
|
+
"@inglorious/renderer-2d/": "https://unpkg.com/@inglorious%2Frenderer-2d@latest/src/",
|
|
89
88
|
"game": "/game.js"
|
|
90
89
|
}
|
|
91
90
|
}
|
|
@@ -93,7 +92,7 @@ Here is a complete example showing how to set up and run a game using the engine
|
|
|
93
92
|
|
|
94
93
|
<script
|
|
95
94
|
type="module"
|
|
96
|
-
src="https://unpkg.com/@inglorious%2Fengine@
|
|
95
|
+
src="https://unpkg.com/@inglorious%2Fengine@latest/src/main.js"
|
|
97
96
|
></script>
|
|
98
97
|
</body>
|
|
99
98
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "A JavaScript game engine written with global state, immutability, and pure functions in mind. Have fun(ctional programming) with it!",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"./*": "./src/*"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"src
|
|
27
|
+
"src"
|
|
28
28
|
],
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@inglorious/
|
|
34
|
-
"@inglorious/
|
|
33
|
+
"@inglorious/utils": "2.2.0",
|
|
34
|
+
"@inglorious/store": "3.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"prettier": "^3.5.3",
|
package/src/behaviors/audio.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
const DEFAULT_VOLUME = 1
|
|
2
|
+
|
|
1
3
|
export function audio() {
|
|
2
4
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
|
5
|
+
|
|
3
6
|
const audioBufferCache = new Map()
|
|
7
|
+
const activeSources = new Map()
|
|
4
8
|
|
|
5
9
|
return {
|
|
6
10
|
async init(entity) {
|
|
7
11
|
const sounds = entity.sounds || {}
|
|
8
12
|
|
|
9
13
|
await Promise.all(
|
|
10
|
-
Object.entries(sounds).map(async ([name, url]) => {
|
|
14
|
+
Object.entries(sounds).map(async ([name, { url }]) => {
|
|
11
15
|
const response = await fetch(url)
|
|
12
16
|
const arrayBuffer = await response.arrayBuffer()
|
|
13
17
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer)
|
|
@@ -16,13 +20,36 @@ export function audio() {
|
|
|
16
20
|
)
|
|
17
21
|
},
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
soundPlay(entity, name) {
|
|
24
|
+
const { volume = DEFAULT_VOLUME, loop } = entity.sounds[name]
|
|
20
25
|
const audioBuffer = audioBufferCache.get(name)
|
|
21
26
|
|
|
22
27
|
const source = audioContext.createBufferSource()
|
|
28
|
+
const gainNode = audioContext.createGain()
|
|
29
|
+
|
|
23
30
|
source.buffer = audioBuffer
|
|
24
|
-
|
|
31
|
+
gainNode.gain.value = volume
|
|
32
|
+
|
|
33
|
+
source.connect(gainNode)
|
|
34
|
+
gainNode.connect(audioContext.destination)
|
|
35
|
+
|
|
36
|
+
source.loop = loop
|
|
25
37
|
source.start()
|
|
38
|
+
|
|
39
|
+
activeSources.set(name, source)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
soundStop(entity, name) {
|
|
43
|
+
const source = activeSources.get(name)
|
|
44
|
+
source?.stop()
|
|
45
|
+
activeSources.delete(name)
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
stop() {
|
|
49
|
+
for (const source of activeSources.values()) {
|
|
50
|
+
source.stop()
|
|
51
|
+
}
|
|
52
|
+
activeSources.clear()
|
|
26
53
|
},
|
|
27
54
|
}
|
|
28
55
|
}
|
package/src/behaviors/game.js
CHANGED
|
@@ -22,7 +22,7 @@ export function mouse() {
|
|
|
22
22
|
|
|
23
23
|
entity.position = position
|
|
24
24
|
|
|
25
|
-
clampToBounds(entity, game.
|
|
25
|
+
clampToBounds(entity, game.size)
|
|
26
26
|
},
|
|
27
27
|
|
|
28
28
|
mouseClick(entity, position, api) {
|
|
@@ -74,20 +74,17 @@ function createHandler(type, parent, api) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// For move and click events, the payload is the calculated position.
|
|
77
|
-
const payload = calculatePosition(
|
|
78
|
-
clientX: event.clientX,
|
|
79
|
-
clientY: event.clientY,
|
|
80
|
-
parent,
|
|
81
|
-
})
|
|
77
|
+
const payload = calculatePosition(event, parent)
|
|
82
78
|
api.notify(type, payload)
|
|
83
79
|
}
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
function calculatePosition(
|
|
87
|
-
const
|
|
82
|
+
function calculatePosition(event, parent) {
|
|
83
|
+
const { clientX, clientY } = event
|
|
84
|
+
const { left, bottom } = parent.getBoundingClientRect()
|
|
88
85
|
|
|
89
|
-
const x = clientX -
|
|
90
|
-
const z =
|
|
86
|
+
const x = clientX - left
|
|
87
|
+
const z = bottom - clientY
|
|
91
88
|
|
|
92
89
|
return [x, NO_Y, z]
|
|
93
90
|
}
|
package/src/core/engine.js
CHANGED
|
@@ -2,7 +2,10 @@ import { audio } from "@inglorious/engine/behaviors/audio.js"
|
|
|
2
2
|
import { game } from "@inglorious/engine/behaviors/game.js"
|
|
3
3
|
import { createApi } from "@inglorious/store/api.js"
|
|
4
4
|
import { createStore } from "@inglorious/store/store.js"
|
|
5
|
-
import {
|
|
5
|
+
import { augmentType } from "@inglorious/store/types.js"
|
|
6
|
+
import { isArray } from "@inglorious/utils/data-structures/array.js"
|
|
7
|
+
import { extendWith } from "@inglorious/utils/data-structures/objects.js"
|
|
8
|
+
import { isVector } from "@inglorious/utils/math/linear-algebra/vector.js"
|
|
6
9
|
|
|
7
10
|
import { coreEvents } from "./core-events.js"
|
|
8
11
|
import { disconnectDevTools, initDevTools, sendAction } from "./dev-tools.js"
|
|
@@ -21,12 +24,12 @@ const DEFAULT_GAME_CONFIG = {
|
|
|
21
24
|
|
|
22
25
|
types: {
|
|
23
26
|
game: [game()],
|
|
24
|
-
audio: audio(),
|
|
27
|
+
audio: [audio()],
|
|
25
28
|
},
|
|
26
29
|
|
|
27
30
|
entities: {
|
|
28
31
|
// eslint-disable-next-line no-magic-numbers
|
|
29
|
-
game: { type: "game",
|
|
32
|
+
game: { type: "game", size: [800, 600] },
|
|
30
33
|
audio: { type: "audio", sounds: {} },
|
|
31
34
|
},
|
|
32
35
|
}
|
|
@@ -41,8 +44,8 @@ export class Engine {
|
|
|
41
44
|
* @param {Object} [gameConfig] - Game-specific configuration.
|
|
42
45
|
* @param {Object} [renderer] - UI entity responsible for rendering. It must have a `render` method.
|
|
43
46
|
*/
|
|
44
|
-
constructor(
|
|
45
|
-
this._config =
|
|
47
|
+
constructor(...gameConfigs) {
|
|
48
|
+
this._config = extendWith(merger, DEFAULT_GAME_CONFIG, ...gameConfigs)
|
|
46
49
|
|
|
47
50
|
// Determine devMode from the entities config.
|
|
48
51
|
const devMode = this._config.entities.game?.devMode
|
|
@@ -50,9 +53,6 @@ export class Engine {
|
|
|
50
53
|
|
|
51
54
|
// Add user-defined systems
|
|
52
55
|
const systems = [...(this._config.systems ?? [])]
|
|
53
|
-
if (this._config.renderer) {
|
|
54
|
-
systems.push(...this._config.renderer.getSystems())
|
|
55
|
-
}
|
|
56
56
|
|
|
57
57
|
this._store = createStore({ ...this._config, systems })
|
|
58
58
|
|
|
@@ -86,10 +86,13 @@ export class Engine {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
async init() {
|
|
89
|
-
return Promise.all(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
return Promise.all(
|
|
90
|
+
Object.values(this._config.entities).map((entity) => {
|
|
91
|
+
const originalType = this._config.types[entity.type]
|
|
92
|
+
const type = augmentType(originalType)
|
|
93
|
+
return type.init?.(entity, null, this._api)
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
/**
|
|
@@ -98,7 +101,6 @@ export class Engine {
|
|
|
98
101
|
start() {
|
|
99
102
|
this._api.notify("start")
|
|
100
103
|
this._loop.start(this, ONE_SECOND / this._config.loop.fps)
|
|
101
|
-
this.isRunning = true
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/**
|
|
@@ -108,8 +110,6 @@ export class Engine {
|
|
|
108
110
|
this._api.notify("stop")
|
|
109
111
|
this._store.update(this._api)
|
|
110
112
|
this._loop.stop()
|
|
111
|
-
this._config.renderer?.destroy()
|
|
112
|
-
this.isRunning = false
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
@@ -145,3 +145,14 @@ export class Engine {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
+
|
|
149
|
+
function merger(targetValue, sourceValue) {
|
|
150
|
+
if (
|
|
151
|
+
isArray(targetValue) &&
|
|
152
|
+
!isVector(targetValue) &&
|
|
153
|
+
isArray(sourceValue) &&
|
|
154
|
+
!isVector(sourceValue)
|
|
155
|
+
) {
|
|
156
|
+
return [...targetValue, ...sourceValue]
|
|
157
|
+
}
|
|
158
|
+
}
|
package/src/main.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Engine } from "@inglorious/engine/core/engine.js"
|
|
2
|
-
import {
|
|
2
|
+
import { createRenderer } from "@inglorious/renderer-2d/index.js"
|
|
3
3
|
import game from "game"
|
|
4
4
|
|
|
5
|
-
const canvas = document.getElementById("canvas")
|
|
6
5
|
window.addEventListener("load", async () => {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
const canvas = document.getElementById("canvas")
|
|
7
|
+
const renderer = createRenderer(canvas)
|
|
8
|
+
const engine = new Engine(renderer, game)
|
|
9
9
|
await engine.init()
|
|
10
10
|
engine.start()
|
|
11
11
|
})
|
package/src/physics/bounds.js
CHANGED
|
@@ -9,20 +9,21 @@ import {
|
|
|
9
9
|
import { sum } from "@inglorious/utils/math/linear-algebra/vectors.js"
|
|
10
10
|
import { abs } from "@inglorious/utils/math/numbers.js"
|
|
11
11
|
|
|
12
|
+
const ORIGIN = 0
|
|
12
13
|
const DOUBLE = 2
|
|
13
14
|
const HALF = 2
|
|
14
15
|
const X = 0
|
|
15
16
|
const Z = 2
|
|
16
17
|
|
|
17
|
-
export function bounce(entity, dt, [
|
|
18
|
+
export function bounce(entity, dt, [maxX, maxZ]) {
|
|
18
19
|
const [x, , z] = entity.position
|
|
19
20
|
|
|
20
21
|
const velocity = createVector(entity.maxSpeed, entity.orientation)
|
|
21
|
-
if (x <
|
|
22
|
+
if (x < ORIGIN || x >= maxX) {
|
|
22
23
|
velocity[X] = -velocity[X]
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
if (z <
|
|
26
|
+
if (z < ORIGIN || z >= maxZ) {
|
|
26
27
|
velocity[Z] = -velocity[Z]
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -33,7 +34,7 @@ export function bounce(entity, dt, [minX, minZ, maxX, maxZ]) {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const ClampToBoundsByShape = {
|
|
36
|
-
rectangle(entity, [
|
|
37
|
+
rectangle(entity, [maxX, maxZ], collisionGroup) {
|
|
37
38
|
const [width, height, depth] =
|
|
38
39
|
entity.collisions[collisionGroup].size ?? entity.size
|
|
39
40
|
|
|
@@ -43,31 +44,31 @@ const ClampToBoundsByShape = {
|
|
|
43
44
|
|
|
44
45
|
return clamp(
|
|
45
46
|
entity.position,
|
|
46
|
-
[
|
|
47
|
+
[halfWidth, halfHeight, halfDepth],
|
|
47
48
|
[maxX - halfWidth, maxZ - halfHeight, maxZ - halfDepth],
|
|
48
49
|
)
|
|
49
50
|
},
|
|
50
51
|
|
|
51
|
-
circle(entity, [
|
|
52
|
+
circle(entity, [maxX, maxY], collisionGroup, depthAxis = "y") {
|
|
52
53
|
const radius = entity.collisions[collisionGroup].radius ?? entity.radius
|
|
53
54
|
|
|
54
55
|
if (depthAxis === "z") {
|
|
55
56
|
return clamp(
|
|
56
57
|
entity.position,
|
|
57
|
-
[
|
|
58
|
+
[radius, radius, ORIGIN],
|
|
58
59
|
[maxX - radius, maxY - radius, maxY],
|
|
59
60
|
)
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
return clamp(
|
|
63
64
|
entity.position,
|
|
64
|
-
[
|
|
65
|
+
[radius, ORIGIN, radius],
|
|
65
66
|
[maxX - radius, maxY, maxY - radius],
|
|
66
67
|
)
|
|
67
68
|
},
|
|
68
69
|
|
|
69
|
-
point(entity, [
|
|
70
|
-
return clamp(entity.position,
|
|
70
|
+
point(entity, [maxX, maxZ]) {
|
|
71
|
+
return clamp(entity.position, zero(), [maxX, maxZ, maxZ])
|
|
71
72
|
},
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -82,7 +83,7 @@ export function clampToBounds(
|
|
|
82
83
|
return handler(entity, bounds, collisionGroup, depthAxis)
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
export function flip(entity, [
|
|
86
|
+
export function flip(entity, [maxX, maxZ]) {
|
|
86
87
|
const [x, , z] = entity.position
|
|
87
88
|
|
|
88
89
|
entity.collisions ??= {}
|
|
@@ -111,20 +112,20 @@ export function flip(entity, [minX, minZ, maxX, maxZ]) {
|
|
|
111
112
|
const direction = fromAngle(entity.orientation)
|
|
112
113
|
|
|
113
114
|
if (
|
|
114
|
-
left <
|
|
115
|
+
left < ORIGIN ||
|
|
115
116
|
right >= maxX ||
|
|
116
|
-
bottom <
|
|
117
|
+
bottom < ORIGIN ||
|
|
117
118
|
top >= maxZ ||
|
|
118
|
-
back <
|
|
119
|
+
back < ORIGIN ||
|
|
119
120
|
front >= maxZ
|
|
120
121
|
) {
|
|
121
|
-
if (left <
|
|
122
|
+
if (left < ORIGIN) {
|
|
122
123
|
direction[X] = abs(direction[X])
|
|
123
124
|
} else if (right >= maxX) {
|
|
124
125
|
direction[X] = -abs(direction[X])
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
if (back <
|
|
128
|
+
if (back < ORIGIN) {
|
|
128
129
|
direction[Z] = abs(direction[Z])
|
|
129
130
|
} else if (front >= maxZ) {
|
|
130
131
|
direction[Z] = -abs(direction[Z])
|