@directivegames/genesys.sdk 3.2.2 → 3.2.4
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 +60 -60
- package/dist/src/core/cli.js +22 -22
- package/dist/src/templates/scripts/genesys/genesys-mcp.js +25 -25
- package/dist/src/templates/scripts/genesys/mcp/editor-functions.js +4 -4
- package/dist/src/templates/src/templates/vehicle/src/ui-hints.js +30 -30
- package/package.json +176 -176
- package/scripts/post-install.ts +143 -143
- package/src/asset-pack/.gitattributes +88 -88
- package/src/asset-pack/eslint.config.js +45 -45
- package/src/asset-pack/gitignore +11 -11
- package/src/asset-pack/scripts/postinstall.ts +81 -81
- package/src/asset-pack/tsconfig.json +33 -33
- package/src/templates/.cursor/mcp.json +20 -20
- package/src/templates/.cursorignore +2 -2
- package/src/templates/.gitattributes +88 -88
- package/src/templates/.vscode/settings.json +6 -6
- package/src/templates/AGENTS.md +86 -86
- package/src/templates/README.md +24 -24
- package/src/templates/eslint.config.js +45 -45
- package/src/templates/gitignore +11 -11
- package/src/templates/index.html +34 -34
- package/src/templates/pnpm-lock.yaml +3676 -3676
- package/src/templates/scripts/genesys/build-project.ts +51 -51
- package/src/templates/scripts/genesys/calc-bounding-box.ts +272 -272
- package/src/templates/scripts/genesys/common.ts +46 -46
- package/src/templates/scripts/genesys/const.ts +9 -9
- package/src/templates/scripts/genesys/dev/dump-default-scene.ts +11 -11
- package/src/templates/scripts/genesys/dev/generate-manifest.ts +146 -146
- package/src/templates/scripts/genesys/dev/launcher.ts +46 -46
- package/src/templates/scripts/genesys/dev/storage-provider.ts +229 -229
- package/src/templates/scripts/genesys/dev/update-template-scenes.ts +84 -84
- package/src/templates/scripts/genesys/doc-server.ts +16 -16
- package/src/templates/scripts/genesys/genesys-mcp.ts +526 -526
- package/src/templates/scripts/genesys/mcp/doc-tools.ts +86 -86
- package/src/templates/scripts/genesys/mcp/editor-functions.ts +151 -151
- package/src/templates/scripts/genesys/mcp/editor-tools.ts +73 -73
- package/src/templates/scripts/genesys/mcp/get-scene-state.ts +35 -35
- package/src/templates/scripts/genesys/mcp/run-subprocess.ts +30 -30
- package/src/templates/scripts/genesys/mcp/search-actors.ts +858 -858
- package/src/templates/scripts/genesys/mcp/search-assets.ts +380 -380
- package/src/templates/scripts/genesys/mcp/utils.ts +281 -281
- package/src/templates/scripts/genesys/misc.ts +42 -42
- package/src/templates/scripts/genesys/mock.ts +6 -6
- package/src/templates/scripts/genesys/place-actors.ts +179 -179
- package/src/templates/scripts/genesys/post-install.ts +30 -30
- package/src/templates/scripts/genesys/prefab.schema.json +84 -84
- package/src/templates/scripts/genesys/remove-engine-comments.ts +134 -134
- package/src/templates/scripts/genesys/run-mcp-inspector.bat +4 -4
- package/src/templates/scripts/genesys/storageProvider.ts +182 -182
- package/src/templates/scripts/genesys/validate-prefabs.ts +138 -138
- package/src/templates/src/index.ts +22 -22
- package/src/templates/src/templates/firstPerson/assets/default.genesys-scene +165 -165
- package/src/templates/src/templates/firstPerson/src/game.ts +39 -39
- package/src/templates/src/templates/firstPerson/src/player.ts +63 -63
- package/src/templates/src/templates/fps/assets/default.genesys-scene +9459 -9459
- package/src/templates/src/templates/fps/src/game.ts +39 -39
- package/src/templates/src/templates/fps/src/player.ts +69 -69
- package/src/templates/src/templates/fps/src/weapon.ts +54 -54
- package/src/templates/src/templates/freeCamera/assets/default.genesys-scene +165 -165
- package/src/templates/src/templates/freeCamera/src/game.ts +39 -39
- package/src/templates/src/templates/freeCamera/src/player.ts +45 -45
- package/src/templates/src/templates/sideScroller/assets/default.genesys-scene +121 -121
- package/src/templates/src/templates/sideScroller/src/const.ts +45 -45
- package/src/templates/src/templates/sideScroller/src/game.ts +122 -122
- package/src/templates/src/templates/sideScroller/src/level-generator.ts +361 -361
- package/src/templates/src/templates/sideScroller/src/player.ts +125 -125
- package/src/templates/src/templates/thirdPerson/assets/default.genesys-scene +165 -165
- package/src/templates/src/templates/thirdPerson/src/game.ts +39 -39
- package/src/templates/src/templates/thirdPerson/src/player.ts +61 -61
- package/src/templates/src/templates/vehicle/assets/default.genesys-scene +225 -225
- package/src/templates/src/templates/vehicle/src/base-vehicle.ts +145 -145
- package/src/templates/src/templates/vehicle/src/game.ts +43 -43
- package/src/templates/src/templates/vehicle/src/mesh-vehicle.ts +191 -191
- package/src/templates/src/templates/vehicle/src/player.ts +109 -109
- package/src/templates/src/templates/vehicle/src/primitive-vehicle.ts +266 -266
- package/src/templates/src/templates/vehicle/src/ui-hints.ts +101 -101
- package/src/templates/src/templates/vr-game/assets/default.genesys-scene +246 -246
- package/src/templates/src/templates/vr-game/src/auto-imports.ts +1 -1
- package/src/templates/src/templates/vr-game/src/game.ts +66 -66
- package/src/templates/src/templates/vr-game/src/sample-vr-actor.ts +26 -26
- package/src/templates/tsconfig.json +34 -34
- package/src/templates/vite.config.ts +52 -52
|
@@ -1,361 +1,361 @@
|
|
|
1
|
-
import * as ENGINE from 'genesys.js';
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
|
|
4
|
-
import { COLORS, LEVEL_CONFIG } from './const.js';
|
|
5
|
-
|
|
6
|
-
// ============================================================================
|
|
7
|
-
// LEVEL GENERATOR
|
|
8
|
-
// ============================================================================
|
|
9
|
-
|
|
10
|
-
// Interface for level generator configuration options
|
|
11
|
-
interface LevelGeneratorOptions {
|
|
12
|
-
chunkSize?: number;
|
|
13
|
-
chunksAhead?: number;
|
|
14
|
-
chunksBehind?: number;
|
|
15
|
-
platformHeightRange?: { min: number; max: number };
|
|
16
|
-
platformWidthRange?: { min: number; max: number };
|
|
17
|
-
obstacleHeightRange?: { min: number; max: number };
|
|
18
|
-
chunkMargin?: number;
|
|
19
|
-
minSpacing?: number;
|
|
20
|
-
enableDebugVisualization?: boolean;
|
|
21
|
-
createGroundMesh?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Interface for tracking level chunks
|
|
25
|
-
interface LevelChunk {
|
|
26
|
-
startX: number;
|
|
27
|
-
endX: number;
|
|
28
|
-
actors: ENGINE.Actor[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Interface for positioned elements within chunks
|
|
32
|
-
interface PositionedElement {
|
|
33
|
-
x: number;
|
|
34
|
-
width: number;
|
|
35
|
-
height: number;
|
|
36
|
-
type: 'platform' | 'obstacle';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Simple level generator for infinite side-scrolling levels
|
|
40
|
-
export class SideScrollerLevelGenerator {
|
|
41
|
-
private world: ENGINE.World;
|
|
42
|
-
// Map of chunk index to chunk
|
|
43
|
-
private levelChunks: Map<number, LevelChunk> = new Map();
|
|
44
|
-
|
|
45
|
-
// Configuration
|
|
46
|
-
private chunkSize: number;
|
|
47
|
-
private chunksAhead: number;
|
|
48
|
-
private chunksBehind: number;
|
|
49
|
-
private platformHeightRange: { min: number; max: number };
|
|
50
|
-
private platformWidthRange: { min: number; max: number };
|
|
51
|
-
private obstacleHeightRange: { min: number; max: number };
|
|
52
|
-
private chunkMargin: number;
|
|
53
|
-
private minSpacing: number;
|
|
54
|
-
private debugVisualizationEnabled: boolean = false;
|
|
55
|
-
private createGroundMesh: boolean = false;
|
|
56
|
-
|
|
57
|
-
constructor(world: ENGINE.World, options: LevelGeneratorOptions = {}) {
|
|
58
|
-
this.world = world;
|
|
59
|
-
|
|
60
|
-
// Apply defaults
|
|
61
|
-
this.chunkSize = options.chunkSize ?? LEVEL_CONFIG.CHUNK_SIZE;
|
|
62
|
-
this.chunksAhead = options.chunksAhead ?? LEVEL_CONFIG.CHUNKS_AHEAD;
|
|
63
|
-
this.chunksBehind = options.chunksBehind ?? LEVEL_CONFIG.CHUNKS_BEHIND;
|
|
64
|
-
this.platformHeightRange = options.platformHeightRange ?? LEVEL_CONFIG.PLATFORM_HEIGHT_RANGE;
|
|
65
|
-
this.platformWidthRange = options.platformWidthRange ?? LEVEL_CONFIG.PLATFORM_WIDTH_RANGE;
|
|
66
|
-
this.obstacleHeightRange = options.obstacleHeightRange ?? LEVEL_CONFIG.OBSTACLE_HEIGHT_RANGE;
|
|
67
|
-
this.chunkMargin = options.chunkMargin ?? LEVEL_CONFIG.CHUNK_MARGIN;
|
|
68
|
-
this.minSpacing = options.minSpacing ?? LEVEL_CONFIG.MIN_SPACING;
|
|
69
|
-
this.debugVisualizationEnabled = options.enableDebugVisualization ?? false;
|
|
70
|
-
this.createGroundMesh = options.createGroundMesh ?? false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
public updateLevel(playerX: number): void {
|
|
74
|
-
const playerChunkIndex = this.getChunkIndex(playerX);
|
|
75
|
-
|
|
76
|
-
const validChunkIndices = [];
|
|
77
|
-
for (let i = playerChunkIndex - this.chunksBehind; i <= playerChunkIndex + this.chunksAhead; i++) {
|
|
78
|
-
this.generateChunk(i);
|
|
79
|
-
validChunkIndices.push(i);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
for (const index of this.levelChunks.keys()) {
|
|
83
|
-
if (!validChunkIndices.includes(index)) {
|
|
84
|
-
this.removeChunk(index);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private getChunkIndex(x: number): number {
|
|
90
|
-
return Math.floor(x / this.chunkSize);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private removeChunk(index: number): void {
|
|
94
|
-
const chunk = this.levelChunks.get(index);
|
|
95
|
-
if (chunk) {
|
|
96
|
-
this.world.removeActors(...chunk.actors);
|
|
97
|
-
this.levelChunks.delete(index);
|
|
98
|
-
console.log(`[LevelGenerator] Removed chunk at index ${index}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private generateChunk(index: number): void {
|
|
103
|
-
if (this.levelChunks.has(index)) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const startX = index * this.chunkSize;
|
|
108
|
-
const endX = startX + this.chunkSize;
|
|
109
|
-
const actors: ENGINE.Actor[] = [];
|
|
110
|
-
|
|
111
|
-
// Generate positioned elements for this chunk
|
|
112
|
-
const elements = this.generatePositionedElements(startX);
|
|
113
|
-
|
|
114
|
-
// Create actors for each element
|
|
115
|
-
for (const element of elements) {
|
|
116
|
-
const actor = this.createElementActor(element);
|
|
117
|
-
actors.push(actor);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Add ground mesh if enabled
|
|
121
|
-
if (this.createGroundMesh) {
|
|
122
|
-
const groundActor = this.createGroundMeshActor(startX);
|
|
123
|
-
actors.push(groundActor);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Add debug visualization if enabled
|
|
127
|
-
if (this.debugVisualizationEnabled) {
|
|
128
|
-
const debugActor = this.createChunkDebugVisualization(startX);
|
|
129
|
-
actors.push(debugActor);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Add all actors to the world
|
|
133
|
-
this.world.addActors(...actors);
|
|
134
|
-
|
|
135
|
-
// Track this chunk
|
|
136
|
-
const chunk: LevelChunk = {
|
|
137
|
-
startX,
|
|
138
|
-
endX,
|
|
139
|
-
actors,
|
|
140
|
-
};
|
|
141
|
-
console.log(`[LevelGenerator] Generated chunk at index ${index}`);
|
|
142
|
-
this.levelChunks.set(index, chunk);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private generatePositionedElements(startX: number): PositionedElement[] {
|
|
146
|
-
const elements: PositionedElement[] = [];
|
|
147
|
-
const usableWidth = this.chunkSize - (this.chunkMargin * 2); // Leave margin on each side
|
|
148
|
-
const usableStartX = startX + this.chunkMargin;
|
|
149
|
-
|
|
150
|
-
// Simple alternating generation: platform -> obstacle -> platform -> obstacle...
|
|
151
|
-
const totalElements = Math.floor(usableWidth / this.minSpacing); // How many elements can fit
|
|
152
|
-
|
|
153
|
-
let currentX = usableStartX;
|
|
154
|
-
let isPlatform = true; // Start with platform
|
|
155
|
-
|
|
156
|
-
for (let i = 0; i < totalElements && currentX < (startX + this.chunkSize - this.chunkMargin); i++) {
|
|
157
|
-
if (isPlatform) {
|
|
158
|
-
// Generate platform with random dimensions
|
|
159
|
-
const width = this.platformWidthRange.min +
|
|
160
|
-
Math.random() * (this.platformWidthRange.max - this.platformWidthRange.min);
|
|
161
|
-
const height = this.platformHeightRange.min +
|
|
162
|
-
Math.random() * (this.platformHeightRange.max - this.platformHeightRange.min);
|
|
163
|
-
|
|
164
|
-
elements.push({
|
|
165
|
-
x: currentX + width / 2, // Center position
|
|
166
|
-
width: width,
|
|
167
|
-
height: height,
|
|
168
|
-
type: 'platform'
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
currentX += width + this.minSpacing; // Move past this platform plus spacing
|
|
172
|
-
|
|
173
|
-
} else {
|
|
174
|
-
// Generate obstacle with random dimensions
|
|
175
|
-
const width = LEVEL_CONFIG.OBSTACLE_WIDTH_RANGE.min +
|
|
176
|
-
Math.random() * (LEVEL_CONFIG.OBSTACLE_WIDTH_RANGE.max - LEVEL_CONFIG.OBSTACLE_WIDTH_RANGE.min);
|
|
177
|
-
const height = this.obstacleHeightRange.min +
|
|
178
|
-
Math.random() * (this.obstacleHeightRange.max - this.obstacleHeightRange.min);
|
|
179
|
-
|
|
180
|
-
elements.push({
|
|
181
|
-
x: currentX + width / 2, // Center position
|
|
182
|
-
width: width,
|
|
183
|
-
height: height,
|
|
184
|
-
type: 'obstacle'
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
currentX += width + this.minSpacing; // Move past this obstacle plus spacing
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Alternate between platform and obstacle
|
|
191
|
-
isPlatform = !isPlatform;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return elements;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private createElementActor(element: PositionedElement): ENGINE.Actor {
|
|
198
|
-
const geometry = new THREE.BoxGeometry(element.width, element.height, LEVEL_CONFIG.DEPTH);
|
|
199
|
-
const material = new THREE.MeshStandardMaterial({ color: element.type === 'platform' ? COLORS.PLATFORM : COLORS.OBSTACLE });
|
|
200
|
-
|
|
201
|
-
const meshComponent = new ENGINE.MeshComponent({
|
|
202
|
-
geometry: geometry,
|
|
203
|
-
material: material,
|
|
204
|
-
position: new THREE.Vector3(element.x, element.height / 2, 0), // Position at ground level
|
|
205
|
-
physicsOptions: {
|
|
206
|
-
enabled: true,
|
|
207
|
-
motionType: ENGINE.PhysicsMotionType.Static,
|
|
208
|
-
collisionProfile: ENGINE.DefaultCollisionProfile.BlockAll,
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// Enable shadow casting and receiving
|
|
213
|
-
meshComponent.castShadow = true;
|
|
214
|
-
meshComponent.receiveShadow = true;
|
|
215
|
-
|
|
216
|
-
return new ENGINE.Actor({
|
|
217
|
-
rootComponent: meshComponent,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private createChunkDebugVisualization(startX: number): ENGINE.Actor {
|
|
222
|
-
// Create a wireframe box to visualize chunk boundaries
|
|
223
|
-
const chunkGeometry = new THREE.BoxGeometry(
|
|
224
|
-
this.chunkSize,
|
|
225
|
-
LEVEL_CONFIG.DEBUG_VISUALIZATION_HEIGHT,
|
|
226
|
-
LEVEL_CONFIG.DEPTH
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
const chunkMaterial = new THREE.MeshBasicMaterial({
|
|
230
|
-
color: LEVEL_CONFIG.DEBUG_VISUALIZATION_COLOR,
|
|
231
|
-
opacity: LEVEL_CONFIG.DEBUG_VISUALIZATION_OPACITY,
|
|
232
|
-
wireframe: true,
|
|
233
|
-
transparent: true,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const chunkBoundary = new ENGINE.Actor({
|
|
237
|
-
rootComponent: new ENGINE.MeshComponent({
|
|
238
|
-
geometry: chunkGeometry,
|
|
239
|
-
material: chunkMaterial,
|
|
240
|
-
position: new THREE.Vector3(
|
|
241
|
-
startX + this.chunkSize / 2,
|
|
242
|
-
LEVEL_CONFIG.DEBUG_VISUALIZATION_HEIGHT / 2,
|
|
243
|
-
0
|
|
244
|
-
),
|
|
245
|
-
physicsOptions: {
|
|
246
|
-
enabled: false,
|
|
247
|
-
},
|
|
248
|
-
}),
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
return chunkBoundary;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private createGroundMeshActor(startX: number): ENGINE.Actor {
|
|
255
|
-
// Create a ground mesh that spans the entire chunk width
|
|
256
|
-
const groundGeometry = new THREE.BoxGeometry(
|
|
257
|
-
this.chunkSize,
|
|
258
|
-
LEVEL_CONFIG.GROUND_HEIGHT,
|
|
259
|
-
LEVEL_CONFIG.DEPTH
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
const groundMaterial = new THREE.MeshStandardMaterial({
|
|
263
|
-
color: COLORS.GROUND,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const groundMeshComponent = new ENGINE.MeshComponent({
|
|
267
|
-
geometry: groundGeometry,
|
|
268
|
-
material: groundMaterial,
|
|
269
|
-
position: new THREE.Vector3(
|
|
270
|
-
startX + this.chunkSize / 2,
|
|
271
|
-
LEVEL_CONFIG.GROUND_Y_POSITION,
|
|
272
|
-
0
|
|
273
|
-
),
|
|
274
|
-
physicsOptions: {
|
|
275
|
-
enabled: true,
|
|
276
|
-
motionType: ENGINE.PhysicsMotionType.Static,
|
|
277
|
-
collisionProfile: ENGINE.DefaultCollisionProfile.BlockAll,
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Enable shadow receiving
|
|
282
|
-
groundMeshComponent.receiveShadow = true;
|
|
283
|
-
|
|
284
|
-
return new ENGINE.Actor({
|
|
285
|
-
rootComponent: groundMeshComponent,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Gets detailed geometry information at a given x location.
|
|
291
|
-
* @param x - The x coordinate to query
|
|
292
|
-
* @returns An array of geometry info objects at the given x location
|
|
293
|
-
*/
|
|
294
|
-
public getGeometryInfoAtX(x: number): Array<{
|
|
295
|
-
type: 'ground' | 'platform' | 'obstacle';
|
|
296
|
-
topY: number;
|
|
297
|
-
bottomY: number;
|
|
298
|
-
width: number;
|
|
299
|
-
centerX: number;
|
|
300
|
-
}> {
|
|
301
|
-
const chunkIndex = this.getChunkIndex(x);
|
|
302
|
-
const chunk = this.levelChunks.get(chunkIndex);
|
|
303
|
-
|
|
304
|
-
if (!chunk) {
|
|
305
|
-
return [];
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const geometries: Array<{
|
|
309
|
-
type: 'ground' | 'platform' | 'obstacle';
|
|
310
|
-
topY: number;
|
|
311
|
-
bottomY: number;
|
|
312
|
-
width: number;
|
|
313
|
-
centerX: number;
|
|
314
|
-
}> = [];
|
|
315
|
-
|
|
316
|
-
// Add ground mesh if enabled
|
|
317
|
-
if (this.createGroundMesh) {
|
|
318
|
-
const groundCenterY = LEVEL_CONFIG.GROUND_Y_POSITION;
|
|
319
|
-
const groundHalfHeight = LEVEL_CONFIG.GROUND_HEIGHT / 2;
|
|
320
|
-
geometries.push({
|
|
321
|
-
type: 'ground',
|
|
322
|
-
topY: groundCenterY + groundHalfHeight,
|
|
323
|
-
bottomY: groundCenterY - groundHalfHeight,
|
|
324
|
-
width: this.chunkSize,
|
|
325
|
-
centerX: chunk.startX + this.chunkSize / 2
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Add positioned elements (platforms and obstacles)
|
|
330
|
-
const elements = this.generatePositionedElements(chunk.startX);
|
|
331
|
-
|
|
332
|
-
for (const element of elements) {
|
|
333
|
-
const elementLeft = element.x - element.width / 2;
|
|
334
|
-
const elementRight = element.x + element.width / 2;
|
|
335
|
-
|
|
336
|
-
// Check if the x coordinate falls within this element's bounds
|
|
337
|
-
if (x >= elementLeft && x <= elementRight) {
|
|
338
|
-
geometries.push({
|
|
339
|
-
type: element.type,
|
|
340
|
-
topY: element.height,
|
|
341
|
-
bottomY: 0, // Elements are positioned at ground level (y=0 bottom)
|
|
342
|
-
width: element.width,
|
|
343
|
-
centerX: element.x
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Sort by top height (highest first)
|
|
349
|
-
geometries.sort((a, b) => b.topY - a.topY);
|
|
350
|
-
|
|
351
|
-
return geometries;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
public cleanup(): void {
|
|
355
|
-
for (const index of this.levelChunks.keys()) {
|
|
356
|
-
this.removeChunk(index);
|
|
357
|
-
}
|
|
358
|
-
this.levelChunks.clear();
|
|
359
|
-
console.log('[LevelGenerator] Cleaned up');
|
|
360
|
-
}
|
|
361
|
-
}
|
|
1
|
+
import * as ENGINE from 'genesys.js';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
import { COLORS, LEVEL_CONFIG } from './const.js';
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// LEVEL GENERATOR
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
// Interface for level generator configuration options
|
|
11
|
+
interface LevelGeneratorOptions {
|
|
12
|
+
chunkSize?: number;
|
|
13
|
+
chunksAhead?: number;
|
|
14
|
+
chunksBehind?: number;
|
|
15
|
+
platformHeightRange?: { min: number; max: number };
|
|
16
|
+
platformWidthRange?: { min: number; max: number };
|
|
17
|
+
obstacleHeightRange?: { min: number; max: number };
|
|
18
|
+
chunkMargin?: number;
|
|
19
|
+
minSpacing?: number;
|
|
20
|
+
enableDebugVisualization?: boolean;
|
|
21
|
+
createGroundMesh?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Interface for tracking level chunks
|
|
25
|
+
interface LevelChunk {
|
|
26
|
+
startX: number;
|
|
27
|
+
endX: number;
|
|
28
|
+
actors: ENGINE.Actor[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Interface for positioned elements within chunks
|
|
32
|
+
interface PositionedElement {
|
|
33
|
+
x: number;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
type: 'platform' | 'obstacle';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Simple level generator for infinite side-scrolling levels
|
|
40
|
+
export class SideScrollerLevelGenerator {
|
|
41
|
+
private world: ENGINE.World;
|
|
42
|
+
// Map of chunk index to chunk
|
|
43
|
+
private levelChunks: Map<number, LevelChunk> = new Map();
|
|
44
|
+
|
|
45
|
+
// Configuration
|
|
46
|
+
private chunkSize: number;
|
|
47
|
+
private chunksAhead: number;
|
|
48
|
+
private chunksBehind: number;
|
|
49
|
+
private platformHeightRange: { min: number; max: number };
|
|
50
|
+
private platformWidthRange: { min: number; max: number };
|
|
51
|
+
private obstacleHeightRange: { min: number; max: number };
|
|
52
|
+
private chunkMargin: number;
|
|
53
|
+
private minSpacing: number;
|
|
54
|
+
private debugVisualizationEnabled: boolean = false;
|
|
55
|
+
private createGroundMesh: boolean = false;
|
|
56
|
+
|
|
57
|
+
constructor(world: ENGINE.World, options: LevelGeneratorOptions = {}) {
|
|
58
|
+
this.world = world;
|
|
59
|
+
|
|
60
|
+
// Apply defaults
|
|
61
|
+
this.chunkSize = options.chunkSize ?? LEVEL_CONFIG.CHUNK_SIZE;
|
|
62
|
+
this.chunksAhead = options.chunksAhead ?? LEVEL_CONFIG.CHUNKS_AHEAD;
|
|
63
|
+
this.chunksBehind = options.chunksBehind ?? LEVEL_CONFIG.CHUNKS_BEHIND;
|
|
64
|
+
this.platformHeightRange = options.platformHeightRange ?? LEVEL_CONFIG.PLATFORM_HEIGHT_RANGE;
|
|
65
|
+
this.platformWidthRange = options.platformWidthRange ?? LEVEL_CONFIG.PLATFORM_WIDTH_RANGE;
|
|
66
|
+
this.obstacleHeightRange = options.obstacleHeightRange ?? LEVEL_CONFIG.OBSTACLE_HEIGHT_RANGE;
|
|
67
|
+
this.chunkMargin = options.chunkMargin ?? LEVEL_CONFIG.CHUNK_MARGIN;
|
|
68
|
+
this.minSpacing = options.minSpacing ?? LEVEL_CONFIG.MIN_SPACING;
|
|
69
|
+
this.debugVisualizationEnabled = options.enableDebugVisualization ?? false;
|
|
70
|
+
this.createGroundMesh = options.createGroundMesh ?? false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public updateLevel(playerX: number): void {
|
|
74
|
+
const playerChunkIndex = this.getChunkIndex(playerX);
|
|
75
|
+
|
|
76
|
+
const validChunkIndices = [];
|
|
77
|
+
for (let i = playerChunkIndex - this.chunksBehind; i <= playerChunkIndex + this.chunksAhead; i++) {
|
|
78
|
+
this.generateChunk(i);
|
|
79
|
+
validChunkIndices.push(i);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const index of this.levelChunks.keys()) {
|
|
83
|
+
if (!validChunkIndices.includes(index)) {
|
|
84
|
+
this.removeChunk(index);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private getChunkIndex(x: number): number {
|
|
90
|
+
return Math.floor(x / this.chunkSize);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private removeChunk(index: number): void {
|
|
94
|
+
const chunk = this.levelChunks.get(index);
|
|
95
|
+
if (chunk) {
|
|
96
|
+
this.world.removeActors(...chunk.actors);
|
|
97
|
+
this.levelChunks.delete(index);
|
|
98
|
+
console.log(`[LevelGenerator] Removed chunk at index ${index}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private generateChunk(index: number): void {
|
|
103
|
+
if (this.levelChunks.has(index)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const startX = index * this.chunkSize;
|
|
108
|
+
const endX = startX + this.chunkSize;
|
|
109
|
+
const actors: ENGINE.Actor[] = [];
|
|
110
|
+
|
|
111
|
+
// Generate positioned elements for this chunk
|
|
112
|
+
const elements = this.generatePositionedElements(startX);
|
|
113
|
+
|
|
114
|
+
// Create actors for each element
|
|
115
|
+
for (const element of elements) {
|
|
116
|
+
const actor = this.createElementActor(element);
|
|
117
|
+
actors.push(actor);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Add ground mesh if enabled
|
|
121
|
+
if (this.createGroundMesh) {
|
|
122
|
+
const groundActor = this.createGroundMeshActor(startX);
|
|
123
|
+
actors.push(groundActor);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add debug visualization if enabled
|
|
127
|
+
if (this.debugVisualizationEnabled) {
|
|
128
|
+
const debugActor = this.createChunkDebugVisualization(startX);
|
|
129
|
+
actors.push(debugActor);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Add all actors to the world
|
|
133
|
+
this.world.addActors(...actors);
|
|
134
|
+
|
|
135
|
+
// Track this chunk
|
|
136
|
+
const chunk: LevelChunk = {
|
|
137
|
+
startX,
|
|
138
|
+
endX,
|
|
139
|
+
actors,
|
|
140
|
+
};
|
|
141
|
+
console.log(`[LevelGenerator] Generated chunk at index ${index}`);
|
|
142
|
+
this.levelChunks.set(index, chunk);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private generatePositionedElements(startX: number): PositionedElement[] {
|
|
146
|
+
const elements: PositionedElement[] = [];
|
|
147
|
+
const usableWidth = this.chunkSize - (this.chunkMargin * 2); // Leave margin on each side
|
|
148
|
+
const usableStartX = startX + this.chunkMargin;
|
|
149
|
+
|
|
150
|
+
// Simple alternating generation: platform -> obstacle -> platform -> obstacle...
|
|
151
|
+
const totalElements = Math.floor(usableWidth / this.minSpacing); // How many elements can fit
|
|
152
|
+
|
|
153
|
+
let currentX = usableStartX;
|
|
154
|
+
let isPlatform = true; // Start with platform
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < totalElements && currentX < (startX + this.chunkSize - this.chunkMargin); i++) {
|
|
157
|
+
if (isPlatform) {
|
|
158
|
+
// Generate platform with random dimensions
|
|
159
|
+
const width = this.platformWidthRange.min +
|
|
160
|
+
Math.random() * (this.platformWidthRange.max - this.platformWidthRange.min);
|
|
161
|
+
const height = this.platformHeightRange.min +
|
|
162
|
+
Math.random() * (this.platformHeightRange.max - this.platformHeightRange.min);
|
|
163
|
+
|
|
164
|
+
elements.push({
|
|
165
|
+
x: currentX + width / 2, // Center position
|
|
166
|
+
width: width,
|
|
167
|
+
height: height,
|
|
168
|
+
type: 'platform'
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
currentX += width + this.minSpacing; // Move past this platform plus spacing
|
|
172
|
+
|
|
173
|
+
} else {
|
|
174
|
+
// Generate obstacle with random dimensions
|
|
175
|
+
const width = LEVEL_CONFIG.OBSTACLE_WIDTH_RANGE.min +
|
|
176
|
+
Math.random() * (LEVEL_CONFIG.OBSTACLE_WIDTH_RANGE.max - LEVEL_CONFIG.OBSTACLE_WIDTH_RANGE.min);
|
|
177
|
+
const height = this.obstacleHeightRange.min +
|
|
178
|
+
Math.random() * (this.obstacleHeightRange.max - this.obstacleHeightRange.min);
|
|
179
|
+
|
|
180
|
+
elements.push({
|
|
181
|
+
x: currentX + width / 2, // Center position
|
|
182
|
+
width: width,
|
|
183
|
+
height: height,
|
|
184
|
+
type: 'obstacle'
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
currentX += width + this.minSpacing; // Move past this obstacle plus spacing
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Alternate between platform and obstacle
|
|
191
|
+
isPlatform = !isPlatform;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return elements;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private createElementActor(element: PositionedElement): ENGINE.Actor {
|
|
198
|
+
const geometry = new THREE.BoxGeometry(element.width, element.height, LEVEL_CONFIG.DEPTH);
|
|
199
|
+
const material = new THREE.MeshStandardMaterial({ color: element.type === 'platform' ? COLORS.PLATFORM : COLORS.OBSTACLE });
|
|
200
|
+
|
|
201
|
+
const meshComponent = new ENGINE.MeshComponent({
|
|
202
|
+
geometry: geometry,
|
|
203
|
+
material: material,
|
|
204
|
+
position: new THREE.Vector3(element.x, element.height / 2, 0), // Position at ground level
|
|
205
|
+
physicsOptions: {
|
|
206
|
+
enabled: true,
|
|
207
|
+
motionType: ENGINE.PhysicsMotionType.Static,
|
|
208
|
+
collisionProfile: ENGINE.DefaultCollisionProfile.BlockAll,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Enable shadow casting and receiving
|
|
213
|
+
meshComponent.castShadow = true;
|
|
214
|
+
meshComponent.receiveShadow = true;
|
|
215
|
+
|
|
216
|
+
return new ENGINE.Actor({
|
|
217
|
+
rootComponent: meshComponent,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private createChunkDebugVisualization(startX: number): ENGINE.Actor {
|
|
222
|
+
// Create a wireframe box to visualize chunk boundaries
|
|
223
|
+
const chunkGeometry = new THREE.BoxGeometry(
|
|
224
|
+
this.chunkSize,
|
|
225
|
+
LEVEL_CONFIG.DEBUG_VISUALIZATION_HEIGHT,
|
|
226
|
+
LEVEL_CONFIG.DEPTH
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const chunkMaterial = new THREE.MeshBasicMaterial({
|
|
230
|
+
color: LEVEL_CONFIG.DEBUG_VISUALIZATION_COLOR,
|
|
231
|
+
opacity: LEVEL_CONFIG.DEBUG_VISUALIZATION_OPACITY,
|
|
232
|
+
wireframe: true,
|
|
233
|
+
transparent: true,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const chunkBoundary = new ENGINE.Actor({
|
|
237
|
+
rootComponent: new ENGINE.MeshComponent({
|
|
238
|
+
geometry: chunkGeometry,
|
|
239
|
+
material: chunkMaterial,
|
|
240
|
+
position: new THREE.Vector3(
|
|
241
|
+
startX + this.chunkSize / 2,
|
|
242
|
+
LEVEL_CONFIG.DEBUG_VISUALIZATION_HEIGHT / 2,
|
|
243
|
+
0
|
|
244
|
+
),
|
|
245
|
+
physicsOptions: {
|
|
246
|
+
enabled: false,
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return chunkBoundary;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private createGroundMeshActor(startX: number): ENGINE.Actor {
|
|
255
|
+
// Create a ground mesh that spans the entire chunk width
|
|
256
|
+
const groundGeometry = new THREE.BoxGeometry(
|
|
257
|
+
this.chunkSize,
|
|
258
|
+
LEVEL_CONFIG.GROUND_HEIGHT,
|
|
259
|
+
LEVEL_CONFIG.DEPTH
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const groundMaterial = new THREE.MeshStandardMaterial({
|
|
263
|
+
color: COLORS.GROUND,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const groundMeshComponent = new ENGINE.MeshComponent({
|
|
267
|
+
geometry: groundGeometry,
|
|
268
|
+
material: groundMaterial,
|
|
269
|
+
position: new THREE.Vector3(
|
|
270
|
+
startX + this.chunkSize / 2,
|
|
271
|
+
LEVEL_CONFIG.GROUND_Y_POSITION,
|
|
272
|
+
0
|
|
273
|
+
),
|
|
274
|
+
physicsOptions: {
|
|
275
|
+
enabled: true,
|
|
276
|
+
motionType: ENGINE.PhysicsMotionType.Static,
|
|
277
|
+
collisionProfile: ENGINE.DefaultCollisionProfile.BlockAll,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Enable shadow receiving
|
|
282
|
+
groundMeshComponent.receiveShadow = true;
|
|
283
|
+
|
|
284
|
+
return new ENGINE.Actor({
|
|
285
|
+
rootComponent: groundMeshComponent,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Gets detailed geometry information at a given x location.
|
|
291
|
+
* @param x - The x coordinate to query
|
|
292
|
+
* @returns An array of geometry info objects at the given x location
|
|
293
|
+
*/
|
|
294
|
+
public getGeometryInfoAtX(x: number): Array<{
|
|
295
|
+
type: 'ground' | 'platform' | 'obstacle';
|
|
296
|
+
topY: number;
|
|
297
|
+
bottomY: number;
|
|
298
|
+
width: number;
|
|
299
|
+
centerX: number;
|
|
300
|
+
}> {
|
|
301
|
+
const chunkIndex = this.getChunkIndex(x);
|
|
302
|
+
const chunk = this.levelChunks.get(chunkIndex);
|
|
303
|
+
|
|
304
|
+
if (!chunk) {
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const geometries: Array<{
|
|
309
|
+
type: 'ground' | 'platform' | 'obstacle';
|
|
310
|
+
topY: number;
|
|
311
|
+
bottomY: number;
|
|
312
|
+
width: number;
|
|
313
|
+
centerX: number;
|
|
314
|
+
}> = [];
|
|
315
|
+
|
|
316
|
+
// Add ground mesh if enabled
|
|
317
|
+
if (this.createGroundMesh) {
|
|
318
|
+
const groundCenterY = LEVEL_CONFIG.GROUND_Y_POSITION;
|
|
319
|
+
const groundHalfHeight = LEVEL_CONFIG.GROUND_HEIGHT / 2;
|
|
320
|
+
geometries.push({
|
|
321
|
+
type: 'ground',
|
|
322
|
+
topY: groundCenterY + groundHalfHeight,
|
|
323
|
+
bottomY: groundCenterY - groundHalfHeight,
|
|
324
|
+
width: this.chunkSize,
|
|
325
|
+
centerX: chunk.startX + this.chunkSize / 2
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Add positioned elements (platforms and obstacles)
|
|
330
|
+
const elements = this.generatePositionedElements(chunk.startX);
|
|
331
|
+
|
|
332
|
+
for (const element of elements) {
|
|
333
|
+
const elementLeft = element.x - element.width / 2;
|
|
334
|
+
const elementRight = element.x + element.width / 2;
|
|
335
|
+
|
|
336
|
+
// Check if the x coordinate falls within this element's bounds
|
|
337
|
+
if (x >= elementLeft && x <= elementRight) {
|
|
338
|
+
geometries.push({
|
|
339
|
+
type: element.type,
|
|
340
|
+
topY: element.height,
|
|
341
|
+
bottomY: 0, // Elements are positioned at ground level (y=0 bottom)
|
|
342
|
+
width: element.width,
|
|
343
|
+
centerX: element.x
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Sort by top height (highest first)
|
|
349
|
+
geometries.sort((a, b) => b.topY - a.topY);
|
|
350
|
+
|
|
351
|
+
return geometries;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public cleanup(): void {
|
|
355
|
+
for (const index of this.levelChunks.keys()) {
|
|
356
|
+
this.removeChunk(index);
|
|
357
|
+
}
|
|
358
|
+
this.levelChunks.clear();
|
|
359
|
+
console.log('[LevelGenerator] Cleaned up');
|
|
360
|
+
}
|
|
361
|
+
}
|