Spaces:
Running
Running
| // Main game script | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Game variables | |
| let scene, camera, renderer, controls; | |
| let player, enemies = [], mazeWalls = [], exit; | |
| let moveDirection = { forward: 0, backward: 0, left: 0, right: 0 }; | |
| let isGameOver = false; | |
| let gameStarted = false; | |
| let playerSpeed = 0.1; | |
| let enemySpeed = 0.08; | |
| let mazeSize = 15; | |
| let cellSize = 3; | |
| let wallHeight = 2; | |
| // Initialize game | |
| init(); | |
| createMaze(); | |
| createPlayer(); | |
| createEnemies(3); | |
| createExit(); | |
| setupControls(); | |
| animate(); | |
| // Event listeners | |
| document.getElementById('restart-btn').addEventListener('click', restartGame); | |
| document.getElementById('next-level-btn').addEventListener('click', nextLevel); | |
| function init() { | |
| // Create scene | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x111111); | |
| // Create camera | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.set(0, 10, 15); | |
| camera.lookAt(0, 0, 0); | |
| // Create renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| document.getElementById('game-container').appendChild(renderer.domElement); | |
| // Add lights | |
| const ambientLight = new THREE.AmbientLight(0x404040); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| directionalLight.position.set(1, 1, 1); | |
| directionalLight.castShadow = true; | |
| scene.add(directionalLight); | |
| // Add fog | |
| scene.fog = new THREE.FogExp2(0x111111, 0.05); | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| } | |
| function createMaze() { | |
| // Simple maze generation - in a real game you'd implement a proper maze algorithm | |
| const wallGeometry = new THREE.BoxGeometry(cellSize, wallHeight, 0.2); | |
| const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x880000 }); | |
| // Create outer walls | |
| const outerWalls = []; | |
| const outerSize = mazeSize * cellSize; | |
| // Floor | |
| const floorGeometry = new THREE.PlaneGeometry(outerSize, outerSize); | |
| const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); | |
| const floor = new THREE.Mesh(floorGeometry, floorMaterial); | |
| floor.rotation.x = -Math.PI / 2; | |
| floor.receiveShadow = true; | |
| scene.add(floor); | |
| // Ceiling | |
| const ceiling = new THREE.Mesh(floorGeometry, floorMaterial); | |
| ceiling.rotation.x = Math.PI / 2; | |
| ceiling.position.y = wallHeight; | |
| scene.add(ceiling); | |
| // Inner maze walls (simplified) | |
| for (let i = 0; i < mazeSize - 1; i++) { | |
| for (let j = 0; j < mazeSize - 1; j++) { | |
| if (Math.random() > 0.7) { | |
| const wall = new THREE.Mesh(wallGeometry, wallMaterial); | |
| wall.position.set( | |
| (i - mazeSize/2) * cellSize + cellSize/2, | |
| wallHeight/2, | |
| (j - mazeSize/2) * cellSize + cellSize/2 | |
| ); | |
| wall.castShadow = true; | |
| wall.receiveShadow = true; | |
| scene.add(wall); | |
| mazeWalls.push(wall); | |
| // Randomly rotate some walls | |
| if (Math.random() > 0.5) { | |
| wall.rotation.y = Math.PI / 2; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| function createPlayer() { | |
| const geometry = new THREE.CylinderGeometry(0.5, 0.5, 1.8, 16); | |
| const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); | |
| player = new THREE.Mesh(geometry, material); | |
| player.position.set(0, 1, 0); | |
| player.castShadow = true; | |
| player.receiveShadow = true; | |
| scene.add(player); | |
| // Add a small directional light to the player to make them stand out | |
| const playerLight = new THREE.PointLight(0x00ff00, 1, 5); | |
| player.add(playerLight); | |
| } | |
| function createEnemies(count) { | |
| const geometry = new THREE.CylinderGeometry(0.5, 0.5, 1.8, 16); | |
| const material = new THREE.MeshStandardMaterial({ color: 0xff0000 }); | |
| for (let i = 0; i < count; i++) { | |
| const enemy = new THREE.Mesh(geometry, material); | |
| // Position enemies at random locations in the maze | |
| enemy.position.set( | |
| (Math.random() - 0.5) * mazeSize * cellSize * 0.8, | |
| 1, | |
| (Math.random() - 0.5) * mazeSize * cellSize * 0.8 | |
| ); | |
| enemy.castShadow = true; | |
| enemy.receiveShadow = true; | |
| // Add red glow | |
| const enemyLight = new THREE.PointLight(0xff0000, 1, 5); | |
| enemy.add(enemyLight); | |
| scene.add(enemy); | |
| enemies.push(enemy); | |
| } | |
| } | |
| function createExit() { | |
| const exitGeometry = new THREE.BoxGeometry(cellSize, 0.1, cellSize); | |
| const exitMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00 }); | |
| exit = new THREE.Mesh(exitGeometry, exitMaterial); | |
| // Position exit at a random edge of the maze | |
| const side = Math.floor(Math.random() * 4); | |
| let x, z; | |
| switch(side) { | |
| case 0: // top | |
| x = (Math.random() - 0.5) * mazeSize * cellSize * 0.8; | |
| z = -mazeSize * cellSize / 2; | |
| break; | |
| case 1: // right | |
| x = mazeSize * cellSize / 2; | |
| z = (Math.random() - 0.5) * mazeSize * cellSize * 0.8; | |
| break; | |
| case 2: // bottom | |
| x = (Math.random() - 0.5) * mazeSize * cellSize * 0.8; | |
| z = mazeSize * cellSize / 2; | |
| break; | |
| case 3: // left | |
| x = -mazeSize * cellSize / 2; | |
| z = (Math.random() - 0.5) * mazeSize * cellSize * 0.8; | |
| break; | |
| } | |
| exit.position.set(x, 0.05, z); | |
| scene.add(exit); | |
| } | |
| function setupControls() { | |
| const forwardBtn = document.getElementById('forward-btn'); | |
| const backwardBtn = document.getElementById('backward-btn'); | |
| const leftBtn = document.getElementById('left-btn'); | |
| const rightBtn = document.getElementById('right-btn'); | |
| // Touch start events | |
| forwardBtn.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| moveDirection.forward = 1; | |
| gameStarted = true; | |
| }); | |
| backwardBtn.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| moveDirection.backward = 1; | |
| gameStarted = true; | |
| }); | |
| leftBtn.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| moveDirection.left = 1; | |
| gameStarted = true; | |
| }); | |
| rightBtn.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| moveDirection.right = 1; | |
| gameStarted = true; | |
| }); | |
| // Touch end events | |
| forwardBtn.addEventListener('touchend', (e) => { | |
| e.preventDefault(); | |
| moveDirection.forward = 0; | |
| }); | |
| backwardBtn.addEventListener('touchend', (e) => { | |
| e.preventDefault(); | |
| moveDirection.backward = 0; | |
| }); | |
| leftBtn.addEventListener('touchend', (e) => { | |
| e.preventDefault(); | |
| moveDirection.left = 0; | |
| }); | |
| rightBtn.addEventListener('touchend', (e) => { | |
| e.preventDefault(); | |
| moveDirection.right = 0; | |
| }); | |
| } | |
| function update() { | |
| if (!gameStarted || isGameOver) return; | |
| // Player movement | |
| const moveForward = moveDirection.forward - moveDirection.backward; | |
| const moveSideways = moveDirection.right - moveDirection.left; | |
| // Apply movement | |
| const speed = playerSpeed; | |
| player.position.x -= Math.sin(player.rotation.y) * moveForward * speed; | |
| player.position.z -= Math.cos(player.rotation.y) * moveForward * speed; | |
| // Apply rotation | |
| player.rotation.y -= moveSideways * speed * 0.5; | |
| // Simple collision detection with walls | |
| const playerRadius = 0.5; | |
| for (const wall of mazeWalls) { | |
| const wallBox = new THREE.Box3().setFromObject(wall); | |
| const playerBox = new THREE.Box3( | |
| new THREE.Vector3(player.position.x - playerRadius, 0, player.position.z - playerRadius), | |
| new THREE.Vector3(player.position.x + playerRadius, 2, player.position.z + playerRadius) | |
| ); | |
| if (wallBox.intersectsBox(playerBox)) { | |
| // Push player back | |
| player.position.x -= moveDirection.x * speed * 2; | |
| player.position.z -= moveDirection.z * speed * 2; | |
| break; | |
| } | |
| } | |
| // Enemy movement | |
| for (const enemy of enemies) { | |
| // Simple AI: move toward player | |
| const direction = new THREE.Vector3( | |
| player.position.x - enemy.position.x, | |
| 0, | |
| player.position.z - enemy.position.z | |
| ).normalize(); | |
| enemy.position.x += direction.x * enemySpeed; | |
| enemy.position.z += direction.z * enemySpeed; | |
| // Simple collision detection between enemies and walls | |
| const enemyRadius = 0.5; | |
| for (const wall of mazeWalls) { | |
| const wallBox = new THREE.Box3().setFromObject(wall); | |
| const enemyBox = new THREE.Box3( | |
| new THREE.Vector3(enemy.position.x - enemyRadius, 0, enemy.position.z - enemyRadius), | |
| new THREE.Vector3(enemy.position.x + enemyRadius, 2, enemy.position.z + enemyRadius) | |
| ); | |
| if (wallBox.intersectsBox(enemyBox)) { | |
| // Push enemy back | |
| enemy.position.x -= direction.x * enemySpeed * 2; | |
| enemy.position.z -= direction.z * enemySpeed * 2; | |
| break; | |
| } | |
| } | |
| // Check for collision with player | |
| const distanceToPlayer = Math.sqrt( | |
| Math.pow(player.position.x - enemy.position.x, 2) + | |
| Math.pow(player.position.z - enemy.position.z, 2) | |
| ); | |
| if (distanceToPlayer < 1) { | |
| gameOver(); | |
| } | |
| } | |
| // Check if player reached exit | |
| const exitBox = new THREE.Box3().setFromObject(exit); | |
| const playerBox = new THREE.Box3( | |
| new THREE.Vector3(player.position.x - 0.5, 0, player.position.z - 0.5), | |
| new THREE.Vector3(player.position.x + 0.5, 2, player.position.z + 0.5) | |
| ); | |
| if (exitBox.intersectsBox(playerBox)) { | |
| winGame(); | |
| } | |
| // Update camera position to follow player | |
| camera.position.x = player.position.x; | |
| camera.position.z = player.position.z + 5; | |
| camera.lookAt(player.position.x, player.position.y, player.position.z); | |
| } | |
| function gameOver() { | |
| isGameOver = true; | |
| document.getElementById('game-over').classList.remove('hidden'); | |
| } | |
| function winGame() { | |
| isGameOver = true; | |
| document.getElementById('win-screen').classList.remove('hidden'); | |
| } | |
| function restartGame() { | |
| // Reset player and enemies | |
| player.position.set(0, 1, 0); | |
| enemies.forEach(enemy => { | |
| enemy.position.set( | |
| (Math.random() - 0.5) * mazeSize * cellSize * 0.8, | |
| 1, | |
| (Math.random() - 0.5) * mazeSize * cellSize * 0.8 | |
| ); | |
| }); | |
| // Reset UI | |
| document.getElementById('game-over').classList.add('hidden'); | |
| document.getElementById('win-screen').classList.add('hidden'); | |
| isGameOver = false; | |
| gameStarted = false; | |
| } | |
| function nextLevel() { | |
| // Clear current maze and enemies | |
| mazeWalls.forEach(wall => scene.remove(wall)); | |
| mazeWalls = []; | |
| enemies.forEach(enemy => scene.remove(enemy)); | |
| enemies = []; | |
| scene.remove(exit); | |
| // Increase difficulty | |
| mazeSize += 2; | |
| enemySpeed += 0.01; | |
| // Create new level | |
| createMaze(); | |
| createEnemies(3 + Math.floor(mazeSize / 5)); | |
| createExit(); | |
| // Reset player | |
| player.position.set(0, 1, 0); | |
| // Reset UI | |
| document.getElementById('win-screen').classList.add('hidden'); | |
| isGameOver = false; | |
| gameStarted = false; | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| update(); | |
| renderer.render(scene, camera); | |
| } | |
| }); |