awacke1's picture
Update index.html
960a9eb verified
raw
history blame
21.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DEBUG - Procedural 3D Dungeon</title>
<style>
body { margin: 0; overflow: hidden; background-color: #000; color: white; font-family: monospace; }
canvas { display: block; }
#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; }
#instructions { width: 50%; text-align: center; padding: 20px; background: rgba(20, 20, 20, 0.8); border-radius: 10px; }
#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; }
</style>
</head>
<body>
<div id="blocker">
<div id="instructions">
<h1>Dungeon Explorer (Debug Mode)</h1>
<p>Click to Enter</p>
<p>(W, A, S, D = Move, MOUSE = Look)</p>
<p>Check F12 Console for Errors!</p>
</div>
</div>
<div id="crosshair">+</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
// BufferGeometryUtils not needed for this debug version
// import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
console.log("Script Start");
// --- Config ---
const DUNGEON_WIDTH = 10; // Smaller grid for debug
const DUNGEON_HEIGHT = 10;
const CELL_SIZE = 5;
const WALL_HEIGHT = 4;
const PLAYER_HEIGHT = 1.6;
const PLAYER_RADIUS = 0.4;
const PLAYER_SPEED = 5.0;
// --- Three.js Setup ---
let scene, camera, renderer;
let controls;
let clock;
let flashlight;
// --- Player State ---
const playerVelocity = new THREE.Vector3();
const playerDirection = new THREE.Vector3();
let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
// --- World Data ---
let dungeonLayout = [];
const worldMeshes = []; // Store refs to added meshes for potential cleanup
// --- DOM Elements ---
const blocker = document.getElementById('blocker');
const instructions = document.getElementById('instructions');
const crosshair = document.getElementById('crosshair');
// --- Materials (Basic Colors) ---
const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x555555 }); // Use Lambert for basic lighting check
const wallMaterial = new THREE.MeshLambertMaterial({ color: 0x884444 });
// --- Initialization ---
function init() {
console.log("--- Initializing Game ---");
clock = new THREE.Clock();
// Clear previous scene if restarting
if (scene) {
console.log("Clearing previous scene objects...");
worldMeshes.forEach(mesh => {
if(mesh.parent) scene.remove(mesh);
if(mesh.geometry) mesh.geometry.dispose();
// Only dispose material if we know it's unique per object
});
worldMeshes.length = 0; // Clear the array
} else {
scene = new THREE.Scene();
}
scene.background = new THREE.Color(0x111111);
scene.fog = new THREE.Fog(0x111111, 10, CELL_SIZE * 6);
// Clear previous renderer if restarting
if (renderer) {
console.log("Disposing previous renderer...");
renderer.dispose();
if (renderer.domElement.parentNode) {
renderer.domElement.parentNode.removeChild(renderer.domElement);
}
}
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true; // Keep shadows enabled
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
console.log("Renderer created/reset.");
// Camera (First Person)
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = PLAYER_HEIGHT;
console.log("Camera created.");
// Lighting
scene.add(new THREE.AmbientLight(0x404040, 0.8)); // Slightly brighter ambient
flashlight = new THREE.SpotLight(0xffffff, 3, 30, Math.PI / 5, 0.4, 1.5);
flashlight.position.set(0, 0, 0); // Relative to camera
flashlight.target.position.set(0, 0, -1); // Relative to camera
flashlight.castShadow = true;
flashlight.shadow.mapSize.width = 1024;
flashlight.shadow.mapSize.height = 1024;
flashlight.shadow.camera.near = 0.5;
flashlight.shadow.camera.far = 30;
camera.add(flashlight);
camera.add(flashlight.target);
scene.add(camera); // Add camera (with light) to scene
console.log("Lighting setup.");
// Pointer Lock Controls
controls = new PointerLockControls(camera, renderer.domElement);
// We don't add controls.getObject() directly to scene IF flashlight is child of camera
// scene.add(controls.getObject()); // Only if camera isn't manually added
blocker.addEventListener('click', () => { controls.lock(); });
controls.addEventListener('lock', () => { instructions.style.display = 'none'; blocker.style.display = 'none'; crosshair.style.display = 'block'; });
controls.addEventListener('unlock', () => { blocker.style.display = 'flex'; instructions.style.display = ''; crosshair.style.display = 'none'; });
console.log("Controls setup.");
// Keyboard Listeners
document.removeEventListener('keydown', onKeyDown); // Remove old listeners if restarting
document.removeEventListener('keyup', onKeyUp);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// Resize Listener
window.removeEventListener('resize', onWindowResize); // Remove old
window.addEventListener('resize', onWindowResize);
// --- Generate FIXED Dungeon ---
console.log("Generating FIXED dungeon layout...");
dungeonLayout = generateFixedDungeonLayout(DUNGEON_WIDTH, DUNGEON_HEIGHT);
console.log("Layout generated, creating meshes...");
createDungeonMeshes_Direct(dungeonLayout); // Use direct mesh addition
console.log("Dungeon meshes created.");
// --- Set Player Start Position ---
const startPos = findStartPosition(dungeonLayout);
if (startPos) {
// Position the camera (which is the player view)
controls.getObject().position.set(startPos.x, PLAYER_HEIGHT, startPos.z);
console.log("Player start position set at:", startPos);
} else {
console.error("Could not find valid start position! Placing at center.");
const fallbackX = (DUNGEON_WIDTH / 2) * CELL_SIZE;
const fallbackZ = (DUNGEON_HEIGHT / 2) * CELL_SIZE;
controls.getObject().position.set(fallbackX, PLAYER_HEIGHT, fallbackZ);
}
// Add Axes Helper for orientation check
const axesHelper = new THREE.AxesHelper(CELL_SIZE);
axesHelper.position.copy(controls.getObject().position); // Place at start pos
axesHelper.position.y = 0.1;
scene.add(axesHelper);
worldMeshes.push(axesHelper); // Track for cleanup
console.log("--- Initialization Complete ---");
animate(); // Start the loop
}
// --- Dungeon Generation (FIXED LAYOUT) ---
function generateFixedDungeonLayout(width, height) {
console.log("Generating FIXED 5x5 layout for debugging...");
const grid = Array(height).fill(null).map(() => Array(width).fill(0)); // All walls
// Simple 5x5 room centered
const cx = Math.floor(width / 2);
const cy = Math.floor(height / 2);
for (let y = cy - 2; y <= cy + 2; y++) {
for (let x = cx - 2; x <= cx + 2; x++) {
if (y >= 0 && y < height && x >= 0 && x < width) {
grid[y][x] = 1; // Floor
}
}
}
// Add a corridor
for (let y = cy + 3; y < height -1 ; y++) {
if (grid[y]) grid[y][cx] = 1;
}
console.log(`Fixed Layout Generated (${width}x${height}). Center: ${cx},${cy}`);
// console.log("Grid:", grid.map(row => row.join('')).join('\n')); // Optional: Log grid visually
return grid;
}
// --- Find Start Position (Same as before) ---
function findStartPosition(grid) {
const startY = Math.floor(grid.length / 2);
const startX = Math.floor(grid[0].length / 2);
console.log(`Searching for start near ${startX},${startY}`);
for (let r = 0; r < Math.max(startX, startY); r++) {
for (let y = startY - r; y <= startY + r; y++) {
for (let x = startX - r; x <= startX + r; x++) {
if (Math.abs(y - startY) === r || Math.abs(x - startX) === r || r === 0) {
if (y >= 0 && y < grid.length && x >= 0 && x < grid[0].length && grid[y][x] === 1) {
console.log(`Found start floor at ${x},${y}`);
return { x: x * CELL_SIZE + CELL_SIZE / 2, z: y * CELL_SIZE + CELL_SIZE / 2 };
}
}
}
}
}
console.error("Valid start position (floor tile = 1) not found near center!");
return null; // Fallback
}
// --- Dungeon Meshing (DIRECT ADDITION - NO MERGING) ---
function createDungeonMeshes_Direct(grid) {
console.log("Creating meshes directly (no merging)...");
// Recreate geometries each time to avoid issues with disposed geometries if init is called again
const floorGeo = new THREE.PlaneGeometry(CELL_SIZE, CELL_SIZE);
const wallGeoN = new THREE.BoxGeometry(CELL_SIZE, WALL_HEIGHT, 0.1);
const wallGeoS = new THREE.BoxGeometry(CELL_SIZE, WALL_HEIGHT, 0.1);
const wallGeoE = new THREE.BoxGeometry(0.1, WALL_HEIGHT, CELL_SIZE);
const wallGeoW = new THREE.BoxGeometry(0.1, WALL_HEIGHT, CELL_SIZE);
for (let y = 0; y < grid.length; y++) {
for (let x = 0; x < grid[y].length; x++) {
if (grid[y][x] === 1) { // If it's a floor cell
// Create Floor Tile Mesh
const floorInstance = new THREE.Mesh(floorGeo, floorMaterial); // Use shared geometry instance
floorInstance.rotation.x = -Math.PI / 2;
floorInstance.position.set(x * CELL_SIZE + CELL_SIZE / 2, 0, y * CELL_SIZE + CELL_SIZE / 2);
floorInstance.receiveShadow = true;
scene.add(floorInstance);
worldMeshes.push(floorInstance); // Track mesh
// console.log(`Added floor mesh at ${x},${y}`);
// Check neighbors for Walls
// North Wall
if (y === 0 || grid[y - 1][x] === 0) {
const wallInstance = new THREE.Mesh(wallGeoN, wallMaterial);
wallInstance.position.set(x * CELL_SIZE + CELL_SIZE / 2, WALL_HEIGHT / 2, y * CELL_SIZE);
wallInstance.castShadow = true; wallInstance.receiveShadow = true;
scene.add(wallInstance); worldMeshes.push(wallInstance);
}
// South Wall
if (y === grid.length - 1 || grid[y + 1][x] === 0) {
const wallInstance = new THREE.Mesh(wallGeoS, wallMaterial);
wallInstance.position.set(x * CELL_SIZE + CELL_SIZE / 2, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE);
wallInstance.castShadow = true; wallInstance.receiveShadow = true;
scene.add(wallInstance); worldMeshes.push(wallInstance);
}
// West Wall
if (x === 0 || grid[y][x - 1] === 0) {
const wallInstance = new THREE.Mesh(wallGeoW, wallMaterial);
wallInstance.position.set(x * CELL_SIZE, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE / 2);
wallInstance.castShadow = true; wallInstance.receiveShadow = true;
scene.add(wallInstance); worldMeshes.push(wallInstance);
}
// East Wall
if (x === grid[y].length - 1 || grid[y][x + 1] === 0) {
const wallInstance = new THREE.Mesh(wallGeoE, wallMaterial);
wallInstance.position.set(x * CELL_SIZE + CELL_SIZE, WALL_HEIGHT / 2, y * CELL_SIZE + CELL_SIZE / 2);
wallInstance.castShadow = true; wallInstance.receiveShadow = true;
scene.add(wallInstance); worldMeshes.push(wallInstance);
}
}
}
}
// Geometries are shared, no need to dispose here unless we cloned them.
// If we were cloning: floorGeo.dispose(); wallGeoN.dispose(); ...
console.log("Direct mesh creation complete.");
}
// --- Player Movement & Collision (No Physics) ---
function handleInputAndMovement(deltaTime) {
if (!controls || !controls.isLocked) return;
const speed = PLAYER_SPEED * deltaTime;
// Reset velocity, we calculate total displacement based on keys
playerVelocity.x = 0;
playerVelocity.z = 0;
// Get camera direction (ignore Y)
controls.getDirection(playerDirection); // Gets normalized direction vector
playerDirection.y = 0;
playerDirection.normalize();
// Calculate right vector based on camera direction
const rightDirection = new THREE.Vector3();
rightDirection.crossVectors(camera.up, playerDirection).normalize(); // camera.up is (0,1,0)
// Apply movement based on keys
if (moveForward) playerVelocity.add(playerDirection);
if (moveBackward) playerVelocity.sub(playerDirection);
if (moveLeft) playerVelocity.sub(rightDirection);
if (moveRight) playerVelocity.add(rightDirection);
// Normalize diagonal velocity if needed and apply speed
if (playerVelocity.lengthSq() > 0) {
playerVelocity.normalize().multiplyScalar(speed);
}
// --- Basic Collision Detection BEFORE moving ---
const currentPos = controls.getObject().position;
let moveXAllowed = true;
let moveZAllowed = true;
// Check X Collision
if (playerVelocity.x !== 0) {
const nextX = currentPos.x + playerVelocity.x;
// Check slightly ahead in X direction, at feet and head level Z
const checkGridX = Math.floor((nextX + Math.sign(playerVelocity.x) * PLAYER_RADIUS) / CELL_SIZE);
const checkGridZFeet = Math.floor((currentPos.z - PLAYER_RADIUS) / CELL_SIZE);
const checkGridZHead = Math.floor((currentPos.z + PLAYER_RADIUS) / CELL_SIZE);
if ((dungeonLayout[checkGridZFeet]?.[checkGridX] === 0) || (dungeonLayout[checkGridZHead]?.[checkGridX] === 0)) {
moveXAllowed = false;
// console.log(`Collision X at grid ${checkGridX},${checkGridZFeet}/${checkGridZHead}`);
}
}
// Check Z Collision
if (playerVelocity.z !== 0) {
const nextZ = currentPos.z + playerVelocity.z;
// Check slightly ahead in Z direction, at feet and head level X
const checkGridZ = Math.floor((nextZ + Math.sign(playerVelocity.z) * PLAYER_RADIUS) / CELL_SIZE);
const checkGridXFeet = Math.floor((currentPos.x - PLAYER_RADIUS) / CELL_SIZE);
const checkGridXHead = Math.floor((currentPos.x + PLAYER_RADIUS) / CELL_SIZE);
if ((dungeonLayout[checkGridZ]?.[checkGridXFeet] === 0) || (dungeonLayout[checkGridZ]?.[checkGridXHead] === 0)) {
moveZAllowed = false;
// console.log(`Collision Z at grid ${checkGridXFeet}/${checkGridXHead},${checkGridZ}`);
}
}
// Apply movement only if allowed
if (moveXAllowed) {
controls.moveRight(playerVelocity.x); // moveRight uses internal right vector, so feed X velocity
}
if (moveZAllowed) {
controls.moveForward(playerVelocity.z); // moveForward uses internal forward vector, so feed Z velocity
}
// Keep player at fixed height (no gravity/jump yet)
controls.getObject().position.y = PLAYER_HEIGHT;
// Log position occasionally
// if (Math.random() < 0.05) console.log("Player Pos:", controls.getObject().position);
}
// --- Event Handlers ---
function onKeyDown(event) {
// console.log("KeyDown:", event.code); // Debug key codes
switch (event.code) {
case 'ArrowUp': case 'KeyW': moveForward = true; break;
case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
case 'ArrowDown': case 'KeyS': moveBackward = true; break;
case 'ArrowRight': case 'KeyD': moveRight = true; break;
// QWE ZXC movement not implemented in this simplified non-physics version yet
// Jump/F/Space not implemented yet
}
}
function onKeyUp(event) {
switch (event.code) {
case 'ArrowUp': case 'KeyW': moveForward = false; break;
case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
case 'ArrowDown': case 'KeyS': moveBackward = false; break;
case 'ArrowRight': case 'KeyD': moveRight = false; break;
}
}
function onWindowResize() {
if (!camera || !renderer) return;
console.log("Resizing...");
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// --- UI Update Functions (Simplified) ---
function updateUI() {
// Display basic position for debugging
if (controls) {
const pos = controls.getObject().position;
statsElement.innerHTML = `<span>Pos: ${pos.x.toFixed(1)}, ${pos.y.toFixed(1)}, ${pos.z.toFixed(1)}</span>`;
}
// Inventory display can be added later
inventoryElement.innerHTML = '<em>Inventory N/A</em>';
}
function addLog(message, type = "info") {
const p = document.createElement('p');
p.classList.add(type); // Add class for styling
p.textContent = `[${new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' })}] ${message}`; // Add timestamp
logElement.appendChild(p);
logElement.scrollTop = logElement.scrollHeight; // Auto-scroll
}
// --- Animation Loop ---
function animate() {
animationFrameId = requestAnimationFrame(animate);
const delta = clock.getDelta();
// Update movement only if controls are locked
if (controls && controls.isLocked === true) {
handleInputAndMovement(delta);
updateUI(); // Update UI less frequently if needed
}
renderer.render(scene, camera);
}
// --- Start ---
console.log("Attempting to initialize...");
try {
init();
} catch(err) {
console.error("Initialization failed:", err);
alert("Error during initialization. Check the console (F12).");
}
</script>
</body>
</html>