awacke1 commited on
Commit
b079db3
·
verified ·
1 Parent(s): 38289dd

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +411 -108
index.html CHANGED
@@ -3,137 +3,440 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>3D Dungeon Explorer</title>
7
  <style>
8
- body {
9
- font-family: 'Courier New', monospace;
10
- background-color: #1a1a1a;
11
- color: #e0e0e0;
12
- margin: 0;
13
- padding: 0;
14
- overflow: hidden;
15
  display: flex;
16
- flex-direction: column;
17
- height: 100vh;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  }
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- #game-container {
21
- display: flex;
22
- flex-grow: 1;
23
- overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
- #scene-container {
27
- flex-grow: 3;
28
- position: relative;
29
- border-right: 2px solid #444;
30
- background-color: #000;
31
- cursor: crosshair; /* Indicate interaction area */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
- #ui-container {
35
- flex-grow: 1; /* Smaller UI panel */
36
- padding: 15px;
37
- overflow-y: auto;
38
- background-color: #2a2a2a;
39
- display: flex;
40
- flex-direction: column;
41
- border-left: 1px solid #444;
42
- min-width: 250px; /* Ensure UI doesn't get too small */
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
 
45
- .ui-section {
46
- margin-bottom: 15px;
47
- padding-bottom: 10px;
48
- border-bottom: 1px solid #444;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
- .ui-section:last-child {
51
- border-bottom: none;
52
- margin-bottom: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
 
55
- .ui-section h4 {
56
- margin: 0 0 8px 0;
57
- color: #aaa;
58
- font-size: 0.9em;
59
- text-transform: uppercase;
60
- letter-spacing: 1px;
 
 
 
 
61
  }
62
 
63
- #stats-display, #inventory-display, #log-display {
64
- font-size: 0.9em;
65
- line-height: 1.6;
 
 
 
 
66
  }
67
- #log-display {
68
- flex-grow: 1; /* Allow log to fill space */
69
- background-color: #1f1f1f;
70
- padding: 10px;
71
- border-radius: 3px;
72
- overflow-y: auto; /* Scroll log messages */
73
- max-height: 300px; /* Limit log height */
74
  }
75
- #log-display p { margin: 0 0 5px 0; font-size: 0.9em; }
76
- #log-display .pickup { color: #ffd700; } /* Gold for pickup */
77
- #log-display .combat { color: #ff6b6b; } /* Red for combat */
78
- #log-display .info { color: #aaaaaa; } /* Grey for info */
79
-
80
- #stats-display span, #inventory-display span {
81
- display: inline-block;
82
- background-color: #3a3a3a;
83
- padding: 3px 10px;
84
- border-radius: 15px;
85
- margin-right: 8px;
86
- margin-bottom: 6px;
87
- border: 1px solid #555;
88
- white-space: nowrap;
89
- }
90
- #inventory-display span { cursor: help; }
91
- /* Item type colors from previous version */
92
- #inventory-display .item-quest { background-color: #666030; border-color: #999048;}
93
- #inventory-display .item-weapon { background-color: #663030; border-color: #994848;}
94
- #inventory-display .item-armor { background-color: #306630; border-color: #489948;}
95
- #inventory-display .item-spell { background-color: #303066; border-color: #484899;}
96
- #inventory-display .item-consumable { background-color: #543066; border-color: #7d4899;}
97
- #inventory-display .item-unknown { background-color: #555; border-color: #777;}
98
 
 
 
 
99
 
100
- canvas { display: block; }
101
- </style>
102
- </head>
103
- <body>
104
 
105
- <div id="game-container">
106
- <div id="scene-container"></div>
107
-
108
- <div id="ui-container">
109
- <div class="ui-section">
110
- <h4>Status</h4>
111
- <div id="stats-display">HP: 30/30 Str: 7</div>
112
- </div>
113
- <div class="ui-section">
114
- <h4>Inventory</h4>
115
- <div id="inventory-display"><em>Empty</em></div>
116
- </div>
117
- <div class="ui-section" style="flex-grow: 1; display: flex; flex-direction: column;"> <h4>Log</h4>
118
- <div id="log-display">
119
- <p class="info">Welcome! Use WASD or Arrows to move.</p>
120
- <p class="info">Spacebar to interact/attack.</p>
121
- </div>
122
- </div>
123
- </div>
124
- </div>
125
 
126
- <script type="importmap">
127
- {
128
- "imports": {
129
- "three": "https://unpkg.com/[email protected]/build/three.module.js",
130
- "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/",
131
- "cannon-es": "https://unpkg.com/[email protected]/dist/cannon-es.js"
132
- }
133
  }
134
- </script>
135
 
136
- <script type="module" src="game.js"></script>
 
137
 
 
138
  </body>
139
  </html>
 
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>
51
 
52
+ <script type="importmap">
53
+ {
54
+ "imports": {
55
+ "three": "https://unpkg.com/[email protected]/build/three.module.js",
56
+ "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
57
+ }
58
+ }
59
+ </script>
60
+
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
 
409
+ function onKeyUp(event) {
410
+ switch (event.code) {
411
+ case 'ArrowUp': case 'KeyW': moveForward = false; break;
412
+ case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
413
+ case 'ArrowDown': case 'KeyS': moveBackward = false; break;
414
+ case 'ArrowRight': case 'KeyD': moveRight = false; break;
415
+ }
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>
442
  </html>