// Game State let scene, camera, renderer; let player, playerModel; let moveSpeed = 0.1; let runSpeed = 0.2; let jumpHeight = 0.3; let isJumping = false; let velocity = { x: 0, y: 0, z: 0 }; let keys = {}; let mouseX = 0, mouseY = 0; let isPaused = false; let gameStartTime = Date.now(); let buildings = []; let ground; let clock = new THREE.Clock(); // Initialize Game function init() { // Create Scene scene = new THREE.Scene(); scene.fog = new THREE.Fog(0x87CEEB, 10, 100); scene.background = new THREE.Color(0x87CEEB); // Setup Camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 5, 10); // Setup Renderer renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('gameCanvas'), antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Create Lighting setupLighting(); // Create World createGround(); createBuildings(); createPlayer(); // Setup Controls setupControls(); // Start Game Loop animate(); // Hide Loading Screen setTimeout(() => { document.getElementById('loadingScreen').style.display = 'none'; }, 2000); } function setupLighting() { // Ambient Light const ambientLight = new THREE.AmbientLight(0x404040, 0.6); scene.add(ambientLight); // Directional Light (Sun) const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(50, 100, 50); directionalLight.castShadow = true; directionalLight.shadow.camera.left = -50; directionalLight.shadow.camera.right = 50; directionalLight.shadow.camera.top = 50; directionalLight.shadow.camera.bottom = -50; directionalLight.shadow.camera.near = 0.1; directionalLight.shadow.camera.far = 200; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; scene.add(directionalLight); } function createGround() { const groundGeometry = new THREE.PlaneGeometry(200, 200); const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x3a5f3a, side: THREE.DoubleSide }); ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground); // Add road const roadGeometry = new THREE.PlaneGeometry(10, 200); const roadMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 }); const road = new THREE.Mesh(roadGeometry, roadMaterial); road.rotation.x = -Math.PI / 2; road.position.y = 0.01; road.receiveShadow = true; scene.add(road); } function createBuildings() { const buildingPositions = [ { x: -20, z: -20, height: 30, width: 10, depth: 10 }, { x: 20, z: -20, height: 25, width: 12, depth: 8 }, { x: -20, z: 20, height: 35, width: 8, depth: 12 }, { x: 20, z: 20, height: 40, width: 15, depth: 10 }, { x: -40, z: 0, height: 20, width: 10, depth: 10 }, { x: 40, z: 0, height: 28, width: 10, depth: 10 }, { x: 0, z: -40, height: 32, width: 12, depth: 12 }, { x: 0, z: 40, height: 24, width: 10, depth: 10 } ]; buildingPositions.forEach(pos => { const buildingGeometry = new THREE.BoxGeometry(pos.width, pos.height, pos.depth); const buildingMaterial = new THREE.MeshLambertMaterial({ color: new THREE.Color().setHSL(Math.random() * 0.1 + 0.5, 0.5, 0.6) }); const building = new THREE.Mesh(buildingGeometry, buildingMaterial); building.position.set(pos.x, pos.height / 2, pos.z); building.castShadow = true; building.receiveShadow = true; buildings.push(building); scene.add(building); }); } function createPlayer() { // Player group player = new THREE.Group(); // Body const bodyGeometry = new THREE.CapsuleGeometry(0.5, 1.5, 4, 8); const bodyMaterial = new THREE.MeshLambertMaterial({ color: 0x4169e1 }); const body = new THREE.Mesh(bodyGeometry, bodyMaterial); body.position.y = 1.5; body.castShadow = true; player.add(body); // Head const headGeometry = new THREE.SphereGeometry(0.3, 16, 16); const headMaterial = new THREE.MeshLambertMaterial({ color: 0xffdbac }); const head = new THREE.Mesh(headGeometry, headMaterial); head.position.y = 2.7; head.castShadow = true; player.add(head); player.position.set(0, 0, 0); playerModel = player; scene.add(player); } function setupControls() { // Keyboard controls document.addEventListener('keydown', (event) => { keys[event.code] = true; if (event.code === 'Space' && !isJumping) { velocity.y = jumpHeight; isJumping = true; } }); document.addEventListener('keyup', (event) => { keys[event.code] = false; }); // Mouse controls document.addEventListener('mousemove', (event) => { mouseX = (event.clientX / window.innerWidth) * 2 - 1; mouseY = -(event.clientY / window.innerHeight) * 2 + 1; }); // Window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // UI Controls document.getElementById('startButton').addEventListener('click', () => { document.getElementById('startScreen').style.display = 'none'; document.getElementById('hud').classList.remove('hidden'); document.body.requestPointerLock(); }); document.getElementById('togglePause').addEventListener('click', () => { isPaused = !isPaused; const btn = document.getElementById('togglePause'); btn.innerHTML = isPaused ? 'Resume' : 'Pause'; feather.replace(); }); } function updatePlayer() { if (isPaused) return; const speed = keys['ShiftLeft'] ? runSpeed : moveSpeed; const moveVector = new THREE.Vector3(); // Calculate movement if (keys['KeyW']) moveVector.z -= speed; if (keys['KeyS']) moveVector.z += speed; if (keys['KeyA']) moveVector.x -= speed; if (keys['KeyD']) moveVector.x += speed; // Apply movement relative to camera direction const angle = camera.rotation.y; const rotatedVector = moveVector.applyAxisAngle(new THREE.Vector3(0, 1, 0), angle); player.position.x += rotatedVector.x; player.position.z += rotatedVector.z; // Apply gravity velocity.y -= 0.015; player.position.y += velocity.y; // Ground collision if (player.position.y <= 0) { player.position.y = 0; velocity.y = 0; isJumping = false; } // Building collision (simple) buildings.forEach(building => { const distance = player.position.distanceTo(building.position); const minDistance = 5; // Simple collision radius if (distance < minDistance) { const direction = new THREE.Vector3() .subVectors(player.position, building.position) .normalize(); player.position.copy(building.position).add(direction.multiplyScalar(minDistance)); } }); // Update camera to follow player const cameraOffset = new THREE.Vector3(0, 5, 10); cameraOffset.applyAxisAngle(new THREE.Vector3(0, 1, 0), angle); camera.position.copy(player.position).add(cameraOffset); camera.lookAt(player.position); // Camera rotation with mouse camera.rotation.y = mouseX * Math.PI; camera.rotation.x = mouseY * Math.PI / 4; // Update HUD updateHUD(); } function updateHUD() { // Position document.getElementById('position').textContent = `${Math.round(player.position.x)}, ${Math.round(player.position.z)}`; // Speed const currentSpeed = Math.sqrt(velocity.x ** 2 + velocity.z ** 2) * 100; document.getElementById('speed').textContent = Math.round(currentSpeed); // Game Time const elapsed = Date.now() - gameStartTime; const minutes = Math.floor(elapsed / 60000); const seconds = Math.floor((elapsed % 60000) / 1000); document.getElementById('gameTime').textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; // Update mini map updateMiniMap(); } function updateMiniMap() { const canvas = document.getElementById('miniMap'); const ctx = canvas.getContext('2d'); canvas.width = 192; canvas.height = 192; // Clear ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillRect(0, 0, 192, 192); // Draw player position const mapX = (player.position.x + 100) * 0.96; const mapZ = (player.position.z + 100) * 0.96; ctx.fillStyle = '#4ade80'; ctx.beginPath(); ctx.arc(mapX, mapZ, 3, 0, Math.PI * 2); ctx.fill(); // Draw buildings on map ctx.fillStyle = '#6b7280'; buildings.forEach(building => { const bX = (building.position.x + 100) * 0.96; const bZ = (building.position.z + 100) * 0.96; ctx.fillRect(bX - 5, bZ - 5, 10, 10); }); } function animate() { requestAnimationFrame(animate); if (!isPaused) { updatePlayer(); // Animate player model (simple bobbing) if (keys['KeyW'] || keys['KeyS'] || keys['KeyA'] || keys['KeyD']) { player.rotation.y = camera.rotation.y; player.position.y = Math.sin(Date.now() * 0.01) * 0.05; } } renderer.render(scene, camera); } // Start the game when page loads window.addEventListener('load', init);