@skewedaspect/sage 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/LICENSE +21 -0
- package/Readme.md +53 -0
- package/dist/classes/bindings/toggle.d.ts +122 -0
- package/dist/classes/bindings/trigger.d.ts +79 -0
- package/dist/classes/bindings/value.d.ts +104 -0
- package/dist/classes/entity.d.ts +83 -0
- package/dist/classes/eventBus.d.ts +94 -0
- package/dist/classes/gameEngine.d.ts +57 -0
- package/dist/classes/input/gamepad.d.ts +94 -0
- package/dist/classes/input/keyboard.d.ts +66 -0
- package/dist/classes/input/mouse.d.ts +80 -0
- package/dist/classes/input/readers/gamepad.d.ts +77 -0
- package/dist/classes/input/readers/keyboard.d.ts +60 -0
- package/dist/classes/input/readers/mouse.d.ts +45 -0
- package/dist/classes/loggers/consoleBackend.d.ts +29 -0
- package/dist/classes/loggers/nullBackend.d.ts +14 -0
- package/dist/engines/scene.d.ts +11 -0
- package/dist/interfaces/action.d.ts +20 -0
- package/dist/interfaces/binding.d.ts +144 -0
- package/dist/interfaces/entity.d.ts +9 -0
- package/dist/interfaces/game.d.ts +26 -0
- package/dist/interfaces/input.d.ts +181 -0
- package/dist/interfaces/logger.d.ts +88 -0
- package/dist/managers/binding.d.ts +185 -0
- package/dist/managers/entity.d.ts +70 -0
- package/dist/managers/game.d.ts +20 -0
- package/dist/managers/input.d.ts +56 -0
- package/dist/managers/level.d.ts +55 -0
- package/dist/sage.d.ts +20 -0
- package/dist/sage.es.js +2208 -0
- package/dist/sage.es.js.map +1 -0
- package/dist/sage.umd.js +2 -0
- package/dist/sage.umd.js.map +1 -0
- package/dist/utils/capabilities.d.ts +2 -0
- package/dist/utils/graphics.d.ts +10 -0
- package/dist/utils/logger.d.ts +66 -0
- package/dist/utils/physics.d.ts +2 -0
- package/dist/utils/version.d.ts +5 -0
- package/docs/architecture.md +129 -0
- package/docs/behaviors.md +706 -0
- package/docs/binding_system.md +820 -0
- package/docs/design/input.md +86 -0
- package/docs/entity_system.md +538 -0
- package/docs/eventbus.md +225 -0
- package/docs/getting_started.md +264 -0
- package/docs/images/sage_logo.png +0 -0
- package/docs/images/sage_logo_shape.png +0 -0
- package/docs/overview.md +38 -0
- package/docs/physics_system.md +686 -0
- package/docs/scene_system.md +513 -0
- package/package.json +69 -0
- package/src/classes/bindings/toggle.ts +261 -0
- package/src/classes/bindings/trigger.ts +211 -0
- package/src/classes/bindings/value.ts +227 -0
- package/src/classes/entity.ts +256 -0
- package/src/classes/eventBus.ts +259 -0
- package/src/classes/gameEngine.ts +125 -0
- package/src/classes/input/gamepad.ts +388 -0
- package/src/classes/input/keyboard.ts +189 -0
- package/src/classes/input/mouse.ts +276 -0
- package/src/classes/input/readers/gamepad.ts +179 -0
- package/src/classes/input/readers/keyboard.ts +123 -0
- package/src/classes/input/readers/mouse.ts +133 -0
- package/src/classes/loggers/consoleBackend.ts +135 -0
- package/src/classes/loggers/nullBackend.ts +51 -0
- package/src/engines/scene.ts +112 -0
- package/src/images/sage_logo.svg +172 -0
- package/src/images/sage_logo_shape.svg +146 -0
- package/src/interfaces/action.ts +30 -0
- package/src/interfaces/binding.ts +191 -0
- package/src/interfaces/entity.ts +21 -0
- package/src/interfaces/game.ts +44 -0
- package/src/interfaces/input.ts +221 -0
- package/src/interfaces/logger.ts +118 -0
- package/src/managers/binding.ts +729 -0
- package/src/managers/entity.ts +252 -0
- package/src/managers/game.ts +111 -0
- package/src/managers/input.ts +233 -0
- package/src/managers/level.ts +261 -0
- package/src/sage.ts +119 -0
- package/src/types/global.d.ts +11 -0
- package/src/utils/capabilities.ts +16 -0
- package/src/utils/graphics.ts +148 -0
- package/src/utils/logger.ts +225 -0
- package/src/utils/physics.ts +16 -0
- package/src/utils/version.ts +11 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Input System Design Document
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
|
|
5
|
+
The input system decouples raw device events (from keyboards, mice, controllers, etc.) from high-level game actions.
|
|
6
|
+
Low-level inputs are mapped to configurable actions (e.g., `action:jump` or `action:pitch`), and the system supports
|
|
7
|
+
both digital and analog inputs—including modifier key combinations. Entire sets of bindings are defined in
|
|
8
|
+
**Action Contexts**, which can be swapped dynamically when switching game contexts.
|
|
9
|
+
|
|
10
|
+
## 2. Requirements
|
|
11
|
+
|
|
12
|
+
- **High-Level Actions:**
|
|
13
|
+
- Support digital actions (e.g., toggle, press, momentary)
|
|
14
|
+
- Support analog actions (e.g., continuous values)
|
|
15
|
+
|
|
16
|
+
- **Action Contexts:**
|
|
17
|
+
- Facilitate switching game contexts at runtime by swapping entire input configurations
|
|
18
|
+
- Allow the developer to define which actions are active in each context (a global context is implied)
|
|
19
|
+
|
|
20
|
+
- **Flexible Binding Configuration:**
|
|
21
|
+
- Bindings can be loaded from external sources (batch definitions) or modified in real time (via a UI)
|
|
22
|
+
- In either case, loading bindings is considered an external concern to this library; it will simply consume the
|
|
23
|
+
provided objects/lists or offer endpoints to configure them on the fly
|
|
24
|
+
|
|
25
|
+
## 3. Binding Types
|
|
26
|
+
|
|
27
|
+
Bindings are categorized based on the type of input they accept and the type of action they output.
|
|
28
|
+
|
|
29
|
+
### Digital-to-Digital Bindings
|
|
30
|
+
|
|
31
|
+
- **Trigger**
|
|
32
|
+
• **Input:** Digital (key/button press)
|
|
33
|
+
• **Action:** Digital (instantaneous event, e.g., jump)
|
|
34
|
+
|
|
35
|
+
- **Momentary**
|
|
36
|
+
• **Input:** Digital (key/button hold)
|
|
37
|
+
• **Action:** Digital (active while held, e.g., sustained movement)
|
|
38
|
+
|
|
39
|
+
- **Toggle**
|
|
40
|
+
• **Input:** Digital (key/button press)
|
|
41
|
+
• **Action:** Digital (toggles state; remains set until triggered again, e.g., turning a flashlight on/off)
|
|
42
|
+
|
|
43
|
+
### Analog-to-Analog Bindings
|
|
44
|
+
|
|
45
|
+
- **Value**
|
|
46
|
+
• **Input:** Analog (continuous axis, such as a joystick)
|
|
47
|
+
• **Action:** Analog (emits continuous values, e.g., camera pitch)
|
|
48
|
+
|
|
49
|
+
### Digital-to-Analog Bindings
|
|
50
|
+
|
|
51
|
+
- **TriggerValue**
|
|
52
|
+
• **Input:** Digital (key/button press)
|
|
53
|
+
• **Action:** Analog (emits a fixed value when triggered, e.g., a key press produces a “1.0” value)
|
|
54
|
+
|
|
55
|
+
- **ToggleValue**
|
|
56
|
+
• **Input:** Digital (key/button press)
|
|
57
|
+
• **Action:** Analog (toggles between off and a defined analog value, similar to a digital toggle but emitting an
|
|
58
|
+
analog output)
|
|
59
|
+
|
|
60
|
+
- **MomentaryValue**
|
|
61
|
+
• **Input:** Digital (key/button hold)
|
|
62
|
+
• **Action:** Analog (continually emits a specified analog value while the input is held)
|
|
63
|
+
|
|
64
|
+
### Analog-to-Digital Bindings
|
|
65
|
+
- **ValueTrigger**
|
|
66
|
+
• **Input:** Analog (continuous axis, such as a joystick)
|
|
67
|
+
• **Action:** Digital (emits a digital event when the analog value exceeds a threshold, e.g., joystick tilt triggers
|
|
68
|
+
a jump)
|
|
69
|
+
|
|
70
|
+
## 4. Binding Modifiers
|
|
71
|
+
|
|
72
|
+
*Binding Modifiers* are additional properties that can be applied to any binding to enhance or alter its behavior
|
|
73
|
+
without affecting its fundamental digital or analog nature. Examples include:
|
|
74
|
+
|
|
75
|
+
- **Combination Modifier:**
|
|
76
|
+
• Requires additional inputs (e.g., modifier keys) to be active for the binding to fire
|
|
77
|
+
• Differentiates between a bare key press and one combined with a key modifier
|
|
78
|
+
|
|
79
|
+
- **Repeat Modifier:**
|
|
80
|
+
• Enables auto-repeat functionality for digital inputs with configurable delay and repeat rate
|
|
81
|
+
|
|
82
|
+
- **Analog Modifier (Deadzone/Sensitivity):**
|
|
83
|
+
• Adjusts the processing of analog inputs by applying deadzone filtering and sensitivity scaling
|
|
84
|
+
|
|
85
|
+
*Note:* Although analog modifiers naturally align with analog bindings, binding modifiers offer a unified mechanism
|
|
86
|
+
for behavior tweaking across all binding types.
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# Entity System Guide
|
|
2
|
+
|
|
3
|
+
SAGE uses a flexible building-block approach to create game objects. This guide explains how this system works and how you can use it to build your game.
|
|
4
|
+
|
|
5
|
+
## Building Games with Building Blocks
|
|
6
|
+
|
|
7
|
+
Think of creating game objects like building with LEGO bricks. Instead of creating one large, specialized piece for each object in your game, you use smaller, reusable pieces that snap together in different combinations.
|
|
8
|
+
|
|
9
|
+
For example, rather than creating a complete "Player" object from scratch, you might:
|
|
10
|
+
- Start with a basic object
|
|
11
|
+
- Add a piece that handles movement
|
|
12
|
+
- Add another piece that manages health
|
|
13
|
+
- Add a piece that handles player input
|
|
14
|
+
|
|
15
|
+
Then, for an enemy character, you might:
|
|
16
|
+
- Start with that same basic object
|
|
17
|
+
- Use that same movement piece
|
|
18
|
+
- Use that same health piece
|
|
19
|
+
- But instead of player input, add a piece that controls AI behavior
|
|
20
|
+
|
|
21
|
+
This approach lets you build many different game objects by mixing and matching these smaller pieces.
|
|
22
|
+
|
|
23
|
+
## Why Build Games This Way?
|
|
24
|
+
|
|
25
|
+
1. **Reuse and Efficiency**:
|
|
26
|
+
- Once you create a piece (like "movement"), you can use it in many different objects
|
|
27
|
+
- You don't have to rewrite the same code over and over
|
|
28
|
+
|
|
29
|
+
2. **Easier Changes**:
|
|
30
|
+
- Need to modify how movement works? Change it in one place
|
|
31
|
+
- All objects using that piece automatically get the update
|
|
32
|
+
|
|
33
|
+
3. **Flexibility**:
|
|
34
|
+
- Want to give a character new abilities? Just add the right pieces
|
|
35
|
+
- Want to temporarily modify something? Just swap pieces in or out
|
|
36
|
+
|
|
37
|
+
4. **Better Organization**:
|
|
38
|
+
- Each piece does one thing well
|
|
39
|
+
- Easier to understand and fix problems when things are separated
|
|
40
|
+
|
|
41
|
+
5. **Dynamic Changes**:
|
|
42
|
+
- Objects can change their capabilities during gameplay
|
|
43
|
+
- For example, a character could gain flying abilities by adding a "flying" piece when they get a power-up
|
|
44
|
+
|
|
45
|
+
## Our Building Block System: Entities and Behaviors
|
|
46
|
+
|
|
47
|
+
In SAGE, we call our system the "Entity System," and it has two main concepts:
|
|
48
|
+
|
|
49
|
+
### Entities
|
|
50
|
+
|
|
51
|
+
An entity is a game object - anything that exists in your game world. It could be:
|
|
52
|
+
- A character (player, enemy, NPC)
|
|
53
|
+
- An item (weapon, health pack)
|
|
54
|
+
- An obstacle (door, crate)
|
|
55
|
+
- An invisible manager (spawn point, trigger area)
|
|
56
|
+
|
|
57
|
+
Each entity has:
|
|
58
|
+
- A unique ID (so we can find it)
|
|
59
|
+
- A type (what kind of entity it is)
|
|
60
|
+
- State data (information about the entity, like position or health)
|
|
61
|
+
- A collection of behaviors (the pieces that define what it can do)
|
|
62
|
+
|
|
63
|
+
### Behaviors
|
|
64
|
+
|
|
65
|
+
Behaviors are the building blocks that define what an entity can do. Each behavior:
|
|
66
|
+
- Handles a specific aspect of functionality (movement, health, combat)
|
|
67
|
+
- Responds to events (like "take damage" or "player pressed jump")
|
|
68
|
+
- Can update each frame (for continuous actions like movement)
|
|
69
|
+
- Can interact with the entity's state data
|
|
70
|
+
|
|
71
|
+
Examples of behaviors:
|
|
72
|
+
- A movement behavior that updates position based on speed
|
|
73
|
+
- A health behavior that tracks damage and healing
|
|
74
|
+
- An input behavior that responds to player controls
|
|
75
|
+
- An AI behavior that makes decisions for enemies
|
|
76
|
+
|
|
77
|
+
For a comprehensive guide on creating and using behaviors, check out the [Behaviors Guide](behaviors.md). It provides detailed information about implementing, testing, and combining behaviors to create complex entity functionality.
|
|
78
|
+
|
|
79
|
+
## Technical Details
|
|
80
|
+
|
|
81
|
+
In code, these concepts are implemented as:
|
|
82
|
+
|
|
83
|
+
- `GameEntity`: The class that represents any object in your game
|
|
84
|
+
- `GameEntityBehavior`: The base class for all the building-block behaviors
|
|
85
|
+
|
|
86
|
+
When you want to create objects for your game, you'll define what type of entity it is, what state data it has, and which behaviors it should use. The Entity System takes care of connecting everything together and making sure it all works smoothly.
|
|
87
|
+
|
|
88
|
+
The following sections will go deeper into how to work with entities and behaviors, including code examples and advanced usage patterns.
|
|
89
|
+
|
|
90
|
+
## Entity Communication Architecture
|
|
91
|
+
|
|
92
|
+
In our game engine, we use an event-driven architecture that allows game objects (entities) to communicate without direct dependencies. This creates a more modular and maintainable system.
|
|
93
|
+
|
|
94
|
+
### Event Broadcasting System
|
|
95
|
+
|
|
96
|
+
Events function as a communication channel in our game. When an entity needs to communicate:
|
|
97
|
+
- It can "broadcast" events (like "I just took damage" or "Player collected an item")
|
|
98
|
+
- Other entities can "listen" for specific events they care about
|
|
99
|
+
- Listeners can respond when they receive relevant events
|
|
100
|
+
|
|
101
|
+
This pattern enables game objects to interact without being directly coupled, improving flexibility and maintainability. See the [Event Bus Guide](eventbus.md) for more information on how events work in SAGE.
|
|
102
|
+
|
|
103
|
+
## Core System Components
|
|
104
|
+
|
|
105
|
+
### Entities: Game Objects
|
|
106
|
+
|
|
107
|
+
Each entity in our system has:
|
|
108
|
+
- A unique ID
|
|
109
|
+
- A type (such as "player," "enemy," or "item")
|
|
110
|
+
- State data (properties like health, position, or inventory)
|
|
111
|
+
- Behaviors (component pieces that define functionality)
|
|
112
|
+
|
|
113
|
+
### The Entity Manager
|
|
114
|
+
|
|
115
|
+
The Entity Manager serves as the central controller for all entities. It:
|
|
116
|
+
- Creates new entities as needed
|
|
117
|
+
- Maintains the collection of all active entities
|
|
118
|
+
- Ensures all entities are updated each frame
|
|
119
|
+
- Handles entity removal when appropriate
|
|
120
|
+
|
|
121
|
+
## Entity Implementation Details
|
|
122
|
+
|
|
123
|
+
### The GameEntity Class
|
|
124
|
+
|
|
125
|
+
Every game object is based on the `GameEntity` class, which provides:
|
|
126
|
+
- The ability to attach different behaviors
|
|
127
|
+
- Event listening and response mechanisms
|
|
128
|
+
- State management
|
|
129
|
+
- Update logic for game loop integration
|
|
130
|
+
|
|
131
|
+
### Behavior Components
|
|
132
|
+
|
|
133
|
+
Behaviors are modular components that provide specific functionality. Each behavior:
|
|
134
|
+
- Has a unique identifier
|
|
135
|
+
- Specifies which events it responds to
|
|
136
|
+
- Contains event response logic
|
|
137
|
+
- May include per-frame update logic
|
|
138
|
+
- Can emit its own events
|
|
139
|
+
|
|
140
|
+
While behaviors are covered in more detail in the [Behaviors Guide](behaviors.md), here's a quick example:
|
|
141
|
+
|
|
142
|
+
#### Example: Implementing a Behavior
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Define a drinking behavior for a character
|
|
146
|
+
class DrinkingBehavior extends GameEntityBehavior {
|
|
147
|
+
name = 'DrinkingBehavior';
|
|
148
|
+
eventSubscriptions = ['beer:nearby', 'alcohol:consumed'];
|
|
149
|
+
|
|
150
|
+
// Handle when beer is nearby
|
|
151
|
+
handleBeerNearby(event: GameEvent, state: any): boolean {
|
|
152
|
+
// Character notices beverage and moves toward it
|
|
153
|
+
this.$emit({
|
|
154
|
+
type: 'character:speak',
|
|
155
|
+
data: { message: "I should check that out!" }
|
|
156
|
+
});
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle when alcohol is consumed
|
|
161
|
+
handleAlcoholConsumed(event: GameEvent, state: any): boolean {
|
|
162
|
+
// Character gains energy when drinking
|
|
163
|
+
state.energyLevel += 25;
|
|
164
|
+
this.$emit({
|
|
165
|
+
type: 'character:speak',
|
|
166
|
+
data: { message: "That's much better!" }
|
|
167
|
+
});
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Main event processor that routes to the right handler
|
|
172
|
+
processEvent(event: GameEvent, state: any): boolean {
|
|
173
|
+
// Use the event type to determine which handler to call
|
|
174
|
+
if (event.type === 'beer:nearby') {
|
|
175
|
+
return this.handleBeerNearby(event, state);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (event.type === 'alcohol:consumed') {
|
|
179
|
+
return this.handleAlcoholConsumed(event, state);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Organizing Event Handling with the Strategy Pattern
|
|
188
|
+
|
|
189
|
+
When behaviors need to handle multiple types of events, the code can quickly become cluttered and hard to read. A helpful approach is to use what programmers call the "Strategy Pattern" - though you can think of it as the "Right Tool for the Right Job" approach.
|
|
190
|
+
|
|
191
|
+
#### How It Works (In Simple Terms)
|
|
192
|
+
|
|
193
|
+
Imagine you're a handyperson with different tools in your toolbox:
|
|
194
|
+
- You have a hammer for nails
|
|
195
|
+
- You have a screwdriver for screws
|
|
196
|
+
- You have pliers for bending wire
|
|
197
|
+
|
|
198
|
+
When you get a task, you first identify what you're working with, then grab the right tool for the job. You don't try to hammer in a screw!
|
|
199
|
+
|
|
200
|
+
In our code, we do something similar:
|
|
201
|
+
1. We create separate methods (tools) for handling each type of event
|
|
202
|
+
2. When an event arrives, we first check what type it is
|
|
203
|
+
3. Then we call the specific method designed to handle that event type
|
|
204
|
+
|
|
205
|
+
#### The Benefits
|
|
206
|
+
|
|
207
|
+
This approach has several advantages:
|
|
208
|
+
- **Cleaner Code**: Each event handler does one job and does it well
|
|
209
|
+
- **Easier to Read**: You can quickly see all the events a behavior handles
|
|
210
|
+
- **Simpler to Maintain**: Need to change how one event is handled? Just update that one method
|
|
211
|
+
- **Better Organization**: Related code stays together, making it easier to understand
|
|
212
|
+
|
|
213
|
+
#### Example: The Strategy Pattern in Action
|
|
214
|
+
|
|
215
|
+
Here's how our previous examples look using this pattern:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
class WeaponBehavior extends GameEntityBehavior {
|
|
219
|
+
name = 'WeaponBehavior';
|
|
220
|
+
eventSubscriptions = ['command:fire', 'command:reload', 'command:inspect'];
|
|
221
|
+
|
|
222
|
+
// Handle firing the weapon
|
|
223
|
+
handleFire(event: GameEvent, state: any): boolean {
|
|
224
|
+
// Broadcast firing event
|
|
225
|
+
this.$emit({
|
|
226
|
+
type: 'weapon:fired',
|
|
227
|
+
data: {
|
|
228
|
+
target: event.data.targetId,
|
|
229
|
+
damage: state.damage
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Handle reloading the weapon
|
|
236
|
+
handleReload(event: GameEvent, state: any): boolean {
|
|
237
|
+
state.ammo = state.maxAmmo;
|
|
238
|
+
this.$emit({
|
|
239
|
+
type: 'weapon:reloaded',
|
|
240
|
+
data: { ammo: state.ammo }
|
|
241
|
+
});
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle inspecting the weapon
|
|
246
|
+
handleInspect(event: GameEvent, state: any): boolean {
|
|
247
|
+
this.$emit({
|
|
248
|
+
type: 'character:speak',
|
|
249
|
+
data: { message: `This ${state.weaponName} looks well-maintained.` }
|
|
250
|
+
});
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Main event processor - routes to the right handler
|
|
255
|
+
processEvent(event: GameEvent, state: any): boolean {
|
|
256
|
+
// Map event types to their handlers
|
|
257
|
+
const handlers = {
|
|
258
|
+
'command:fire': this.handleFire,
|
|
259
|
+
'command:reload': this.handleReload,
|
|
260
|
+
'command:inspect': this.handleInspect
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// If we have a handler for this event type, call it
|
|
264
|
+
if (handlers[event.type]) {
|
|
265
|
+
return handlers[event.type].call(this, event, state);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// No handler found
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Entity Creation Process
|
|
275
|
+
|
|
276
|
+
To create an entity for your game:
|
|
277
|
+
|
|
278
|
+
1. Define an entity blueprint - specifying type, initial state, and behaviors
|
|
279
|
+
2. Register this definition with the Entity Manager
|
|
280
|
+
3. Request the Entity Manager to create instances as needed
|
|
281
|
+
|
|
282
|
+
Here's an example for creating a weapon:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// Define a weapon
|
|
286
|
+
const energySwordDefinition = {
|
|
287
|
+
type: 'weapon:energySword',
|
|
288
|
+
defaultState: {
|
|
289
|
+
color: 'blue',
|
|
290
|
+
isActive: false,
|
|
291
|
+
damage: 50,
|
|
292
|
+
owner: null
|
|
293
|
+
},
|
|
294
|
+
behaviors: [
|
|
295
|
+
GlowingBehavior,
|
|
296
|
+
SoundEffectBehavior,
|
|
297
|
+
DamageBehavior
|
|
298
|
+
]
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// Register the definition
|
|
302
|
+
entityManager.registerEntityDefinition(energySwordDefinition);
|
|
303
|
+
|
|
304
|
+
// Later, create the weapon with additional initial state
|
|
305
|
+
const playerSword = entityManager.createEntity('weapon:energySword', { color: 'green' });
|
|
306
|
+
playerSword.state.color = 'green'; // Customize the instance
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Communication Between Entities
|
|
310
|
+
|
|
311
|
+
Since our entities are designed to be independent, they need reliable communication methods:
|
|
312
|
+
|
|
313
|
+
### 1. Event Broadcasting
|
|
314
|
+
|
|
315
|
+
The primary communication method uses the event system:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
class WeaponBehavior extends GameEntityBehavior {
|
|
319
|
+
name = 'WeaponBehavior';
|
|
320
|
+
eventSubscriptions = ['command:fire'];
|
|
321
|
+
|
|
322
|
+
processEvent(event: GameEvent, state: any): boolean {
|
|
323
|
+
if (event.type === 'command:fire') {
|
|
324
|
+
// Broadcast firing event
|
|
325
|
+
this.$emit({
|
|
326
|
+
type: 'weapon:fired',
|
|
327
|
+
data: {
|
|
328
|
+
target: event.data.targetId,
|
|
329
|
+
damage: state.damage
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// In another entity's behavior
|
|
339
|
+
class DamageReceiverBehavior extends GameEntityBehavior {
|
|
340
|
+
name = 'DamageReceiverBehavior';
|
|
341
|
+
eventSubscriptions = ['weapon:fired', 'healing:applied'];
|
|
342
|
+
|
|
343
|
+
// Handle taking damage
|
|
344
|
+
handleWeaponFired(event: GameEvent, state: any): boolean {
|
|
345
|
+
// Only process if we're the target
|
|
346
|
+
if (event.data.target === this.entity.id) {
|
|
347
|
+
// Take damage
|
|
348
|
+
state.health -= event.data.damage;
|
|
349
|
+
|
|
350
|
+
// Emit damaged event
|
|
351
|
+
this.$emit({
|
|
352
|
+
type: 'character:damaged',
|
|
353
|
+
data: { amount: event.data.damage }
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Check if health is depleted
|
|
357
|
+
if (state.health <= 0) {
|
|
358
|
+
this.$emit({
|
|
359
|
+
type: 'character:died',
|
|
360
|
+
data: { cause: 'weapon' }
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Handle receiving healing
|
|
370
|
+
handleHealingApplied(event: GameEvent, state: any): boolean {
|
|
371
|
+
// Only process if we're the target
|
|
372
|
+
if (event.data.target === this.entity.id) {
|
|
373
|
+
// Apply healing
|
|
374
|
+
const oldHealth = state.health;
|
|
375
|
+
state.health = Math.min(state.maxHealth, state.health + event.data.amount);
|
|
376
|
+
|
|
377
|
+
// Emit healed event
|
|
378
|
+
this.$emit({
|
|
379
|
+
type: 'character:healed',
|
|
380
|
+
data: { amount: state.health - oldHealth }
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Dispatch to the correct handler based on event type
|
|
389
|
+
processEvent(event: GameEvent, state: any): boolean {
|
|
390
|
+
if (event.type === 'weapon:fired') {
|
|
391
|
+
return this.handleWeaponFired(event, state);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (event.type === 'healing:applied') {
|
|
395
|
+
return this.handleHealingApplied(event, state);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### 2. Entity Queries
|
|
404
|
+
|
|
405
|
+
Sometimes an entity needs to find specific other entities:
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
class ReproductionBehavior extends GameEntityBehavior {
|
|
409
|
+
name = 'ReproductionBehavior';
|
|
410
|
+
|
|
411
|
+
update(dt: number, state: any): void {
|
|
412
|
+
// Check if there are resources nearby
|
|
413
|
+
const entityManager = sage.managers.entityManager;
|
|
414
|
+
|
|
415
|
+
// Look for food in the area
|
|
416
|
+
for (const entity of entityManager.entities.values()) {
|
|
417
|
+
if (entity.type === 'item:food' && this.isNearby(entity, state)) {
|
|
418
|
+
// Found food! Time to reproduce
|
|
419
|
+
state.reproductionTimer = 0;
|
|
420
|
+
|
|
421
|
+
// Create a new entity
|
|
422
|
+
if (state.canReproduce) {
|
|
423
|
+
entityManager.createEntity(this.entity.type, {
|
|
424
|
+
position: { ...state.position }
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
isNearby(entity, state) {
|
|
433
|
+
// Check proximity logic
|
|
434
|
+
// Implementation details...
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### 3. Direct Entity References
|
|
441
|
+
|
|
442
|
+
For established relationships between entities:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
class ControllerBehavior extends GameEntityBehavior {
|
|
446
|
+
name = 'ControllerBehavior';
|
|
447
|
+
eventSubscriptions = ['controller:command'];
|
|
448
|
+
|
|
449
|
+
processEvent(event: GameEvent, state: any): boolean {
|
|
450
|
+
if (event.type === 'controller:command') {
|
|
451
|
+
const entityManager = sage.managers.entityManager;
|
|
452
|
+
|
|
453
|
+
// Manage controlled entities
|
|
454
|
+
if (!state.controlledEntities) {
|
|
455
|
+
state.controlledEntities = [];
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Find entities to control
|
|
459
|
+
for (const entity of entityManager.entities.values()) {
|
|
460
|
+
if (entity.type === 'unit' && this.canControl(entity)) {
|
|
461
|
+
// Add to controlled collection
|
|
462
|
+
state.controlledEntities.push(entity.id);
|
|
463
|
+
|
|
464
|
+
// Set entity to controlled state
|
|
465
|
+
entity.state.controlled = true;
|
|
466
|
+
entity.state.controllerId = this.entity.id;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
update(dt: number, state: any): void {
|
|
475
|
+
// Manage controlled entities
|
|
476
|
+
if (state.controlledEntities && state.controlledEntities.length > 0) {
|
|
477
|
+
const entityManager = sage.managers.entityManager;
|
|
478
|
+
|
|
479
|
+
// Issue commands to controlled units
|
|
480
|
+
for (const unitId of state.controlledEntities) {
|
|
481
|
+
const unit = entityManager.getEntity(unitId);
|
|
482
|
+
if (unit) {
|
|
483
|
+
// Command implementation
|
|
484
|
+
// Details omitted...
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
canControl(entity) {
|
|
491
|
+
// Control logic
|
|
492
|
+
// Implementation details...
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Best Practices for Entity Design
|
|
499
|
+
|
|
500
|
+
1. **Single-Responsibility Behaviors**: Each behavior should handle one aspect of functionality well.
|
|
501
|
+
|
|
502
|
+
2. **Minimize Behavior Interdependencies**: Design behaviors that can operate independently of other specific behaviors when possible.
|
|
503
|
+
|
|
504
|
+
3. **Use Events for Communication**: Instead of direct references between entities, use the event system to maintain loose coupling.
|
|
505
|
+
|
|
506
|
+
4. **Modular Construction**: Build complex entities by combining simpler, reusable behaviors.
|
|
507
|
+
|
|
508
|
+
5. **Test Behaviors Individually**: Validate behavior functionality in isolation before integration.
|
|
509
|
+
|
|
510
|
+
6. **Document Behavior Needs**: Clearly document what state properties each behavior expects.
|
|
511
|
+
|
|
512
|
+
7. **Consider Behavior Order**: Remember that the order behaviors are attached affects event processing priority.
|
|
513
|
+
|
|
514
|
+
8. **Use the Strategy Pattern**: For behaviors that handle multiple event types, use separate methods for each type to keep your code organized.
|
|
515
|
+
|
|
516
|
+
For detailed guidance on behavior implementation, best practices, and patterns, see the [Behaviors Guide](behaviors.md).
|
|
517
|
+
|
|
518
|
+
## Performance Considerations
|
|
519
|
+
|
|
520
|
+
- Monitor entities subscribing to high-frequency events
|
|
521
|
+
- Batch similar updates when possible
|
|
522
|
+
- Implement entity pooling for frequently created/destroyed objects
|
|
523
|
+
- Use efficient entity queries and filtering
|
|
524
|
+
|
|
525
|
+
## Debugging Tools
|
|
526
|
+
|
|
527
|
+
- Create visualization behaviors for development
|
|
528
|
+
- Implement event logging for tracking communication
|
|
529
|
+
- Add runtime inspection tools for entities
|
|
530
|
+
|
|
531
|
+
## Summary
|
|
532
|
+
|
|
533
|
+
Our entity system provides a modular approach to game object creation. By focusing on small, reusable behaviors and communication through events, you can create games that are easier to build, modify, and maintain. This architecture supports projects of varying complexity while providing the flexibility needed to implement diverse gameplay mechanics.
|
|
534
|
+
|
|
535
|
+
For the next steps in your learning journey:
|
|
536
|
+
- Dive deeper into [Behaviors Guide](behaviors.md) to master behavior creation
|
|
537
|
+
- Learn about communication with the [Event Bus Guide](eventbus.md)
|
|
538
|
+
- Explore the [Scene System Guide](scene_system.md) for managing game worlds
|