awacke1 commited on
Commit
960a9eb
·
verified ·
1 Parent(s): b079db3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +242 -241
index.html CHANGED
@@ -3,48 +3,22 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Procedural 3D Dungeon Explorer</title>
7
  <style>
8
  body { margin: 0; overflow: hidden; background-color: #000; color: white; font-family: monospace; }
9
  canvas { display: block; }
10
- #blocker { /* For Pointer Lock API */
11
- position: absolute;
12
- width: 100%;
13
- height: 100%;
14
- background-color: rgba(0,0,0,0.5);
15
- display: flex;
16
- justify-content: center;
17
- align-items: center;
18
- cursor: pointer;
19
- }
20
- #instructions {
21
- width: 50%;
22
- text-align: center;
23
- padding: 20px;
24
- background: rgba(20, 20, 20, 0.8);
25
- border-radius: 10px;
26
- }
27
- #crosshair {
28
- position: absolute;
29
- top: 50%;
30
- left: 50%;
31
- width: 10px;
32
- height: 10px;
33
- border: 1px solid white;
34
- border-radius: 50%;
35
- transform: translate(-50%, -50%);
36
- pointer-events: none; /* Don't interfere with clicks */
37
- mix-blend-mode: difference; /* Make visible on most backgrounds */
38
- display: none; /* Hidden until pointer lock */
39
- }
40
  </style>
41
  </head>
42
  <body>
43
  <div id="blocker">
44
  <div id="instructions">
45
- <h1>Dungeon Explorer</h1>
46
  <p>Click to Enter</p>
47
  <p>(W, A, S, D = Move, MOUSE = Look)</p>
 
48
  </div>
49
  </div>
50
  <div id="crosshair">+</div>
@@ -61,348 +35,346 @@
61
  <script type="module">
62
  import * as THREE from 'three';
63
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
64
- import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
 
 
 
65
 
66
  // --- Config ---
67
- const DUNGEON_WIDTH = 30; // Size of grid (cells)
68
- const DUNGEON_HEIGHT = 30;
69
- const CELL_SIZE = 5; // Size of one cell in world units
70
  const WALL_HEIGHT = 4;
71
- const PLAYER_HEIGHT = 1.6; // Camera height offset from ground
72
- const PLAYER_RADIUS = 0.4; // For collision
73
- const PLAYER_SPEED = 5.0; // Units per second
74
- const GENERATION_STEPS = 1500; // How long the random walk carves
75
 
76
  // --- Three.js Setup ---
77
  let scene, camera, renderer;
78
- let controls; // PointerLockControls
79
  let clock;
 
80
 
81
  // --- Player State ---
82
  const playerVelocity = new THREE.Vector3();
83
- const playerDirection = new THREE.Vector3(); // Forward direction based on camera
84
  let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
85
- let playerOnGround = true; // Basic ground check for potential jump later
86
 
87
  // --- World Data ---
88
- let dungeonLayout = []; // 2D grid: 0 = wall, 1 = floor
89
- let floorMesh = null;
90
- let wallMesh = null;
91
 
92
  // --- DOM Elements ---
93
  const blocker = document.getElementById('blocker');
94
  const instructions = document.getElementById('instructions');
95
  const crosshair = document.getElementById('crosshair');
96
 
 
 
 
 
97
  // --- Initialization ---
98
  function init() {
99
- console.log("Initializing...");
100
  clock = new THREE.Clock();
101
 
102
- // Scene
103
- scene = new THREE.Scene();
 
 
 
 
 
 
 
 
 
 
104
  scene.background = new THREE.Color(0x111111);
105
- scene.fog = new THREE.Fog(0x111111, 5, CELL_SIZE * 5); // Fog based on cell size
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
  // Camera (First Person)
108
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
109
- camera.position.y = PLAYER_HEIGHT; // Set initial height
110
- console.log("Camera created");
111
-
112
- // Renderer
113
- renderer = new THREE.WebGLRenderer({ antialias: true });
114
- renderer.setSize(window.innerWidth, window.innerHeight);
115
- renderer.setPixelRatio(window.devicePixelRatio);
116
- renderer.shadowMap.enabled = true;
117
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;
118
- document.body.appendChild(renderer.domElement);
119
- console.log("Renderer created");
120
 
121
  // Lighting
122
- scene.add(new THREE.AmbientLight(0x404040, 0.5)); // Dim ambient light
123
 
124
- const flashlight = new THREE.SpotLight(0xffffff, 3, 25, Math.PI / 6, 0.3, 1); // Intensity, Distance, Angle, Penumbra, Decay
125
- flashlight.position.set(0, 0, 0); // Position relative to camera
126
- flashlight.target.position.set(0, 0, -1); // Target relative to camera
127
  flashlight.castShadow = true;
128
  flashlight.shadow.mapSize.width = 1024;
129
  flashlight.shadow.mapSize.height = 1024;
130
  flashlight.shadow.camera.near = 0.5;
131
- flashlight.shadow.camera.far = 25;
132
- camera.add(flashlight); // Attach light to camera
133
  camera.add(flashlight.target);
134
  scene.add(camera); // Add camera (with light) to scene
135
- console.log("Lighting setup");
136
 
137
  // Pointer Lock Controls
138
  controls = new PointerLockControls(camera, renderer.domElement);
139
- scene.add(controls.getObject()); // Add the camera controls group
 
140
 
141
  blocker.addEventListener('click', () => { controls.lock(); });
142
- controls.addEventListener('lock', () => {
143
- instructions.style.display = 'none';
144
- blocker.style.display = 'none';
145
- crosshair.style.display = 'block';
146
- });
147
- controls.addEventListener('unlock', () => {
148
- blocker.style.display = 'flex';
149
- instructions.style.display = '';
150
- crosshair.style.display = 'none';
151
- });
152
 
153
  // Keyboard Listeners
 
 
154
  document.addEventListener('keydown', onKeyDown);
155
  document.addEventListener('keyup', onKeyUp);
156
 
157
  // Resize Listener
 
158
  window.addEventListener('resize', onWindowResize);
159
 
160
- // Generate and Build Dungeon
161
- console.log("Generating dungeon layout...");
162
- dungeonLayout = generateDungeonLayout(DUNGEON_WIDTH, DUNGEON_HEIGHT);
163
  console.log("Layout generated, creating meshes...");
164
- createDungeonMeshes(dungeonLayout);
165
  console.log("Dungeon meshes created.");
166
 
167
- // Find starting position for player
168
  const startPos = findStartPosition(dungeonLayout);
169
  if (startPos) {
170
- camera.position.x = startPos.x;
171
- camera.position.z = startPos.z;
172
- console.log("Player start position set:", startPos);
173
  } else {
174
- console.error("Could not find valid start position!");
175
- camera.position.x = (DUNGEON_WIDTH / 2) * CELL_SIZE; // Fallback
176
- camera.position.z = (DUNGEON_HEIGHT / 2) * CELL_SIZE;
 
177
  }
178
 
 
 
 
 
 
 
 
179
 
180
- console.log("Initialization Complete.");
181
  animate(); // Start the loop
182
  }
183
 
184
- // --- Dungeon Generation (Random Walk) ---
185
- function generateDungeonLayout(width, height) {
186
- const grid = Array(height).fill(null).map(() => Array(width).fill(0)); // 0 = wall
187
- let x = Math.floor(width / 2);
188
- let y = Math.floor(height / 2);
189
- let currentSteps = 0;
190
-
191
- grid[y][x] = 1; // Start point is floor
192
-
193
- const directions = [ [0, -1], [0, 1], [-1, 0], [1, 0] ]; // N, S, W, E
194
-
195
- while (currentSteps < GENERATION_STEPS) {
196
- const dir = directions[Math.floor(Math.random() * directions.length)];
197
- const nextX = x + dir[0];
198
- const nextY = y + dir[1];
199
-
200
- // Check bounds (stay within grid, maybe leave border walls?)
201
- if (nextX > 0 && nextX < width - 1 && nextY > 0 && nextY < height - 1) {
202
- x = nextX;
203
- y = nextY;
204
- if (grid[y][x] === 0) {
205
- grid[y][x] = 1; // Carve floor
206
- currentSteps++;
207
  }
208
- // Allow walker to backtrack or carve adjacent cells more freely?
209
- // Maybe carve a 2x1 area sometimes? For now, simple walk.
210
- } else {
211
- // Hit edge, pick new random start point on existing floor?
212
- // Or just pick new direction? Let's just pick new direction.
213
- // To avoid getting stuck, could randomly jump to another floor tile.
214
  }
215
-
216
- // Randomly change direction bias? (e.g. tend to go straight)
217
- // Keep it simple for now.
218
  }
 
 
 
 
 
 
219
  return grid;
220
  }
221
 
222
- // --- Find Start Position ---
 
223
  function findStartPosition(grid) {
224
- // Find the first floor tile encountered, starting from center
225
  const startY = Math.floor(grid.length / 2);
226
  const startX = Math.floor(grid[0].length / 2);
227
-
228
- // Simple search outwards from center
229
  for (let r = 0; r < Math.max(startX, startY); r++) {
230
  for (let y = startY - r; y <= startY + r; y++) {
231
  for (let x = startX - r; x <= startX + r; x++) {
232
- // Check only perimeter of radius r or center itself
233
  if (Math.abs(y - startY) === r || Math.abs(x - startX) === r || r === 0) {
234
  if (y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 1) {
235
- return { x: x * CELL_SIZE + CELL_SIZE / 2, z: y * CELL_SIZE + CELL_SIZE / 2 }; // Center of cell
 
236
  }
237
  }
238
  }
239
  }
240
  }
241
- return null; // Should not happen if generation works
 
242
  }
243
 
244
 
245
- // --- Dungeon Meshing ---
246
- function createDungeonMeshes(grid) {
247
- console.log("Creating merged geometries...");
248
- const floorGeometries = [];
249
- const wallGeometries = [];
250
-
251
- const floorGeo = new THREE.PlaneGeometry(CELL_SIZE, CELL_SIZE);
252
- const wallGeoN = new THREE.BoxGeometry(CELL_SIZE, WALL_HEIGHT, 0.1);
253
- const wallGeoS = new THREE.BoxGeometry(CELL_SIZE, WALL_HEIGHT, 0.1);
254
- const wallGeoE = new THREE.BoxGeometry(0.1, WALL_HEIGHT, CELL_SIZE);
255
- const wallGeoW = new THREE.BoxGeometry(0.1, WALL_HEIGHT, CELL_SIZE);
256
-
257
- // Load Textures (Replace with actual URLs)
258
- const textureLoader = new THREE.TextureLoader();
259
- const floorTexture = textureLoader.load('https://threejs.org/examples/textures/hardwood2_diffuse.jpg'); // Placeholder wood floor
260
- const wallTexture = textureLoader.load('https://threejs.org/examples/textures/brick_diffuse.jpg'); // Placeholder brick wall
261
- floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
262
- wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping;
263
- // Adjust texture repeats based on CELL_SIZE if needed
264
- // floorTexture.repeat.set(CELL_SIZE / 2, CELL_SIZE / 2);
265
- // wallTexture.repeat.set(CELL_SIZE / 2, WALL_HEIGHT / 2);
266
-
267
- const floorMaterial = new THREE.MeshStandardMaterial({ map: floorTexture, roughness: 0.8, metalness: 0.1 });
268
- const wallMaterial = new THREE.MeshStandardMaterial({ map: wallTexture, roughness: 0.9, metalness: 0.0 });
269
 
270
  for (let y = 0; y < grid.length; y++) {
271
  for (let x = 0; x < grid[y].length; x++) {
272
  if (grid[y][x] === 1) { // If it's a floor cell
273
- // Create Floor Tile Geometry
274
- const floorInstance = floorGeo.clone();
275
- floorInstance.rotateX(-Math.PI / 2);
276
- floorInstance.translate(x * CELL_SIZE + CELL_SIZE / 2, 0, y * CELL_SIZE + CELL_SIZE / 2);
277
- floorGeometries.push(floorInstance);
 
 
 
278
 
279
  // Check neighbors for Walls
280
  // North Wall
281
  if (y === 0 || grid[y - 1][x] === 0) {
282
- const wallInstance = wallGeoN.clone();
283
- wallInstance.translate(x * CELL_SIZE + CELL_SIZE / 2, WALL_HEIGHT / 2, y * CELL_SIZE);
284
- wallGeometries.push(wallInstance);
 
285
  }
286
  // South Wall
287
  if (y === grid.length - 1 || grid[y + 1][x] === 0) {
288
- const wallInstance = wallGeoS.clone();
289
- wallInstance.translate(x * CELL_SIZE + CELL_SIZE / 2, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE);
290
- wallGeometries.push(wallInstance);
 
291
  }
292
  // West Wall
293
  if (x === 0 || grid[y][x - 1] === 0) {
294
- const wallInstance = wallGeoW.clone();
295
- wallInstance.translate(x * CELL_SIZE, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE / 2);
296
- wallGeometries.push(wallInstance);
 
297
  }
298
  // East Wall
299
  if (x === grid[y].length - 1 || grid[y][x + 1] === 0) {
300
- const wallInstance = wallGeoE.clone();
301
- wallInstance.translate(x * CELL_SIZE + CELL_SIZE, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE / 2);
302
- wallGeometries.push(wallInstance);
 
303
  }
304
  }
305
  }
306
  }
307
-
308
- // Merge Geometries
309
- if (floorGeometries.length > 0) {
310
- const mergedFloorGeometry = BufferGeometryUtils.mergeGeometries(floorGeometries, false);
311
- if (mergedFloorGeometry) {
312
- floorMesh = new THREE.Mesh(mergedFloorGeometry, floorMaterial);
313
- floorMesh.receiveShadow = true;
314
- scene.add(floorMesh);
315
- console.log("Merged floor mesh added.");
316
- } else { console.error("Floor geometry merging failed."); }
317
-
318
- }
319
- if (wallGeometries.length > 0) {
320
- const mergedWallGeometry = BufferGeometryUtils.mergeGeometries(wallGeometries, false);
321
- if (mergedWallGeometry) {
322
- wallMesh = new THREE.Mesh(mergedWallGeometry, wallMaterial);
323
- wallMesh.castShadow = true;
324
- wallMesh.receiveShadow = true; // Walls can receive shadows from other parts
325
- scene.add(wallMesh);
326
- console.log("Merged wall mesh added.");
327
- } else { console.error("Wall geometry merging failed."); }
328
- }
329
-
330
- // Dispose of individual geometries to save memory
331
- floorGeo.dispose();
332
- wallGeoN.dispose();
333
- wallGeoS.dispose();
334
- wallGeoE.dispose();
335
- wallGeoW.dispose();
336
- floorGeometries.forEach(g => g.dispose());
337
- wallGeometries.forEach(g => g.dispose());
338
-
339
- console.log("Individual geometries disposed.");
340
  }
341
 
342
 
343
- // --- Player Movement & Collision ---
344
  function handleInputAndMovement(deltaTime) {
345
- if (!controls.isLocked) return;
346
 
347
  const speed = PLAYER_SPEED * deltaTime;
348
- playerVelocity.x = 0; // Reset velocity each frame (using direct position change)
 
349
  playerVelocity.z = 0;
350
 
351
- // Get camera direction
352
- camera.getWorldDirection(playerDirection);
353
- playerDirection.y = 0; // Ignore vertical component for movement
354
  playerDirection.normalize();
355
 
356
- const rightDirection = new THREE.Vector3().crossVectors(camera.up, playerDirection).normalize();
 
 
357
 
358
- // Calculate movement based on keys
359
  if (moveForward) playerVelocity.add(playerDirection);
360
  if (moveBackward) playerVelocity.sub(playerDirection);
361
- if (moveLeft) playerVelocity.sub(rightDirection); // Strafe left
362
- if (moveRight) playerVelocity.add(rightDirection); // Strafe right
363
 
364
- // Normalize diagonal movement speed if necessary (optional but good)
365
  if (playerVelocity.lengthSq() > 0) {
366
- playerVelocity.normalize().multiplyScalar(speed);
367
  }
368
 
369
- // --- Basic Collision Detection ---
370
  const currentPos = controls.getObject().position;
371
- const nextPos = currentPos.clone().add(playerVelocity); // Potential next position
372
-
373
- // Check X movement collision
374
- const targetGridX_X = Math.floor((nextPos.x + Math.sign(playerVelocity.x) * PLAYER_RADIUS) / CELL_SIZE);
375
- const currentGridZ_X = Math.floor(currentPos.z / CELL_SIZE);
376
- if (dungeonLayout[currentGridZ_X]?.[targetGridX_X] === 0) { // Hit wall in X direction
377
- playerVelocity.x = 0; // Stop X movement
378
- console.log("Collision X");
 
 
 
 
 
 
379
  }
380
 
381
- // Check Z movement collision
382
- const currentGridX_Z = Math.floor(currentPos.x / CELL_SIZE);
383
- const targetGridZ_Z = Math.floor((nextPos.z + Math.sign(playerVelocity.z) * PLAYER_RADIUS) / CELL_SIZE);
384
- if (dungeonLayout[targetGridZ_Z]?.[currentGridX_Z] === 0) { // Hit wall in Z direction
385
- playerVelocity.z = 0; // Stop Z movement
386
- console.log("Collision Z");
 
 
 
 
 
387
  }
388
 
389
- // Apply final velocity to camera controls object
390
- controls.moveRight(playerVelocity.x); // Use moveRight for strafing based on calculated velocity X
391
- controls.moveForward(playerVelocity.z); // Use moveForward for Z based on calculated velocity Z
 
 
 
 
392
 
393
  // Keep player at fixed height (no gravity/jump yet)
394
- controls.getObject().position.y = PLAYER_HEIGHT;
 
 
 
395
  }
396
 
397
 
398
  // --- Event Handlers ---
399
  function onKeyDown(event) {
 
400
  switch (event.code) {
401
  case 'ArrowUp': case 'KeyW': moveForward = true; break;
402
  case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
403
  case 'ArrowDown': case 'KeyS': moveBackward = true; break;
404
  case 'ArrowRight': case 'KeyD': moveRight = true; break;
405
- // Add jump/other actions later
 
406
  }
407
  }
408
 
@@ -416,26 +388,55 @@
416
  }
417
 
418
  function onWindowResize() {
 
 
419
  camera.aspect = window.innerWidth / window.innerHeight;
420
  camera.updateProjectionMatrix();
421
  renderer.setSize(window.innerWidth, window.innerHeight);
422
  }
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  // --- Animation Loop ---
425
  function animate() {
426
- animationFrameId = requestAnimationFrame(animate); // Store frame ID
427
 
428
  const delta = clock.getDelta();
429
 
430
- handleInputAndMovement(delta);
431
-
432
- // Update other game elements (monsters, etc.) here later
 
 
433
 
434
  renderer.render(scene, camera);
435
  }
436
 
437
  // --- Start ---
438
- init();
 
 
 
 
 
 
439
 
440
  </script>
441
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>DEBUG - Procedural 3D Dungeon</title>
7
  <style>
8
  body { margin: 0; overflow: hidden; background-color: #000; color: white; font-family: monospace; }
9
  canvas { display: block; }
10
+ #blocker { position: absolute; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; cursor: pointer; z-index: 10; }
11
+ #instructions { width: 50%; text-align: center; padding: 20px; background: rgba(20, 20, 20, 0.8); border-radius: 10px; }
12
+ #crosshair { position: absolute; top: 50%; left: 50%; width: 10px; height: 10px; border: 1px solid white; border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; mix-blend-mode: difference; display: none; z-index: 11; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  </style>
14
  </head>
15
  <body>
16
  <div id="blocker">
17
  <div id="instructions">
18
+ <h1>Dungeon Explorer (Debug Mode)</h1>
19
  <p>Click to Enter</p>
20
  <p>(W, A, S, D = Move, MOUSE = Look)</p>
21
+ <p>Check F12 Console for Errors!</p>
22
  </div>
23
  </div>
24
  <div id="crosshair">+</div>
 
35
  <script type="module">
36
  import * as THREE from 'three';
37
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
38
+ // BufferGeometryUtils not needed for this debug version
39
+ // import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
40
+
41
+ console.log("Script Start");
42
 
43
  // --- Config ---
44
+ const DUNGEON_WIDTH = 10; // Smaller grid for debug
45
+ const DUNGEON_HEIGHT = 10;
46
+ const CELL_SIZE = 5;
47
  const WALL_HEIGHT = 4;
48
+ const PLAYER_HEIGHT = 1.6;
49
+ const PLAYER_RADIUS = 0.4;
50
+ const PLAYER_SPEED = 5.0;
 
51
 
52
  // --- Three.js Setup ---
53
  let scene, camera, renderer;
54
+ let controls;
55
  let clock;
56
+ let flashlight;
57
 
58
  // --- Player State ---
59
  const playerVelocity = new THREE.Vector3();
60
+ const playerDirection = new THREE.Vector3();
61
  let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
 
62
 
63
  // --- World Data ---
64
+ let dungeonLayout = [];
65
+ const worldMeshes = []; // Store refs to added meshes for potential cleanup
 
66
 
67
  // --- DOM Elements ---
68
  const blocker = document.getElementById('blocker');
69
  const instructions = document.getElementById('instructions');
70
  const crosshair = document.getElementById('crosshair');
71
 
72
+ // --- Materials (Basic Colors) ---
73
+ const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x555555 }); // Use Lambert for basic lighting check
74
+ const wallMaterial = new THREE.MeshLambertMaterial({ color: 0x884444 });
75
+
76
  // --- Initialization ---
77
  function init() {
78
+ console.log("--- Initializing Game ---");
79
  clock = new THREE.Clock();
80
 
81
+ // Clear previous scene if restarting
82
+ if (scene) {
83
+ console.log("Clearing previous scene objects...");
84
+ worldMeshes.forEach(mesh => {
85
+ if(mesh.parent) scene.remove(mesh);
86
+ if(mesh.geometry) mesh.geometry.dispose();
87
+ // Only dispose material if we know it's unique per object
88
+ });
89
+ worldMeshes.length = 0; // Clear the array
90
+ } else {
91
+ scene = new THREE.Scene();
92
+ }
93
  scene.background = new THREE.Color(0x111111);
94
+ scene.fog = new THREE.Fog(0x111111, 10, CELL_SIZE * 6);
95
+
96
+ // Clear previous renderer if restarting
97
+ if (renderer) {
98
+ console.log("Disposing previous renderer...");
99
+ renderer.dispose();
100
+ if (renderer.domElement.parentNode) {
101
+ renderer.domElement.parentNode.removeChild(renderer.domElement);
102
+ }
103
+ }
104
+ renderer = new THREE.WebGLRenderer({ antialias: true });
105
+ renderer.setSize(window.innerWidth, window.innerHeight);
106
+ renderer.setPixelRatio(window.devicePixelRatio);
107
+ renderer.shadowMap.enabled = true; // Keep shadows enabled
108
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
109
+ document.body.appendChild(renderer.domElement);
110
+ console.log("Renderer created/reset.");
111
+
112
 
113
  // Camera (First Person)
114
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
115
+ camera.position.y = PLAYER_HEIGHT;
116
+ console.log("Camera created.");
 
 
 
 
 
 
 
 
 
117
 
118
  // Lighting
119
+ scene.add(new THREE.AmbientLight(0x404040, 0.8)); // Slightly brighter ambient
120
 
121
+ flashlight = new THREE.SpotLight(0xffffff, 3, 30, Math.PI / 5, 0.4, 1.5);
122
+ flashlight.position.set(0, 0, 0); // Relative to camera
123
+ flashlight.target.position.set(0, 0, -1); // Relative to camera
124
  flashlight.castShadow = true;
125
  flashlight.shadow.mapSize.width = 1024;
126
  flashlight.shadow.mapSize.height = 1024;
127
  flashlight.shadow.camera.near = 0.5;
128
+ flashlight.shadow.camera.far = 30;
129
+ camera.add(flashlight);
130
  camera.add(flashlight.target);
131
  scene.add(camera); // Add camera (with light) to scene
132
+ console.log("Lighting setup.");
133
 
134
  // Pointer Lock Controls
135
  controls = new PointerLockControls(camera, renderer.domElement);
136
+ // We don't add controls.getObject() directly to scene IF flashlight is child of camera
137
+ // scene.add(controls.getObject()); // Only if camera isn't manually added
138
 
139
  blocker.addEventListener('click', () => { controls.lock(); });
140
+ controls.addEventListener('lock', () => { instructions.style.display = 'none'; blocker.style.display = 'none'; crosshair.style.display = 'block'; });
141
+ controls.addEventListener('unlock', () => { blocker.style.display = 'flex'; instructions.style.display = ''; crosshair.style.display = 'none'; });
142
+ console.log("Controls setup.");
 
 
 
 
 
 
 
143
 
144
  // Keyboard Listeners
145
+ document.removeEventListener('keydown', onKeyDown); // Remove old listeners if restarting
146
+ document.removeEventListener('keyup', onKeyUp);
147
  document.addEventListener('keydown', onKeyDown);
148
  document.addEventListener('keyup', onKeyUp);
149
 
150
  // Resize Listener
151
+ window.removeEventListener('resize', onWindowResize); // Remove old
152
  window.addEventListener('resize', onWindowResize);
153
 
154
+ // --- Generate FIXED Dungeon ---
155
+ console.log("Generating FIXED dungeon layout...");
156
+ dungeonLayout = generateFixedDungeonLayout(DUNGEON_WIDTH, DUNGEON_HEIGHT);
157
  console.log("Layout generated, creating meshes...");
158
+ createDungeonMeshes_Direct(dungeonLayout); // Use direct mesh addition
159
  console.log("Dungeon meshes created.");
160
 
161
+ // --- Set Player Start Position ---
162
  const startPos = findStartPosition(dungeonLayout);
163
  if (startPos) {
164
+ // Position the camera (which is the player view)
165
+ controls.getObject().position.set(startPos.x, PLAYER_HEIGHT, startPos.z);
166
+ console.log("Player start position set at:", startPos);
167
  } else {
168
+ console.error("Could not find valid start position! Placing at center.");
169
+ const fallbackX = (DUNGEON_WIDTH / 2) * CELL_SIZE;
170
+ const fallbackZ = (DUNGEON_HEIGHT / 2) * CELL_SIZE;
171
+ controls.getObject().position.set(fallbackX, PLAYER_HEIGHT, fallbackZ);
172
  }
173
 
174
+ // Add Axes Helper for orientation check
175
+ const axesHelper = new THREE.AxesHelper(CELL_SIZE);
176
+ axesHelper.position.copy(controls.getObject().position); // Place at start pos
177
+ axesHelper.position.y = 0.1;
178
+ scene.add(axesHelper);
179
+ worldMeshes.push(axesHelper); // Track for cleanup
180
+
181
 
182
+ console.log("--- Initialization Complete ---");
183
  animate(); // Start the loop
184
  }
185
 
186
+ // --- Dungeon Generation (FIXED LAYOUT) ---
187
+ function generateFixedDungeonLayout(width, height) {
188
+ console.log("Generating FIXED 5x5 layout for debugging...");
189
+ const grid = Array(height).fill(null).map(() => Array(width).fill(0)); // All walls
190
+ // Simple 5x5 room centered
191
+ const cx = Math.floor(width / 2);
192
+ const cy = Math.floor(height / 2);
193
+ for (let y = cy - 2; y <= cy + 2; y++) {
194
+ for (let x = cx - 2; x <= cx + 2; x++) {
195
+ if (y >= 0 && y < height && x >= 0 && x < width) {
196
+ grid[y][x] = 1; // Floor
 
 
 
 
 
 
 
 
 
 
 
 
197
  }
 
 
 
 
 
 
198
  }
 
 
 
199
  }
200
+ // Add a corridor
201
+ for (let y = cy + 3; y < height -1 ; y++) {
202
+ if (grid[y]) grid[y][cx] = 1;
203
+ }
204
+ console.log(`Fixed Layout Generated (${width}x${height}). Center: ${cx},${cy}`);
205
+ // console.log("Grid:", grid.map(row => row.join('')).join('\n')); // Optional: Log grid visually
206
  return grid;
207
  }
208
 
209
+
210
+ // --- Find Start Position (Same as before) ---
211
  function findStartPosition(grid) {
 
212
  const startY = Math.floor(grid.length / 2);
213
  const startX = Math.floor(grid[0].length / 2);
214
+ console.log(`Searching for start near ${startX},${startY}`);
 
215
  for (let r = 0; r < Math.max(startX, startY); r++) {
216
  for (let y = startY - r; y <= startY + r; y++) {
217
  for (let x = startX - r; x <= startX + r; x++) {
 
218
  if (Math.abs(y - startY) === r || Math.abs(x - startX) === r || r === 0) {
219
  if (y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 1) {
220
+ console.log(`Found start floor at ${x},${y}`);
221
+ return { x: x * CELL_SIZE + CELL_SIZE / 2, z: y * CELL_SIZE + CELL_SIZE / 2 };
222
  }
223
  }
224
  }
225
  }
226
  }
227
+ console.error("Valid start position (floor tile = 1) not found near center!");
228
+ return null; // Fallback
229
  }
230
 
231
 
232
+ // --- Dungeon Meshing (DIRECT ADDITION - NO MERGING) ---
233
+ function createDungeonMeshes_Direct(grid) {
234
+ console.log("Creating meshes directly (no merging)...");
235
+ // Recreate geometries each time to avoid issues with disposed geometries if init is called again
236
+ const floorGeo = new THREE.PlaneGeometry(CELL_SIZE, CELL_SIZE);
237
+ const wallGeoN = new THREE.BoxGeometry(CELL_SIZE, WALL_HEIGHT, 0.1);
238
+ const wallGeoS = new THREE.BoxGeometry(CELL_SIZE, WALL_HEIGHT, 0.1);
239
+ const wallGeoE = new THREE.BoxGeometry(0.1, WALL_HEIGHT, CELL_SIZE);
240
+ const wallGeoW = new THREE.BoxGeometry(0.1, WALL_HEIGHT, CELL_SIZE);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
  for (let y = 0; y < grid.length; y++) {
243
  for (let x = 0; x < grid[y].length; x++) {
244
  if (grid[y][x] === 1) { // If it's a floor cell
245
+ // Create Floor Tile Mesh
246
+ const floorInstance = new THREE.Mesh(floorGeo, floorMaterial); // Use shared geometry instance
247
+ floorInstance.rotation.x = -Math.PI / 2;
248
+ floorInstance.position.set(x * CELL_SIZE + CELL_SIZE / 2, 0, y * CELL_SIZE + CELL_SIZE / 2);
249
+ floorInstance.receiveShadow = true;
250
+ scene.add(floorInstance);
251
+ worldMeshes.push(floorInstance); // Track mesh
252
+ // console.log(`Added floor mesh at ${x},${y}`);
253
 
254
  // Check neighbors for Walls
255
  // North Wall
256
  if (y === 0 || grid[y - 1][x] === 0) {
257
+ const wallInstance = new THREE.Mesh(wallGeoN, wallMaterial);
258
+ wallInstance.position.set(x * CELL_SIZE + CELL_SIZE / 2, WALL_HEIGHT / 2, y * CELL_SIZE);
259
+ wallInstance.castShadow = true; wallInstance.receiveShadow = true;
260
+ scene.add(wallInstance); worldMeshes.push(wallInstance);
261
  }
262
  // South Wall
263
  if (y === grid.length - 1 || grid[y + 1][x] === 0) {
264
+ const wallInstance = new THREE.Mesh(wallGeoS, wallMaterial);
265
+ wallInstance.position.set(x * CELL_SIZE + CELL_SIZE / 2, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE);
266
+ wallInstance.castShadow = true; wallInstance.receiveShadow = true;
267
+ scene.add(wallInstance); worldMeshes.push(wallInstance);
268
  }
269
  // West Wall
270
  if (x === 0 || grid[y][x - 1] === 0) {
271
+ const wallInstance = new THREE.Mesh(wallGeoW, wallMaterial);
272
+ wallInstance.position.set(x * CELL_SIZE, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE / 2);
273
+ wallInstance.castShadow = true; wallInstance.receiveShadow = true;
274
+ scene.add(wallInstance); worldMeshes.push(wallInstance);
275
  }
276
  // East Wall
277
  if (x === grid[y].length - 1 || grid[y][x + 1] === 0) {
278
+ const wallInstance = new THREE.Mesh(wallGeoE, wallMaterial);
279
+ wallInstance.position.set(x * CELL_SIZE + CELL_SIZE, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE / 2);
280
+ wallInstance.castShadow = true; wallInstance.receiveShadow = true;
281
+ scene.add(wallInstance); worldMeshes.push(wallInstance);
282
  }
283
  }
284
  }
285
  }
286
+ // Geometries are shared, no need to dispose here unless we cloned them.
287
+ // If we were cloning: floorGeo.dispose(); wallGeoN.dispose(); ...
288
+ console.log("Direct mesh creation complete.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  }
290
 
291
 
292
+ // --- Player Movement & Collision (No Physics) ---
293
  function handleInputAndMovement(deltaTime) {
294
+ if (!controls || !controls.isLocked) return;
295
 
296
  const speed = PLAYER_SPEED * deltaTime;
297
+ // Reset velocity, we calculate total displacement based on keys
298
+ playerVelocity.x = 0;
299
  playerVelocity.z = 0;
300
 
301
+ // Get camera direction (ignore Y)
302
+ controls.getDirection(playerDirection); // Gets normalized direction vector
303
+ playerDirection.y = 0;
304
  playerDirection.normalize();
305
 
306
+ // Calculate right vector based on camera direction
307
+ const rightDirection = new THREE.Vector3();
308
+ rightDirection.crossVectors(camera.up, playerDirection).normalize(); // camera.up is (0,1,0)
309
 
310
+ // Apply movement based on keys
311
  if (moveForward) playerVelocity.add(playerDirection);
312
  if (moveBackward) playerVelocity.sub(playerDirection);
313
+ if (moveLeft) playerVelocity.sub(rightDirection);
314
+ if (moveRight) playerVelocity.add(rightDirection);
315
 
316
+ // Normalize diagonal velocity if needed and apply speed
317
  if (playerVelocity.lengthSq() > 0) {
318
+ playerVelocity.normalize().multiplyScalar(speed);
319
  }
320
 
321
+ // --- Basic Collision Detection BEFORE moving ---
322
  const currentPos = controls.getObject().position;
323
+ let moveXAllowed = true;
324
+ let moveZAllowed = true;
325
+
326
+ // Check X Collision
327
+ if (playerVelocity.x !== 0) {
328
+ const nextX = currentPos.x + playerVelocity.x;
329
+ // Check slightly ahead in X direction, at feet and head level Z
330
+ const checkGridX = Math.floor((nextX + Math.sign(playerVelocity.x) * PLAYER_RADIUS) / CELL_SIZE);
331
+ const checkGridZFeet = Math.floor((currentPos.z - PLAYER_RADIUS) / CELL_SIZE);
332
+ const checkGridZHead = Math.floor((currentPos.z + PLAYER_RADIUS) / CELL_SIZE);
333
+ if ((dungeonLayout[checkGridZFeet]?.[checkGridX] === 0) || (dungeonLayout[checkGridZHead]?.[checkGridX] === 0)) {
334
+ moveXAllowed = false;
335
+ // console.log(`Collision X at grid ${checkGridX},${checkGridZFeet}/${checkGridZHead}`);
336
+ }
337
  }
338
 
339
+ // Check Z Collision
340
+ if (playerVelocity.z !== 0) {
341
+ const nextZ = currentPos.z + playerVelocity.z;
342
+ // Check slightly ahead in Z direction, at feet and head level X
343
+ const checkGridZ = Math.floor((nextZ + Math.sign(playerVelocity.z) * PLAYER_RADIUS) / CELL_SIZE);
344
+ const checkGridXFeet = Math.floor((currentPos.x - PLAYER_RADIUS) / CELL_SIZE);
345
+ const checkGridXHead = Math.floor((currentPos.x + PLAYER_RADIUS) / CELL_SIZE);
346
+ if ((dungeonLayout[checkGridZ]?.[checkGridXFeet] === 0) || (dungeonLayout[checkGridZ]?.[checkGridXHead] === 0)) {
347
+ moveZAllowed = false;
348
+ // console.log(`Collision Z at grid ${checkGridXFeet}/${checkGridXHead},${checkGridZ}`);
349
+ }
350
  }
351
 
352
+ // Apply movement only if allowed
353
+ if (moveXAllowed) {
354
+ controls.moveRight(playerVelocity.x); // moveRight uses internal right vector, so feed X velocity
355
+ }
356
+ if (moveZAllowed) {
357
+ controls.moveForward(playerVelocity.z); // moveForward uses internal forward vector, so feed Z velocity
358
+ }
359
 
360
  // Keep player at fixed height (no gravity/jump yet)
361
+ controls.getObject().position.y = PLAYER_HEIGHT;
362
+
363
+ // Log position occasionally
364
+ // if (Math.random() < 0.05) console.log("Player Pos:", controls.getObject().position);
365
  }
366
 
367
 
368
  // --- Event Handlers ---
369
  function onKeyDown(event) {
370
+ // console.log("KeyDown:", event.code); // Debug key codes
371
  switch (event.code) {
372
  case 'ArrowUp': case 'KeyW': moveForward = true; break;
373
  case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
374
  case 'ArrowDown': case 'KeyS': moveBackward = true; break;
375
  case 'ArrowRight': case 'KeyD': moveRight = true; break;
376
+ // QWE ZXC movement not implemented in this simplified non-physics version yet
377
+ // Jump/F/Space not implemented yet
378
  }
379
  }
380
 
 
388
  }
389
 
390
  function onWindowResize() {
391
+ if (!camera || !renderer) return;
392
+ console.log("Resizing...");
393
  camera.aspect = window.innerWidth / window.innerHeight;
394
  camera.updateProjectionMatrix();
395
  renderer.setSize(window.innerWidth, window.innerHeight);
396
  }
397
 
398
+ // --- UI Update Functions (Simplified) ---
399
+ function updateUI() {
400
+ // Display basic position for debugging
401
+ if (controls) {
402
+ const pos = controls.getObject().position;
403
+ statsElement.innerHTML = `<span>Pos: ${pos.x.toFixed(1)}, ${pos.y.toFixed(1)}, ${pos.z.toFixed(1)}</span>`;
404
+ }
405
+ // Inventory display can be added later
406
+ inventoryElement.innerHTML = '<em>Inventory N/A</em>';
407
+ }
408
+ function addLog(message, type = "info") {
409
+ const p = document.createElement('p');
410
+ p.classList.add(type); // Add class for styling
411
+ p.textContent = `[${new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })}] ${message}`; // Add timestamp
412
+ logElement.appendChild(p);
413
+ logElement.scrollTop = logElement.scrollHeight; // Auto-scroll
414
+ }
415
+
416
+
417
  // --- Animation Loop ---
418
  function animate() {
419
+ animationFrameId = requestAnimationFrame(animate);
420
 
421
  const delta = clock.getDelta();
422
 
423
+ // Update movement only if controls are locked
424
+ if (controls && controls.isLocked === true) {
425
+ handleInputAndMovement(delta);
426
+ updateUI(); // Update UI less frequently if needed
427
+ }
428
 
429
  renderer.render(scene, camera);
430
  }
431
 
432
  // --- Start ---
433
+ console.log("Attempting to initialize...");
434
+ try {
435
+ init();
436
+ } catch(err) {
437
+ console.error("Initialization failed:", err);
438
+ alert("Error during initialization. Check the console (F12).");
439
+ }
440
 
441
  </script>
442
  </body>