@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
package/README.md
CHANGED
|
@@ -1,72 +1,75 @@
|
|
|
1
|
-
# Inglorious Engine
|
|
2
|
-
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
|
|
5
|
-
A JavaScript game engine written with global state, immutability, and pure functions in mind. Have fun(ctional programming) with it!
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- **Functional & Data-Oriented**: Uses a single, immutable state object as the source of truth, inspired by functional programming principles.
|
|
10
|
-
- **Composable by Design**: Build complex behaviors by composing pure functions and decorators, offering a powerful alternative to inheritance.
|
|
11
|
-
- **Renderer Agnostic**: The engine is headless. You can use any rendering technology you like, from Canvas2D and HTML to React components.
|
|
12
|
-
- **Zero Build Step**: Write plain JavaScript and run it directly in the browser. No complex build configurations to worry about.
|
|
13
|
-
|
|
14
|
-
## Documentation
|
|
15
|
-
|
|
16
|
-
The best way to get started is with the official documentation, which includes a **[Quick Start Guide](https://inglorious-engine.vercel.app/?path=/docs/quick-start--docs)**.
|
|
17
|
-
|
|
18
|
-
Full documentation is available at: **[https://inglorious-engine.vercel.app/](https://inglorious-engine.vercel.app/)**
|
|
19
|
-
|
|
20
|
-
## Why Functional Programming?
|
|
21
|
-
|
|
22
|
-
What makes this engine different from all the others is that, instead of Object Oriented Programming (OOP), which seems the most obvious choice for a game engine, this one is based on Functional Programming (FP).
|
|
23
|
-
|
|
24
|
-
FP has many advantages:
|
|
25
|
-
|
|
26
|
-
1.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
1
|
+
# Inglorious Engine
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
|
|
5
|
+
A JavaScript game engine written with global state, immutability, and pure functions in mind. Have fun(ctional programming) with it!
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Functional & Data-Oriented**: Uses a single, immutable state object as the source of truth, inspired by functional programming principles.
|
|
10
|
+
- **Composable by Design**: Build complex behaviors by composing pure functions and decorators, offering a powerful alternative to inheritance.
|
|
11
|
+
- **Renderer Agnostic**: The engine is headless. You can use any rendering technology you like, from Canvas2D and HTML to React components.
|
|
12
|
+
- **Zero Build Step**: Write plain JavaScript and run it directly in the browser. No complex build configurations to worry about.
|
|
13
|
+
|
|
14
|
+
## Documentation
|
|
15
|
+
|
|
16
|
+
The best way to get started is with the official documentation, which includes a **[Quick Start Guide](https://inglorious-engine.vercel.app/?path=/docs/quick-start--docs)**.
|
|
17
|
+
|
|
18
|
+
Full documentation is available at: **[https://inglorious-engine.vercel.app/](https://inglorious-engine.vercel.app/)**
|
|
19
|
+
|
|
20
|
+
## Why Functional Programming?
|
|
21
|
+
|
|
22
|
+
What makes this engine different from all the others is that, instead of Object Oriented Programming (OOP), which seems the most obvious choice for a game engine, this one is based on Functional Programming (FP).
|
|
23
|
+
|
|
24
|
+
FP has many advantages:
|
|
25
|
+
|
|
26
|
+
1. **Single Source of Truth**: Your entire game state is a single, plain JavaScript object. This gives you complete control over your game's world at any moment, rather than having state scattered across countless objects. This is the core idea behind the Data-Oriented Design (DOD) paradigm that many modern engines are now adopting. With this engine, you get that benefit naturally.
|
|
27
|
+
|
|
28
|
+
2. **Efficient Immutability**: A common misconception is that creating a new state on every change is slow. This engine uses structural sharing (via Immer), meaning only the parts of the state that actually change are copied. The rest of the state tree is shared by reference, making updates extremely fast. This provides a huge benefit:
|
|
29
|
+
- **Optimized Rendering**: Detecting changes becomes trivial and fast. A simple reference check (`prevState === nextState`) is all that's needed to determine if data has changed, enabling highly performant UIs (especially with libraries like React). This is much faster than the deep, recursive comparisons required in mutable systems.
|
|
30
|
+
|
|
31
|
+
3. **Pure Functions**: Game logic is built with pure functions — functions that return a value based only on their inputs, with no side effects. This makes your game logic predictable, easy to test in isolation, and highly reusable, freeing you from the complexity of class methods with hidden side effects.
|
|
32
|
+
|
|
33
|
+
4. **Composition over Inheritance**: Instead of complex class hierarchies, you build entities by composing functions. Think of it as a pipeline of operations applied to your data. This is a more flexible and powerful alternative to inheritance. You can mix and match behaviors (e.g., `canBeControlledByPlayer`, `canBeHurt`, `canShoot`) on the fly, avoiding the rigidity and common problems of deep inheritance chains.
|
|
34
|
+
|
|
35
|
+
5. **Dynamic by Nature**: JavaScript objects are dynamic. You can add or remove properties from an entity at any time without being constrained by a rigid class definition. This is perfect for game development, where an entity's state can change unpredictably (e.g., gaining a temporary power-up). This flexibility allows for more emergent game mechanics.
|
|
36
|
+
|
|
37
|
+
6. **Unparalleled Debugging and Tooling**: Because the entire game state is a single, serializable object, you can unlock powerful development patterns that are difficult to achieve in traditional OOP engines.
|
|
38
|
+
- **Time-Travel Debugging**: Save the state at any frame. You can step backward and forward through state changes to find exactly when a bug was introduced.
|
|
39
|
+
- **Hot-Reloading**: Modify your game's logic and instantly see the results without restarting. The engine can reload the code and re-apply it to the current state, dramatically speeding up iteration.
|
|
40
|
+
- **Simplified Persistence**: Saving and loading a game is as simple as serializing and deserializing a single JSON object.
|
|
41
|
+
- **Simplified Networking**: For multiplayer games, you don't need to synchronize complex objects. You just send small, serializable `event` objects over the network. Each client processes the same event with the same pure event handler, guaranteeing their game states stay in sync.
|
|
42
|
+
|
|
43
|
+
7. **Leverage the Full JavaScript Ecosystem**: As a pure JavaScript engine, you have immediate access to the world's largest software repository: npm. Need advanced physics, complex AI, or a specific UI library? Integrate it with a simple `npm install`. You aren't limited to the built-in features or proprietary plugin ecosystem of a monolithic engine like Godot or Unity.
|
|
44
|
+
|
|
45
|
+
## Architecture: State Management
|
|
46
|
+
|
|
47
|
+
The engine's state management is inspired by Redux, but it's specifically tailored for the demands of game development. If you're familiar with Redux, you'll recognize the core pattern: the UI (or game view) is a projection of the state, and the only way to change the state is to dispatch an action.
|
|
48
|
+
|
|
49
|
+
However, there are several key differences that make it unique:
|
|
50
|
+
|
|
51
|
+
1. **Events, not Actions**: In Redux, you "dispatch actions." Here, we "notify of events." This is a deliberate semantic choice. An event handler is a function that reacts to a specific occurrence in the game world (e.g., `playerMove`, `enemyDestroy`). The naming convention is similar to standard JavaScript event handlers like `onClick`, where the handler name describes the event it's listening for.
|
|
52
|
+
|
|
53
|
+
2. **Asynchronous Event Queue**: Unlike Redux's synchronous dispatch, events are not processed immediately. They are added to a central event queue. The engine's main loop processes this queue once per frame. This approach has several advantages:
|
|
54
|
+
- It decouples game logic from state updates.
|
|
55
|
+
- It ensures state changes happen at a predictable point in the game loop, preventing race conditions or cascading updates within a single frame.
|
|
56
|
+
- It allows for event batching and provides a solid foundation for networking and time-travel debugging.
|
|
57
|
+
|
|
58
|
+
3. **Core Engine Events & Naming Convention**: The engine has a few built-in, **single-word** events that drive its core functionality. To avoid conflicts, you should use **multi-word `camelCase`** names for your own custom game events (`playerJump`, `itemCollect`). This convention is similar to how custom HTML elements require a hyphen to distinguish them from standard elements. Key engine events include:
|
|
59
|
+
- `update`: Fired on every frame, typically carrying the `deltaTime` since the last frame. This is where you'll put most of your continuous game logic (like movement).
|
|
60
|
+
- `add`: Used to add a new entity to the game state.
|
|
61
|
+
- `remove`: Used to remove an entity from the game state.
|
|
62
|
+
- `morph`: Used to dynamically change the behaviors associated with an entity's type.
|
|
63
|
+
|
|
64
|
+
4. **Ergonomic Immutability with Immer**: The state is immutable, but to make this easy to work with, we use Immer. Inside your event handlers, you can write code that looks like it's mutating the state directly. Immer handles the magic behind the scenes, producing a new, updated state with structural sharing, giving you the performance benefits of immutability with the developer experience of mutable code.
|
|
65
|
+
|
|
66
|
+
5. **Composable Handlers via Function Piping**: Instead of large, monolithic "reducers," you build event handlers by composing smaller, pure functions. The engine encourages a pipeline pattern where an event and the current state are passed through a series of decorators or transformations. This makes your logic highly modular, reusable, and easy to test in isolation.
|
|
67
|
+
|
|
68
|
+
6. **Handlers Can Issue New Events (Controlled Side-Effects)**: In a strict Redux pattern, reducers must be pure. We relax this rule for a pragmatic reason: event handlers in this engine **can notify of new events**. This allows you to create powerful, reactive chains of logic. For example, an `enemy:take_damage` handler might check the enemy's health and, if it drops to zero, notify of a new `enemy:destroyed` event.
|
|
69
|
+
- **How it works**: Any event notified from within a handler is simply added to the end of the main event queue. It will be processed in a subsequent pass of the game loop, not immediately. This prevents synchronous, cascading updates within a single frame and makes the flow of logic easier to trace.
|
|
70
|
+
|
|
71
|
+
- **A Word of Caution**: This power comes with responsibility. It is possible to create infinite loops (e.g., event A's handler notifies of event B, and event B's handler notifies of event A). Developers should be mindful of this when designing their event chains.
|
|
72
|
+
|
|
73
|
+
## Contributing
|
|
74
|
+
|
|
75
|
+
We welcome contributions from the community! Whether you're fixing a bug, adding a feature, or improving the documentation, your help is appreciated. Please read our Contributing Guidelines for details on how to get started.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
"url": "git+https://github.com/IngloriousCoderz/inglorious-engine.git"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://inglorious-engine.vercel.app/",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/IngloriousCoderz/inglorious-engine/issues"
|
|
14
|
+
},
|
|
12
15
|
"keywords": [
|
|
13
|
-
"
|
|
16
|
+
"data-oriented",
|
|
17
|
+
"ecs",
|
|
14
18
|
"functional-programming",
|
|
15
19
|
"gamedev",
|
|
16
|
-
"
|
|
17
|
-
"ecs",
|
|
18
|
-
"data-oriented"
|
|
20
|
+
"game-engine"
|
|
19
21
|
],
|
|
20
22
|
"type": "module",
|
|
21
23
|
"files": [
|
|
@@ -31,45 +33,21 @@
|
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
35
|
"immer": "^10.1.1",
|
|
34
|
-
"
|
|
35
|
-
"react-dom": "^19.0.0",
|
|
36
|
-
"react-redux": "^9.2.0"
|
|
36
|
+
"@inglorious/utils": "1.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@chromatic-com/storybook": "^3.2.6",
|
|
40
|
-
"@eslint/js": "^9.23.0",
|
|
41
|
-
"@storybook/addon-essentials": "^8.6.12",
|
|
42
|
-
"@storybook/addon-interactions": "^8.6.12",
|
|
43
|
-
"@storybook/addon-links": "^8.6.12",
|
|
44
|
-
"@storybook/blocks": "^8.6.12",
|
|
45
|
-
"@storybook/manager-api": "^8.6.12",
|
|
46
|
-
"@storybook/react": "^8.6.12",
|
|
47
|
-
"@storybook/react-vite": "^8.6.12",
|
|
48
|
-
"@storybook/test": "^8.6.12",
|
|
49
|
-
"@storybook/theming": "^8.6.12",
|
|
50
|
-
"@types/react": "^18.2.15",
|
|
51
|
-
"@types/react-dom": "^18.2.7",
|
|
52
|
-
"@vitejs/plugin-react": "^4.0.3",
|
|
53
|
-
"eslint": "^9.23.0",
|
|
54
|
-
"eslint-plugin-react": "^7.37.4",
|
|
55
|
-
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
56
|
-
"eslint-plugin-storybook": "^0.12.0",
|
|
57
|
-
"globals": "^16.0.0",
|
|
58
39
|
"husky": "^9.1.7",
|
|
59
40
|
"prettier": "^3.5.3",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"
|
|
41
|
+
"vite": "^7.1.3",
|
|
42
|
+
"vitest": "^1.6.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">= 22"
|
|
65
46
|
},
|
|
66
47
|
"scripts": {
|
|
67
|
-
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
68
48
|
"format": "prettier --write '**/*.{js,jsx}'",
|
|
49
|
+
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
69
50
|
"test:watch": "vitest",
|
|
70
|
-
"test": "vitest run"
|
|
71
|
-
"start": "serve",
|
|
72
|
-
"storybook": "storybook dev -p 6006",
|
|
73
|
-
"build-storybook": "storybook build"
|
|
51
|
+
"test": "vitest run"
|
|
74
52
|
}
|
|
75
53
|
}
|
|
@@ -13,28 +13,28 @@ const DEFAULT_ORIENTATION = 0
|
|
|
13
13
|
|
|
14
14
|
const HALF_ANGULAR_ACCELERATION = 0.5
|
|
15
15
|
|
|
16
|
-
export
|
|
17
|
-
|
|
16
|
+
export function align(
|
|
17
|
+
entity,
|
|
18
18
|
target,
|
|
19
|
+
dt,
|
|
19
20
|
{
|
|
20
|
-
dt,
|
|
21
21
|
targetRadius = DEFAULT_TARGET_RADIUS,
|
|
22
22
|
slowRadius = DEFAULT_SLOW_RADIUS,
|
|
23
23
|
timeToTarget = DEFAULT_TIME_TO_TARGET,
|
|
24
|
-
},
|
|
24
|
+
} = {},
|
|
25
25
|
) {
|
|
26
26
|
const maxAngularAcceleration =
|
|
27
|
-
|
|
28
|
-
const maxAngularSpeed =
|
|
27
|
+
entity.maxAngularAcceleration ?? DEFAULT_MAX_ANGULAR_ACCELERATION
|
|
28
|
+
const maxAngularSpeed = entity.maxAngularSpeed ?? DEFAULT_MAX_ANGULAR_SPEED
|
|
29
29
|
|
|
30
|
-
let angularSpeed =
|
|
31
|
-
let orientation =
|
|
30
|
+
let angularSpeed = entity.angularSpeed ?? DEFAULT_ANGULAR_SPEED
|
|
31
|
+
let orientation = entity.orientation ?? DEFAULT_ORIENTATION
|
|
32
32
|
|
|
33
33
|
const direction = toRange(target.orientation - orientation)
|
|
34
34
|
const distance = abs(direction)
|
|
35
35
|
|
|
36
36
|
if (distance < targetRadius) {
|
|
37
|
-
return
|
|
37
|
+
return entity
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
let targetAngularSpeed
|
|
@@ -1,34 +1,33 @@
|
|
|
1
|
+
import { matchVelocity } from "@inglorious/engine/ai/movement/dynamic/match-velocity.js"
|
|
1
2
|
import {
|
|
2
3
|
magnitude,
|
|
3
4
|
setMagnitude,
|
|
4
5
|
} from "@inglorious/utils/math/linear-algebra/vector.js"
|
|
5
6
|
import { subtract } from "@inglorious/utils/math/linear-algebra/vectors.js"
|
|
6
7
|
|
|
7
|
-
import matchVelocity from "./match-velocity.js"
|
|
8
|
-
|
|
9
8
|
export const DEFAULT_TARGET_RADIUS = 1
|
|
10
9
|
export const DEFAULT_SLOW_RADIUS = 100
|
|
11
10
|
export const DEFAULT_TIME_TO_TARGET = 0.1
|
|
12
11
|
|
|
13
12
|
const DEFAULT_MAX_SPEED = 0
|
|
14
13
|
|
|
15
|
-
export
|
|
16
|
-
|
|
14
|
+
export function arrive(
|
|
15
|
+
entity,
|
|
17
16
|
target,
|
|
17
|
+
dt,
|
|
18
18
|
{
|
|
19
|
-
dt,
|
|
20
19
|
targetRadius = DEFAULT_TARGET_RADIUS,
|
|
21
20
|
slowRadius = DEFAULT_SLOW_RADIUS,
|
|
22
21
|
timeToTarget = DEFAULT_TIME_TO_TARGET,
|
|
23
|
-
},
|
|
22
|
+
} = {},
|
|
24
23
|
) {
|
|
25
|
-
const maxSpeed =
|
|
24
|
+
const maxSpeed = entity.maxSpeed ?? DEFAULT_MAX_SPEED
|
|
26
25
|
|
|
27
|
-
const direction = subtract(target.position,
|
|
26
|
+
const direction = subtract(target.position, entity.position)
|
|
28
27
|
const distance = magnitude(direction)
|
|
29
28
|
|
|
30
29
|
if (distance < targetRadius) {
|
|
31
|
-
return
|
|
30
|
+
return entity
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
let speed
|
|
@@ -39,5 +38,5 @@ export default function arrive(
|
|
|
39
38
|
}
|
|
40
39
|
const velocity = setMagnitude(direction, speed)
|
|
41
40
|
|
|
42
|
-
return matchVelocity(
|
|
41
|
+
return matchVelocity(entity, { velocity }, dt, { timeToTarget })
|
|
43
42
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { flee } from "@inglorious/engine/ai/movement/dynamic/flee.js"
|
|
1
2
|
import {
|
|
2
3
|
magnitude,
|
|
3
4
|
multiply,
|
|
@@ -5,22 +6,21 @@ import {
|
|
|
5
6
|
} from "@inglorious/utils/math/linear-algebra/vector.js"
|
|
6
7
|
import { subtract, sum } from "@inglorious/utils/math/linear-algebra/vectors.js"
|
|
7
8
|
|
|
8
|
-
import flee from "./flee.js"
|
|
9
|
-
|
|
10
9
|
export const DEFAULT_MAX_PREDICTION = 10
|
|
11
10
|
|
|
12
|
-
export
|
|
13
|
-
|
|
11
|
+
export function evade(
|
|
12
|
+
entity,
|
|
14
13
|
target,
|
|
15
|
-
|
|
14
|
+
dt,
|
|
15
|
+
{ maxPrediction = DEFAULT_MAX_PREDICTION } = {},
|
|
16
16
|
) {
|
|
17
|
-
let velocity =
|
|
17
|
+
let velocity = entity.velocity ?? zero()
|
|
18
18
|
|
|
19
|
-
const direction = subtract(target.position,
|
|
19
|
+
const direction = subtract(target.position, entity.position)
|
|
20
20
|
const distance = magnitude(direction)
|
|
21
21
|
|
|
22
22
|
if (!distance) {
|
|
23
|
-
return
|
|
23
|
+
return entity
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const speed = magnitude(velocity)
|
|
@@ -34,5 +34,5 @@ export default function evade(
|
|
|
34
34
|
|
|
35
35
|
const position = sum(target.position, multiply(target.velocity, prediction))
|
|
36
36
|
|
|
37
|
-
return flee(
|
|
37
|
+
return flee(entity, { ...target, position }, dt)
|
|
38
38
|
}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
import { align } from "@inglorious/engine/ai/movement/dynamic/align.js"
|
|
1
2
|
import {
|
|
2
3
|
angle,
|
|
3
4
|
magnitude,
|
|
4
5
|
} from "@inglorious/utils/math/linear-algebra/vector.js"
|
|
5
6
|
import { subtract } from "@inglorious/utils/math/linear-algebra/vectors.js"
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export default function face(instance, target, options) {
|
|
10
|
-
const direction = subtract(target.position, instance.position)
|
|
8
|
+
export function face(entity, target, dt, options) {
|
|
9
|
+
const direction = subtract(target.position, entity.position)
|
|
11
10
|
const distance = magnitude(direction)
|
|
12
11
|
|
|
13
12
|
if (!distance) {
|
|
14
|
-
return
|
|
13
|
+
return entity
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
const orientation = angle(direction)
|
|
18
17
|
|
|
19
|
-
return align(
|
|
18
|
+
return align(entity, { ...target, orientation }, dt, options)
|
|
20
19
|
}
|
|
@@ -15,25 +15,26 @@ const MIN_SPEED = 0
|
|
|
15
15
|
|
|
16
16
|
const HALF_ACCELERATION = 0.5
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
const maxAcceleration =
|
|
20
|
-
const maxSpeed =
|
|
18
|
+
export function flee(entity, target, dt) {
|
|
19
|
+
const maxAcceleration = entity.maxAcceleration ?? DEFAULT_MAX_ACCELERATION
|
|
20
|
+
const maxSpeed = entity.maxSpeed ?? DEFAULT_MAX_SPEED
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
let velocity = entity.velocity ?? zero()
|
|
23
|
+
|
|
24
|
+
const direction = subtract(entity.position, target.position)
|
|
23
25
|
const distance = magnitude(direction)
|
|
24
26
|
|
|
25
27
|
if (!distance) {
|
|
26
|
-
return
|
|
28
|
+
return entity
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const acceleration = setMagnitude(direction, maxAcceleration)
|
|
30
32
|
|
|
31
|
-
let velocity = instance.velocity ?? zero()
|
|
32
33
|
velocity = sum(velocity, multiply(acceleration, dt))
|
|
33
34
|
velocity = clamp(velocity, MIN_SPEED, maxSpeed)
|
|
34
35
|
|
|
35
36
|
const position = sum(
|
|
36
|
-
|
|
37
|
+
entity.position,
|
|
37
38
|
multiply(velocity, dt),
|
|
38
39
|
multiply(acceleration, HALF_ACCELERATION * dt * dt),
|
|
39
40
|
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { align } from "@inglorious/engine/ai/movement/dynamic/align.js"
|
|
2
|
+
import {
|
|
3
|
+
angle,
|
|
4
|
+
magnitude,
|
|
5
|
+
zero,
|
|
6
|
+
} from "@inglorious/utils/math/linear-algebra/vector.js"
|
|
7
|
+
|
|
8
|
+
export function lookWhereYoureGoing(entity, dt, options) {
|
|
9
|
+
const velocity = entity.velocity ?? zero()
|
|
10
|
+
|
|
11
|
+
if (!magnitude(velocity)) {
|
|
12
|
+
return entity
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return align(entity, { orientation: angle(velocity) }, dt, options)
|
|
16
|
+
}
|
|
@@ -19,17 +19,18 @@ const MIN_SPEED = 0
|
|
|
19
19
|
|
|
20
20
|
const HALF_ACCELERATION = 0.5
|
|
21
21
|
|
|
22
|
-
export
|
|
23
|
-
|
|
22
|
+
export function matchVelocity(
|
|
23
|
+
entity,
|
|
24
24
|
target,
|
|
25
|
-
|
|
25
|
+
dt,
|
|
26
|
+
{ timeToTarget = DEFAULT_TIME_TO_TARGET } = {},
|
|
26
27
|
) {
|
|
27
|
-
const maxAcceleration =
|
|
28
|
-
const maxSpeed =
|
|
28
|
+
const maxAcceleration = entity.maxAcceleration ?? DEFAULT_MAX_ACCELERATION
|
|
29
|
+
const maxSpeed = entity.maxSpeed ?? DEFAULT_MAX_SPEED
|
|
29
30
|
|
|
30
|
-
let orientation =
|
|
31
|
+
let orientation = entity.orientation ?? DEFAULT_ORIENTATION
|
|
31
32
|
|
|
32
|
-
let velocity =
|
|
33
|
+
let velocity = entity.velocity ?? zero()
|
|
33
34
|
const velocityDelta = subtract(target.velocity, velocity)
|
|
34
35
|
|
|
35
36
|
let acceleration = divide(velocityDelta, timeToTarget)
|
|
@@ -39,7 +40,7 @@ export default function matchVelocity(
|
|
|
39
40
|
velocity = clamp(velocity, MIN_SPEED, maxSpeed)
|
|
40
41
|
|
|
41
42
|
const position = sum(
|
|
42
|
-
|
|
43
|
+
entity.position,
|
|
43
44
|
multiply(velocity, dt),
|
|
44
45
|
multiply(acceleration, HALF_ACCELERATION * dt * dt),
|
|
45
46
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { seek } from "@inglorious/engine/ai/movement/dynamic/seek.js"
|
|
1
2
|
import {
|
|
2
3
|
magnitude,
|
|
3
4
|
multiply,
|
|
@@ -5,22 +6,21 @@ import {
|
|
|
5
6
|
} from "@inglorious/utils/math/linear-algebra/vector.js"
|
|
6
7
|
import { subtract, sum } from "@inglorious/utils/math/linear-algebra/vectors.js"
|
|
7
8
|
|
|
8
|
-
import seek from "./seek.js"
|
|
9
|
-
|
|
10
9
|
export const DEFAULT_MAX_PREDICTION = 10
|
|
11
10
|
|
|
12
|
-
export
|
|
13
|
-
|
|
11
|
+
export function pursue(
|
|
12
|
+
entity,
|
|
14
13
|
target,
|
|
15
|
-
|
|
14
|
+
dt,
|
|
15
|
+
{ maxPrediction = DEFAULT_MAX_PREDICTION } = {},
|
|
16
16
|
) {
|
|
17
|
-
const velocity =
|
|
17
|
+
const velocity = entity.velocity ?? zero()
|
|
18
18
|
|
|
19
|
-
const direction = subtract(target.position,
|
|
19
|
+
const direction = subtract(target.position, entity.position)
|
|
20
20
|
const distance = magnitude(direction)
|
|
21
21
|
|
|
22
22
|
if (!distance) {
|
|
23
|
-
return
|
|
23
|
+
return entity
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const speed = magnitude(velocity)
|
|
@@ -34,5 +34,5 @@ export default function pursue(
|
|
|
34
34
|
|
|
35
35
|
const position = sum(target.position, multiply(target.velocity, prediction))
|
|
36
36
|
|
|
37
|
-
return seek(
|
|
37
|
+
return seek(entity, { ...target, position }, dt)
|
|
38
38
|
}
|
|
@@ -15,26 +15,25 @@ const MIN_SPEED = 0
|
|
|
15
15
|
|
|
16
16
|
const HALF_ACCELERATION = 0.5
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
const maxAcceleration =
|
|
20
|
-
const maxSpeed =
|
|
18
|
+
export function seek(entity, target, dt) {
|
|
19
|
+
const maxAcceleration = entity.maxAcceleration ?? DEFAULT_MAX_ACCELERATION
|
|
20
|
+
const maxSpeed = entity.maxSpeed ?? DEFAULT_MAX_SPEED
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const direction = subtract(instance.position, target.position)
|
|
22
|
+
const direction = subtract(target.position, entity.position)
|
|
25
23
|
const distance = magnitude(direction)
|
|
26
24
|
|
|
27
25
|
if (!distance) {
|
|
28
|
-
return
|
|
26
|
+
return entity
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
const acceleration = setMagnitude(direction, maxAcceleration)
|
|
32
30
|
|
|
31
|
+
let velocity = entity.velocity ?? zero()
|
|
33
32
|
velocity = sum(velocity, multiply(acceleration, dt))
|
|
34
33
|
velocity = clamp(velocity, MIN_SPEED, maxSpeed)
|
|
35
34
|
|
|
36
35
|
const position = sum(
|
|
37
|
-
|
|
36
|
+
entity.position,
|
|
38
37
|
multiply(velocity, dt),
|
|
39
38
|
multiply(acceleration, HALF_ACCELERATION * dt * dt),
|
|
40
39
|
)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { seek } from "@inglorious/engine/ai/movement/dynamic/seek.js"
|
|
1
2
|
import { createVector } from "@inglorious/utils/math/linear-algebra/vector.js"
|
|
2
3
|
import { sum } from "@inglorious/utils/math/linear-algebra/vectors.js"
|
|
3
4
|
import { randomBinomial } from "@inglorious/utils/math/rng.js"
|
|
4
5
|
|
|
5
|
-
import seek from "./seek.js"
|
|
6
|
-
|
|
7
6
|
export const DEFAULT_WANDER_OFFSET = 100
|
|
8
7
|
export const DEFAULT_WANDER_RADIUS = 100
|
|
9
8
|
|
|
@@ -11,22 +10,22 @@ const DEFAULT_MAX_ANGULAR_SPEED = 0
|
|
|
11
10
|
|
|
12
11
|
const DEFAULT_ORIENTATION = 0
|
|
13
12
|
|
|
14
|
-
export
|
|
15
|
-
|
|
13
|
+
export function wander(
|
|
14
|
+
entity,
|
|
15
|
+
dt,
|
|
16
16
|
{
|
|
17
17
|
wanderOffset = DEFAULT_WANDER_OFFSET,
|
|
18
18
|
wanderRadius = DEFAULT_WANDER_RADIUS,
|
|
19
|
-
|
|
20
|
-
},
|
|
19
|
+
} = {},
|
|
21
20
|
) {
|
|
22
|
-
const maxAngularSpeed =
|
|
21
|
+
const maxAngularSpeed = entity.maxAngularSpeed ?? DEFAULT_MAX_ANGULAR_SPEED
|
|
23
22
|
|
|
24
|
-
let orientation =
|
|
23
|
+
let orientation = entity.orientation ?? DEFAULT_ORIENTATION
|
|
25
24
|
|
|
26
|
-
let position = sum(
|
|
25
|
+
let position = sum(entity.position, createVector(wanderOffset, orientation))
|
|
27
26
|
|
|
28
27
|
orientation += randomBinomial() * maxAngularSpeed
|
|
29
28
|
position = sum(position, createVector(wanderRadius, orientation))
|
|
30
29
|
|
|
31
|
-
return seek(
|
|
30
|
+
return seek(entity, { position }, dt)
|
|
32
31
|
}
|
|
@@ -8,24 +8,24 @@ const DEFAULT_MAX_ANGULAR_SPEED = 0
|
|
|
8
8
|
|
|
9
9
|
const DEFAULT_ORIENTATION = 0
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
|
|
11
|
+
export function align(
|
|
12
|
+
entity,
|
|
13
13
|
target,
|
|
14
|
+
dt,
|
|
14
15
|
{
|
|
15
|
-
dt,
|
|
16
16
|
targetRadius = DEFAULT_TARGET_RADIUS,
|
|
17
17
|
timeToTarget = DEFAULT_TIME_TO_TARGET,
|
|
18
|
-
},
|
|
18
|
+
} = {},
|
|
19
19
|
) {
|
|
20
|
-
const maxAngularSpeed =
|
|
20
|
+
const maxAngularSpeed = entity.maxAngularSpeed ?? DEFAULT_MAX_ANGULAR_SPEED
|
|
21
21
|
|
|
22
|
-
let orientation =
|
|
22
|
+
let orientation = entity.orientation ?? DEFAULT_ORIENTATION
|
|
23
23
|
|
|
24
24
|
const direction = toRange(target.orientation - orientation)
|
|
25
25
|
const distance = abs(direction)
|
|
26
26
|
|
|
27
27
|
if (distance < targetRadius) {
|
|
28
|
-
return
|
|
28
|
+
return entity
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
let angularVelocity = direction / timeToTarget
|