@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,513 @@
|
|
|
1
|
+
# Scene System Guide
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
The SAGE Scene System provides the visual foundation for your game, creating and managing the 3D environments that players will experience. Powered by BabylonJS, one of the most powerful web-based 3D engines available, the Scene System handles everything from rendering and lighting to camera control and visual effects.
|
|
6
|
+
|
|
7
|
+
## Core Concepts
|
|
8
|
+
|
|
9
|
+
In SAGE, a scene represents a complete 3D environment with its own:
|
|
10
|
+
- Geometry (meshes)
|
|
11
|
+
- Lighting
|
|
12
|
+
- Cameras
|
|
13
|
+
- Materials
|
|
14
|
+
- Physics simulation
|
|
15
|
+
|
|
16
|
+
The scene is where your entity system's abstract data gets translated into visuals that players can see and interact with.
|
|
17
|
+
|
|
18
|
+
## The SceneEngine
|
|
19
|
+
|
|
20
|
+
The core of SAGE's visual system is the `SceneEngine` class, which manages scene creation, loading, and integration with the rest of the engine:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { SceneEngine } from '@skewedaspect/sage';
|
|
24
|
+
|
|
25
|
+
// The SceneEngine is created automatically when you initialize the game engine
|
|
26
|
+
const sceneEngine = gameEngine.engines.sceneEngine;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Creating Your First Scene
|
|
30
|
+
|
|
31
|
+
Let's walk through creating a basic 3D scene for your game:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// This example shows how to use the SceneEngine directly
|
|
35
|
+
// In most cases, this would be handled by your Game Manager
|
|
36
|
+
|
|
37
|
+
async function createGameWorld(canvas) {
|
|
38
|
+
// Create a scene
|
|
39
|
+
const scene = await gameEngine.engines.sceneEngine.loadScene(canvas);
|
|
40
|
+
|
|
41
|
+
// At this point, you have a basic scene with:
|
|
42
|
+
// - A default camera
|
|
43
|
+
// - Default lighting
|
|
44
|
+
// - Physics enabled
|
|
45
|
+
|
|
46
|
+
// You can customize it further...
|
|
47
|
+
|
|
48
|
+
return scene;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Scene Components
|
|
53
|
+
|
|
54
|
+
### Cameras
|
|
55
|
+
|
|
56
|
+
Cameras define the player's view into your 3D world. SAGE uses BabylonJS cameras:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { FreeCamera, Vector3 } from '@babylonjs/core';
|
|
60
|
+
|
|
61
|
+
// Create a free-flying camera
|
|
62
|
+
const camera = new FreeCamera('playerCamera', new Vector3(0, 5, -10), scene);
|
|
63
|
+
|
|
64
|
+
// Point the camera at the origin
|
|
65
|
+
camera.setTarget(Vector3.Zero());
|
|
66
|
+
|
|
67
|
+
// Allow the player to control the camera with mouse/keyboard
|
|
68
|
+
camera.attachControl(canvas, true);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Different camera types offer different gameplay experiences:
|
|
72
|
+
- `FreeCamera`: First-person perspective with 6 degrees of freedom
|
|
73
|
+
- `ArcRotateCamera`: Orbits around a target point (great for third-person games)
|
|
74
|
+
- `FollowCamera`: Follows a specific mesh (like a character)
|
|
75
|
+
|
|
76
|
+
### Lighting
|
|
77
|
+
|
|
78
|
+
Proper lighting is crucial for creating atmosphere in your game:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { HemisphericLight, DirectionalLight, Vector3 } from '@babylonjs/core';
|
|
82
|
+
|
|
83
|
+
// Create ambient lighting from above
|
|
84
|
+
const ambientLight = new HemisphericLight(
|
|
85
|
+
'ambientLight',
|
|
86
|
+
new Vector3(0, 1, 0),
|
|
87
|
+
scene
|
|
88
|
+
);
|
|
89
|
+
ambientLight.intensity = 0.4;
|
|
90
|
+
|
|
91
|
+
// Add directional sunlight
|
|
92
|
+
const sunlight = new DirectionalLight(
|
|
93
|
+
'sunlight',
|
|
94
|
+
new Vector3(0.5, -0.6, 0.5),
|
|
95
|
+
scene
|
|
96
|
+
);
|
|
97
|
+
sunlight.intensity = 0.7;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Adding Objects
|
|
101
|
+
|
|
102
|
+
You can add 3D objects to your scene in several ways:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { MeshBuilder, StandardMaterial, Color3 } from '@babylonjs/core';
|
|
106
|
+
|
|
107
|
+
// Create a simple sphere
|
|
108
|
+
const sphere = MeshBuilder.CreateSphere('sphere', {
|
|
109
|
+
diameter: 2,
|
|
110
|
+
segments: 32
|
|
111
|
+
}, scene);
|
|
112
|
+
|
|
113
|
+
// Position it above the ground
|
|
114
|
+
sphere.position.y = 1;
|
|
115
|
+
|
|
116
|
+
// Create and apply a material
|
|
117
|
+
const material = new StandardMaterial('sphereMaterial', scene);
|
|
118
|
+
material.diffuseColor = new Color3(0.2, 0.4, 0.8);
|
|
119
|
+
material.specularColor = new Color3(0.3, 0.3, 0.3);
|
|
120
|
+
sphere.material = material;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Connecting Entities to Scene Objects
|
|
124
|
+
|
|
125
|
+
A key aspect of SAGE is connecting your entity system to the visual representation. Here's a pattern to achieve this:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// A behavior that connects an entity to a 3D model
|
|
129
|
+
class VisualRepresentationBehavior extends GameEntityBehavior<{
|
|
130
|
+
position: { x: number, y: number, z: number },
|
|
131
|
+
modelType: string,
|
|
132
|
+
mesh?: any
|
|
133
|
+
}> {
|
|
134
|
+
name = 'VisualRepresentationBehavior';
|
|
135
|
+
eventSubscriptions = ['entity:moved', 'scene:loaded'];
|
|
136
|
+
|
|
137
|
+
processEvent(event: GameEvent, state: any): boolean {
|
|
138
|
+
if (event.type === 'scene:loaded') {
|
|
139
|
+
// Create the visual representation when the scene is ready
|
|
140
|
+
this.createMesh(event.payload.scene, state);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (event.type === 'entity:moved') {
|
|
145
|
+
// Update the mesh position when the entity moves
|
|
146
|
+
if (state.mesh) {
|
|
147
|
+
state.mesh.position.x = state.position.x;
|
|
148
|
+
state.mesh.position.y = state.position.y;
|
|
149
|
+
state.mesh.position.z = state.position.z;
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
createMesh(scene, state) {
|
|
158
|
+
// Create different mesh types based on modelType
|
|
159
|
+
if (state.modelType === 'character') {
|
|
160
|
+
state.mesh = MeshBuilder.CreateCapsule(
|
|
161
|
+
'character',
|
|
162
|
+
{ radius: 0.5, height: 2 },
|
|
163
|
+
scene
|
|
164
|
+
);
|
|
165
|
+
} else if (state.modelType === 'box') {
|
|
166
|
+
state.mesh = MeshBuilder.CreateBox(
|
|
167
|
+
'box',
|
|
168
|
+
{ size: 1 },
|
|
169
|
+
scene
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Set initial position
|
|
174
|
+
if (state.mesh) {
|
|
175
|
+
state.mesh.position.x = state.position.x;
|
|
176
|
+
state.mesh.position.y = state.position.y;
|
|
177
|
+
state.mesh.position.z = state.position.z;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Physics Integration
|
|
184
|
+
|
|
185
|
+
SAGE uses Havok Physics (via BabylonJS) for realistic physical interactions:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { PhysicsAggregate, PhysicsShapeType } from '@babylonjs/core';
|
|
189
|
+
|
|
190
|
+
// Create a physical ground plane
|
|
191
|
+
const ground = MeshBuilder.CreateGround('ground', {
|
|
192
|
+
width: 50,
|
|
193
|
+
height: 50
|
|
194
|
+
}, scene);
|
|
195
|
+
|
|
196
|
+
// Make the ground a static physics object
|
|
197
|
+
const groundAggregate = new PhysicsAggregate(
|
|
198
|
+
ground,
|
|
199
|
+
PhysicsShapeType.BOX,
|
|
200
|
+
{ mass: 0 }, // Mass of 0 means static (immovable)
|
|
201
|
+
scene
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Create a dynamic physics object (that will fall and bounce)
|
|
205
|
+
const ball = MeshBuilder.CreateSphere('ball', {
|
|
206
|
+
diameter: 1
|
|
207
|
+
}, scene);
|
|
208
|
+
ball.position.y = 10; // Start above the ground
|
|
209
|
+
|
|
210
|
+
const ballAggregate = new PhysicsAggregate(
|
|
211
|
+
ball,
|
|
212
|
+
PhysicsShapeType.SPHERE,
|
|
213
|
+
{
|
|
214
|
+
mass: 1, // Mass affects how it responds to forces
|
|
215
|
+
restitution: 0.7 // Bounciness (0-1)
|
|
216
|
+
},
|
|
217
|
+
scene
|
|
218
|
+
);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Scenes and Game States
|
|
222
|
+
|
|
223
|
+
Different game states often require different scenes. Here's a pattern for managing multiple scenes:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// A simplified scene manager
|
|
227
|
+
class GameSceneManager {
|
|
228
|
+
private scenes: Map<string, Scene> = new Map();
|
|
229
|
+
private activeScene: Scene | null = null;
|
|
230
|
+
|
|
231
|
+
constructor(private gameEngine: SkewedAspectGameEngine) {}
|
|
232
|
+
|
|
233
|
+
async loadScene(sceneName: string): Promise<Scene> {
|
|
234
|
+
// Check if we've already loaded this scene
|
|
235
|
+
if (this.scenes.has(sceneName)) {
|
|
236
|
+
return this.scenes.get(sceneName)!;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Otherwise load the scene
|
|
240
|
+
const scene = await this.createScene(sceneName);
|
|
241
|
+
this.scenes.set(sceneName, scene);
|
|
242
|
+
return scene;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async setActiveScene(sceneName: string): Promise<void> {
|
|
246
|
+
// Load the scene if needed
|
|
247
|
+
const scene = await this.loadScene(sceneName);
|
|
248
|
+
|
|
249
|
+
// Set as active
|
|
250
|
+
this.activeScene = scene;
|
|
251
|
+
|
|
252
|
+
// Notify interested systems about the scene change
|
|
253
|
+
this.gameEngine.eventBus.publish({
|
|
254
|
+
type: 'scene:changed',
|
|
255
|
+
payload: {
|
|
256
|
+
scene,
|
|
257
|
+
name: sceneName
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private async createScene(sceneName: string): Promise<Scene> {
|
|
263
|
+
const canvas = this.gameEngine.canvas;
|
|
264
|
+
|
|
265
|
+
// Load different scenes based on name
|
|
266
|
+
if (sceneName === 'mainMenu') {
|
|
267
|
+
// Create a simple menu scene
|
|
268
|
+
return this.createMenuScene(canvas);
|
|
269
|
+
} else if (sceneName === 'level1') {
|
|
270
|
+
// Create the first level
|
|
271
|
+
return this.createLevel1Scene(canvas);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Default scene
|
|
275
|
+
return this.gameEngine.engines.sceneEngine.loadScene(canvas);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Custom scene creation methods...
|
|
279
|
+
private createMenuScene(canvas) { /* ... */ }
|
|
280
|
+
private createLevel1Scene(canvas) { /* ... */ }
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Advanced Scene Techniques
|
|
285
|
+
|
|
286
|
+
### Asset Loading
|
|
287
|
+
|
|
288
|
+
For larger games, you'll want to load meshes from external files:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { SceneLoader } from '@babylonjs/core';
|
|
292
|
+
import '@babylonjs/loaders'; // Import the loaders
|
|
293
|
+
|
|
294
|
+
// Load a model asynchronously
|
|
295
|
+
const loadModelAsync = async (scene, filename, folderPath) => {
|
|
296
|
+
const result = await SceneLoader.ImportMeshAsync(
|
|
297
|
+
'', // Load all meshes in the file
|
|
298
|
+
folderPath,
|
|
299
|
+
filename,
|
|
300
|
+
scene
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
return result.meshes;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Example usage
|
|
307
|
+
const characterMeshes = await loadModelAsync(
|
|
308
|
+
scene,
|
|
309
|
+
'character.glb',
|
|
310
|
+
'assets/models/'
|
|
311
|
+
);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Performance Optimization
|
|
315
|
+
|
|
316
|
+
For better performance in complex scenes:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// Create a level of detail (LOD) system
|
|
320
|
+
const createLODMesh = (scene, position) => {
|
|
321
|
+
// High detail (close distance)
|
|
322
|
+
const highDetail = MeshBuilder.CreateSphere('highDetail', {
|
|
323
|
+
diameter: 2,
|
|
324
|
+
segments: 32
|
|
325
|
+
}, scene);
|
|
326
|
+
|
|
327
|
+
// Medium detail (medium distance)
|
|
328
|
+
const mediumDetail = MeshBuilder.CreateSphere('mediumDetail', {
|
|
329
|
+
diameter: 2,
|
|
330
|
+
segments: 16
|
|
331
|
+
}, scene);
|
|
332
|
+
|
|
333
|
+
// Low detail (far distance)
|
|
334
|
+
const lowDetail = MeshBuilder.CreateSphere('lowDetail', {
|
|
335
|
+
diameter: 2,
|
|
336
|
+
segments: 8
|
|
337
|
+
}, scene);
|
|
338
|
+
|
|
339
|
+
// Set up LOD
|
|
340
|
+
highDetail.addLODLevel(30, mediumDetail);
|
|
341
|
+
mediumDetail.addLODLevel(60, lowDetail);
|
|
342
|
+
|
|
343
|
+
// Position
|
|
344
|
+
highDetail.position = position;
|
|
345
|
+
|
|
346
|
+
return highDetail; // Return the highest detail mesh
|
|
347
|
+
};
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Special Effects
|
|
351
|
+
|
|
352
|
+
Add visual flair with particle systems:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { ParticleSystem, Color4, Vector3 } from '@babylonjs/core';
|
|
356
|
+
|
|
357
|
+
// Create a fire effect
|
|
358
|
+
const createFireEffect = (scene, position) => {
|
|
359
|
+
const particleSystem = new ParticleSystem('fire', 2000, scene);
|
|
360
|
+
|
|
361
|
+
// Particle appearance
|
|
362
|
+
particleSystem.particleTexture = new Texture('assets/textures/flame.png', scene);
|
|
363
|
+
|
|
364
|
+
// Emitter shape and location
|
|
365
|
+
particleSystem.emitter = position;
|
|
366
|
+
particleSystem.minEmitBox = new Vector3(-0.2, 0, -0.2);
|
|
367
|
+
particleSystem.maxEmitBox = new Vector3(0.2, 0, 0.2);
|
|
368
|
+
|
|
369
|
+
// Colors (from core to outer flames)
|
|
370
|
+
particleSystem.color1 = new Color4(1, 0.9, 0.3, 1.0);
|
|
371
|
+
particleSystem.color2 = new Color4(1, 0.5, 0.2, 1.0);
|
|
372
|
+
particleSystem.colorDead = new Color4(0.1, 0.1, 0.1, 0.0);
|
|
373
|
+
|
|
374
|
+
// Sizes and lifetime
|
|
375
|
+
particleSystem.minSize = 0.3;
|
|
376
|
+
particleSystem.maxSize = 1.5;
|
|
377
|
+
particleSystem.minLifeTime = 0.2;
|
|
378
|
+
particleSystem.maxLifeTime = 0.8;
|
|
379
|
+
|
|
380
|
+
// Emission rate
|
|
381
|
+
particleSystem.emitRate = 500;
|
|
382
|
+
|
|
383
|
+
// Direction and velocity
|
|
384
|
+
particleSystem.direction1 = new Vector3(-0.5, 4, -0.5);
|
|
385
|
+
particleSystem.direction2 = new Vector3(0.5, 4, 0.5);
|
|
386
|
+
particleSystem.minEmitPower = 1;
|
|
387
|
+
particleSystem.maxEmitPower = 3;
|
|
388
|
+
|
|
389
|
+
// Start the particle system
|
|
390
|
+
particleSystem.start();
|
|
391
|
+
|
|
392
|
+
return particleSystem;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Example usage - create fire at a campsite
|
|
396
|
+
const campfirePosition = new Vector3(5, 0, 10);
|
|
397
|
+
const fireEffect = createFireEffect(scene, campfirePosition);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Integration with Game Logic
|
|
401
|
+
|
|
402
|
+
A key aspect of the SAGE architecture is connecting your scenes to your game logic:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// A game component that manages scene transitions
|
|
406
|
+
class LevelManager {
|
|
407
|
+
constructor(private gameEngine) {}
|
|
408
|
+
|
|
409
|
+
async loadLevel(levelNumber) {
|
|
410
|
+
// 1. Load the scene first
|
|
411
|
+
const levelScene = await this.gameEngine.engines.sceneEngine.loadScene(
|
|
412
|
+
this.gameEngine.canvas
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
// 2. Configure the environment
|
|
416
|
+
this.setupEnvironment(levelScene, levelNumber);
|
|
417
|
+
|
|
418
|
+
// 3. Create entities for this level
|
|
419
|
+
this.spawnEntities(levelNumber);
|
|
420
|
+
|
|
421
|
+
// 4. Notify other systems that level is ready
|
|
422
|
+
this.gameEngine.eventBus.publish({
|
|
423
|
+
type: 'level:loaded',
|
|
424
|
+
payload: {
|
|
425
|
+
levelNumber,
|
|
426
|
+
scene: levelScene
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return levelScene;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private setupEnvironment(scene, levelNumber) {
|
|
434
|
+
// Configure different environments based on level
|
|
435
|
+
if (levelNumber === 1) {
|
|
436
|
+
// Forest environment
|
|
437
|
+
scene.fogEnabled = true;
|
|
438
|
+
scene.fogColor = new Color3(0.8, 0.9, 0.8);
|
|
439
|
+
scene.fogDensity = 0.01;
|
|
440
|
+
} else if (levelNumber === 2) {
|
|
441
|
+
// Desert environment
|
|
442
|
+
scene.fogEnabled = true;
|
|
443
|
+
scene.fogColor = new Color3(0.9, 0.8, 0.6);
|
|
444
|
+
scene.fogDensity = 0.05;
|
|
445
|
+
scene.clearColor = new Color4(0.9, 0.8, 0.6, 1);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Add appropriate lighting...
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private spawnEntities(levelNumber) {
|
|
452
|
+
// Get entity definitions for this level
|
|
453
|
+
const entityDefinitions = this.getLevelEntityDefinitions(levelNumber);
|
|
454
|
+
|
|
455
|
+
// Create all entities
|
|
456
|
+
for (const def of entityDefinitions) {
|
|
457
|
+
this.gameEngine.managers.entityManager.registerEntityDefinition(def);
|
|
458
|
+
// Updated example to reflect 'defaultState' and 'initialState' merging
|
|
459
|
+
const entity = gameEngine.managers.entityManager.createEntity(def.type, { position: { x: 10, y: 0, z: 5 } });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private getLevelEntityDefinitions(levelNumber) {
|
|
464
|
+
// Return different entities based on level
|
|
465
|
+
if (levelNumber === 1) {
|
|
466
|
+
return [
|
|
467
|
+
// Level 1 entities (forest creatures, terrain, etc.)
|
|
468
|
+
{
|
|
469
|
+
type: 'creature:elf',
|
|
470
|
+
initialState: { /* ... */ },
|
|
471
|
+
behaviors: [ /* ... */ ]
|
|
472
|
+
},
|
|
473
|
+
// More entities...
|
|
474
|
+
];
|
|
475
|
+
}
|
|
476
|
+
// More levels...
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Best Practices
|
|
482
|
+
|
|
483
|
+
1. **Separate Visual and Logic Concerns**: Use behaviors to bridge between entity state and visual representation
|
|
484
|
+
2. **Use Asset Management**: Load assets efficiently and consider preloading key assets
|
|
485
|
+
3. **Optimize for Performance**: Use techniques like LOD, frustum culling, and scene partitioning
|
|
486
|
+
4. **Maintain a Consistent Scale**: Establish and stick to a consistent unit scale (e.g., 1 unit = 1 meter)
|
|
487
|
+
5. **Scene Transitions**: Handle scene transitions smoothly with loading screens or fades
|
|
488
|
+
|
|
489
|
+
## Debugging Tools
|
|
490
|
+
|
|
491
|
+
BabylonJS provides excellent debugging tools:
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
import { Inspector } from '@babylonjs/inspector';
|
|
495
|
+
|
|
496
|
+
// Show the inspector with F12
|
|
497
|
+
scene.debugLayer.isVisible = false;
|
|
498
|
+
window.addEventListener('keydown', (e) => {
|
|
499
|
+
if (e.key === 'F12') {
|
|
500
|
+
if (scene.debugLayer.isVisible) {
|
|
501
|
+
scene.debugLayer.hide();
|
|
502
|
+
} else {
|
|
503
|
+
scene.debugLayer.show();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Conclusion
|
|
510
|
+
|
|
511
|
+
The Scene System is your window into the game world. It translates abstract entity data into visual elements that players can see and interact with. By understanding how to create, customize, and optimize scenes, you can create rich, immersive game environments for players to explore.
|
|
512
|
+
|
|
513
|
+
Together with the Entity System and Event Bus, the Scene System forms a powerful foundation for building any kind of game you can imagine, from simple 2D puzzles to complex 3D adventures. May your polygons be plentiful and your frame rates high!
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skewedaspect/sage",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"main": "dist/sage.umd.js",
|
|
5
|
+
"module": "dist/sage.es.js",
|
|
6
|
+
"types": "dist/sage.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"build:ts": "tsc",
|
|
11
|
+
"dev": "vite",
|
|
12
|
+
"test": "mocha 'test/**/*.spec.ts'",
|
|
13
|
+
"test:bench": "node ./benchmark/eventbus.bench.ts",
|
|
14
|
+
"lint": "eslint \"{src,test,benchmark}/**/*.{ts,js,vue}\" --max-warnings=0 --no-warn-ignored",
|
|
15
|
+
"lint:staged": "lint-staged",
|
|
16
|
+
"prepare": "if-env NODE_ENV=production && exit 0 || husky install",
|
|
17
|
+
"release": "npm run release:patch",
|
|
18
|
+
"release:patch": "npm version patch && git push --follow-tags && npm publish",
|
|
19
|
+
"release:minor": "npm version minor && git push --follow-tags && npm publish",
|
|
20
|
+
"release:major": "npm version major && git push --follow-tags && npm publish",
|
|
21
|
+
"release:prerelease": "npm version prerelease && git push --follow-tags && npm publish",
|
|
22
|
+
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"docs",
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [],
|
|
30
|
+
"author": "Christopher S. Case<chris.case@g33xnexus.com>",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"description": "",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^9.24.0",
|
|
35
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
36
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
37
|
+
"@stylistic/eslint-plugin": "^4.2.0",
|
|
38
|
+
"@types/chai": "^5.2.1",
|
|
39
|
+
"@types/eslint__js": "^8.42.3",
|
|
40
|
+
"@types/mocha": "^10.0.10",
|
|
41
|
+
"@types/node": "^22.14.0",
|
|
42
|
+
"@types/sinon": "^17.0.4",
|
|
43
|
+
"chai": "^5.2.0",
|
|
44
|
+
"conventional-changelog": "^6.0.0",
|
|
45
|
+
"conventional-changelog-cli": "^4.1.0",
|
|
46
|
+
"eslint": "^9.24.0",
|
|
47
|
+
"eslint-formatter-junit": "^8.40.0",
|
|
48
|
+
"husky": "^8.0.0",
|
|
49
|
+
"if-env": "^1.0.4",
|
|
50
|
+
"jsdom": "^26.0.0",
|
|
51
|
+
"jsdom-global": "^3.0.2",
|
|
52
|
+
"lint-staged": "^15.5.0",
|
|
53
|
+
"mocha": "^11.1.0",
|
|
54
|
+
"sinon": "^20.0.0",
|
|
55
|
+
"tinybench": "^4.0.1",
|
|
56
|
+
"tslib": "^2.8.1",
|
|
57
|
+
"typescript": "^5.8.3",
|
|
58
|
+
"typescript-eslint": "^8.29.0",
|
|
59
|
+
"vite": "^6.2.6",
|
|
60
|
+
"vite-plugin-checker": "^0.9.1"
|
|
61
|
+
},
|
|
62
|
+
"lint-staged": {
|
|
63
|
+
"*.{ts,js}": "npm run lint"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@babylonjs/core": "^8.1.1",
|
|
67
|
+
"@babylonjs/havok": "^1.3.10"
|
|
68
|
+
}
|
|
69
|
+
}
|