Spaces:
Running
Running
Update index.html
Browse files- index.html +122 -407
index.html
CHANGED
@@ -3,441 +3,156 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
-
<title>
|
7 |
<style>
|
8 |
-
body {
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
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>
|
25 |
-
|
26 |
-
<script type="importmap">
|
27 |
-
{
|
28 |
-
"imports": {
|
29 |
-
"three": "https://unpkg.com/[email protected]/build/three.module.js",
|
30 |
-
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
|
31 |
-
}
|
32 |
}
|
33 |
-
</script>
|
34 |
-
|
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 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
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 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
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 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
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 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
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 |
-
|
293 |
-
|
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 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
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 |
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
}
|
389 |
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
camera.aspect = window.innerWidth / window.innerHeight;
|
394 |
-
camera.updateProjectionMatrix();
|
395 |
-
renderer.setSize(window.innerWidth, window.innerHeight);
|
396 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
397 |
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
407 |
}
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
logElement.appendChild(p);
|
413 |
-
logElement.scrollTop = logElement.scrollHeight; // Auto-scroll
|
414 |
-
}
|
415 |
|
416 |
|
417 |
-
|
418 |
-
function animate() {
|
419 |
-
animationFrameId = requestAnimationFrame(animate);
|
420 |
|
421 |
-
|
|
|
|
|
422 |
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
428 |
|
429 |
-
|
|
|
|
|
|
|
|
|
|
|
430 |
}
|
|
|
431 |
|
432 |
-
|
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>
|
443 |
</html>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Choose Your Own 3D Adventure</title>
|
7 |
<style>
|
8 |
+
body {
|
9 |
+
font-family: 'Courier New', monospace;
|
10 |
+
background-color: #222; /* Dark background */
|
11 |
+
color: #eee; /* Light text */
|
12 |
+
margin: 0;
|
13 |
+
padding: 0;
|
14 |
+
overflow: hidden; /* Prevent scrollbars from Three.js */
|
15 |
+
display: flex;
|
16 |
+
flex-direction: column;
|
17 |
+
height: 100vh;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
+
#game-container {
|
21 |
+
display: flex;
|
22 |
+
flex-grow: 1; /* Allow container to fill space */
|
23 |
+
overflow: hidden; /* Contain children */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
}
|
25 |
|
26 |
+
#scene-container {
|
27 |
+
flex-grow: 3; /* Give more space to 3D view */
|
28 |
+
position: relative; /* For potential overlays */
|
29 |
+
border-right: 2px solid #555;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
|
32 |
+
#ui-container {
|
33 |
+
flex-grow: 2; /* Space for UI elements */
|
34 |
+
padding: 20px;
|
35 |
+
overflow-y: auto; /* Allow scrolling for long text/options */
|
36 |
+
background-color: #333;
|
37 |
+
display: flex;
|
38 |
+
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
}
|
40 |
|
41 |
+
#story-title {
|
42 |
+
color: #ffcc66; /* Goldish title */
|
43 |
+
margin-top: 0;
|
44 |
+
border-bottom: 1px solid #555;
|
45 |
+
padding-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
}
|
47 |
|
48 |
+
#story-content {
|
49 |
+
margin-bottom: 20px;
|
50 |
+
line-height: 1.6;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
}
|
52 |
|
53 |
+
#choices-container {
|
54 |
+
margin-top: auto; /* Push choices towards the bottom */
|
55 |
+
padding-top: 15px;
|
56 |
+
border-top: 1px solid #555;
|
57 |
+
}
|
58 |
|
59 |
+
#choices-container h3 {
|
60 |
+
margin-top: 0;
|
61 |
+
margin-bottom: 10px;
|
62 |
+
color: #aaa;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
}
|
64 |
|
65 |
+
.choice-button {
|
66 |
+
display: block;
|
67 |
+
width: calc(100% - 20px); /* Adjust width */
|
68 |
+
padding: 10px;
|
69 |
+
margin-bottom: 10px;
|
70 |
+
background-color: #555;
|
71 |
+
color: #eee;
|
72 |
+
border: 1px solid #777;
|
73 |
+
border-radius: 5px;
|
74 |
+
cursor: pointer;
|
75 |
+
text-align: left;
|
76 |
+
font-family: 'Courier New', monospace;
|
77 |
+
font-size: 1em;
|
78 |
+
transition: background-color 0.2s;
|
79 |
}
|
80 |
|
81 |
+
.choice-button:hover {
|
82 |
+
background-color: #d4a017; /* Gold hover */
|
83 |
+
color: #222;
|
|
|
|
|
|
|
84 |
}
|
85 |
+
.choice-button:disabled {
|
86 |
+
background-color: #444;
|
87 |
+
color: #888;
|
88 |
+
cursor: not-allowed;
|
89 |
+
border-color: #666;
|
90 |
+
}
|
91 |
|
92 |
+
#stats-inventory-container {
|
93 |
+
margin-top: 20px;
|
94 |
+
padding-top: 15px;
|
95 |
+
border-top: 1px solid #555;
|
96 |
+
font-size: 0.9em;
|
97 |
+
}
|
98 |
+
#stats-display, #inventory-display {
|
99 |
+
margin-bottom: 10px;
|
100 |
+
}
|
101 |
+
#stats-display span, #inventory-display span {
|
102 |
+
display: inline-block;
|
103 |
+
background-color: #444;
|
104 |
+
padding: 3px 8px;
|
105 |
+
border-radius: 15px;
|
106 |
+
margin-right: 8px;
|
107 |
+
margin-bottom: 5px; /* Wrap nicely */
|
108 |
+
border: 1px solid #666;
|
109 |
}
|
110 |
+
#inventory-display .item-quest { background-color: #666030; border-color: #999048;}
|
111 |
+
#inventory-display .item-weapon { background-color: #663030; border-color: #994848;}
|
112 |
+
#inventory-display .item-armor { background-color: #306630; border-color: #489948;}
|
113 |
+
#inventory-display .item-spell { background-color: #303066; border-color: #484899;}
|
|
|
|
|
|
|
114 |
|
115 |
|
116 |
+
canvas { display: block; } /* Prevent extra space below canvas */
|
|
|
|
|
117 |
|
118 |
+
</style>
|
119 |
+
</head>
|
120 |
+
<body>
|
121 |
|
122 |
+
<div id="game-container">
|
123 |
+
<div id="scene-container"></div>
|
124 |
+
|
125 |
+
<div id="ui-container">
|
126 |
+
<h2 id="story-title">Loading Adventure...</h2>
|
127 |
+
<div id="story-content">
|
128 |
+
<p>Please wait while the adventure loads.</p>
|
129 |
+
</div>
|
130 |
+
|
131 |
+
<div id="stats-inventory-container">
|
132 |
+
<div id="stats-display">
|
133 |
+
</div>
|
134 |
+
<div id="inventory-display">
|
135 |
+
</div>
|
136 |
+
</div>
|
137 |
+
|
138 |
+
<div id="choices-container">
|
139 |
+
<h3>What will you do?</h3>
|
140 |
+
<div id="choices">
|
141 |
+
</div>
|
142 |
+
</div>
|
143 |
+
</div>
|
144 |
+
</div>
|
145 |
|
146 |
+
<script type="importmap">
|
147 |
+
{
|
148 |
+
"imports": {
|
149 |
+
"three": "https://unpkg.com/[email protected]/build/three.module.js",
|
150 |
+
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
|
151 |
+
}
|
152 |
}
|
153 |
+
</script>
|
154 |
|
155 |
+
<script type="module" src="game.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
|
|
157 |
</body>
|
158 |
</html>
|