= true; aliens.push(alien); scene.add(alien); } function shootPlayerBullet() { if (!canShoot || gameOver) return; const bullet = new THREE.Mesh( new THREE.SphereGeometry(0.2), new THREE.MeshLambertMaterial({ color: 0xffff00 }) ); const shootDirection = new THREE.Vector3(); camera.getWorldDirection(shootDirection); bullet.position.copy(camera.position); bullet.position.add(shootDirection.clone().multiplyScalar(2)); bullet.userData = { type: 'playerBullet', velocity: shootDirection.multiplyScalar(50), life: 100 }; playerBullets.push(bullet); scene.add(bullet); // Weapon cooldown canShoot = false; weaponEl.textContent = 'Reloading...'; setTimeout(() => { canShoot = true; weaponEl.textContent = 'Ready'; }, 1000); } function shootAlienBullet(alien) { const bullet = new THREE.Mesh( new THREE.SphereGeometry(0.15), new THREE.MeshLambertMaterial({ color: 0xff0000 }) ); const playerPos = controls.getObject().position; const alienPos = alien.position; const shootDirection = new THREE.Vector3().subVectors(playerPos, alienPos).normalize(); bullet.position.copy(alienPos); bullet.position.y += 2; bullet.userData = { type: 'alienBullet', velocity: shootDirection.multiplyScalar(30), life: 100 }; alienBullets.push(bullet); scene.add(bullet); } function onMouseDown(event) { if (!controls.isLocked || gameOver) return; if (event.button === 0) { // Left click - destroy objects event.preventDefault(); isLeftClickHeld = true; breakBlock(); } else if (event.button === 2) { // Right click - place cube event.preventDefault(); isRightClickHeld = true; placeCube(); } } function onMouseUp(event) { if (event.button === 0) { isLeftClickHeld = false; } else if (event.button === 2) { isRightClickHeld = false; } } function breakBlock() { const currentTime = Date.now(); if (currentTime - lastBreakTime < breakCooldown) return; raycaster.setFromCamera(new THREE.Vector2(0, 0), camera); const intersects = raycaster.intersectObjects(objects, true); if (intersects.length > 0) { const obj = intersects[0].object; // Can destroy trees, leaves, cubes (blocks you've placed) if (obj.userData.type === 'tree' || obj.userData.type === 'leaves' || obj.userData.type === 'cube') { scene.remove(obj); const index = objects.indexOf(obj); if (index > -1) { objects.splice(index, 1); } lastBreakTime = currentTime; } } } function placeCube() { const currentTime = Date.now(); if (currentTime - lastBuildTime < buildCooldown) return; raycaster.setFromCamera(new THREE.Vector2(0, 0), camera); // Check all scene children so we can build on ground/grass const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { const point = intersects[0].point; const cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshLambertMaterial({ color: 0x000000 }) ); cube.position.copy(point); cube.position.y += 0.5; cube.castShadow = true; cube.userData = { type: 'cube' }; scene.add(cube); objects.push(cube); lastBuildTime = currentTime; } } // Disable context menu document.addEventListener('contextmenu', e => e.preventDefault()); function updateBullets() { // Update player bullets for (let i = playerBullets.length - 1; i >= 0; i--) { const bullet = playerBullets[i]; bullet.position.add(bullet.userData.velocity.clone().multiplyScalar(0.016)); bullet.userData.life--; // Check collision with aliens for (let j = aliens.length - 1; j >= 0; j--) { const alien = aliens[j]; const distance = bullet.position.distanceTo(alien.position); // More accurate hitbox: aliens are ~3 units wide and ~4.5 units tall if (distance < 2.5) { // Hit alien alien.userData.health--; scene.remove(bullet); playerBullets.splice(i, 1); if (alien.userData.health <= 0) { // Kill alien scene.remove(alien); aliens.splice(j, 1); score += 10; scoreEl.textContent = score; // Spawn new alien if (!gameOver) { setTimeout(() => spawnAlien(), 2000); } } break; } } // Remove old bullets if (bullet.userData.life <= 0) { scene.remove(bullet); playerBullets.splice(i, 1); } } // Update alien bullets for (let i = alienBullets.length - 1; i >= 0; i--) { const bullet = alienBullets[i]; bullet.position.add(bullet.userData.velocity.clone().multiplyScalar(0.016)); bullet.userData.life--; // Check collision with player const playerPos = controls.getObject().position; if (bullet.position.distanceTo(playerPos) < 2) { // Hit player playerHealth -= 20; healthEl.textContent = playerHealth; scene.remove(bullet); alienBullets.splice(i, 1); if (playerHealth <= 0) { // Game over gameOver = true; finalScoreEl.textContent = score; gameOverEl.style.display = 'flex'; controls.unlock(); } break; } // Remove old bullets if (bullet.userData.life <= 0) { scene.remove(bullet); alienBullets.splice(i, 1); } } } function updateAliens() { const time = Date.now(); const playerPos = controls.getObject().position; aliens.forEach(alien => { const alienPos = alien.position; const distance = alienPos.distanceTo(playerPos); // AI behavior if (distance < 50) { // Move towards player const direction = new THREE.Vector3().subVectors(playerPos, alienPos).normalize(); alien.position.add(direction.multiplyScalar(alien.userData.moveSpeed * 0.016)); // Look at player alien.lookAt(playerPos); // Shoot at player if (time - alien.userData.lastShot > 2000 + Math.random() * 2000) { shootAlienBullet(alien); alien.userData.lastShot = time; } } }); } function updateClouds() { clouds.forEach(cloud => { cloud.position.x += cloud.userData.speed * 0.016; if (cloud.position.x > 500) { cloud.position.x = -500; } }); } function checkCollisions(newPosition) { const playerBox = new THREE.Box3().setFromCenterAndSize( newPosition, new THREE.Vector3(2, 4, 2) ); for (let obj of objects) { if (obj.userData.solid) { const objBox = new THREE.Box3().setFromObject(obj); if (playerBox.intersectsBox(objBox)) { return true; } } } return false; } function animate() { requestAnimationFrame(animate); if (gameOver) { renderer.render(scene, camera); return; } if (controls.isLocked) { // Movement direction.z = Number(move.forward) - Number(move.backward); direction.x = Number(move.right) - Number(move.left); direction.normalize(); const speed = 15.0; velocity.x = direction.x * speed; velocity.z = direction.z * speed; // Gravity and jumping velocity.y -= 40.0 * 0.016; if (move.jump && canJump) { velocity.y = 15; canJump = false; move.jump = false; } // Get current position const position = controls.getObject().position; const oldPosition = position.clone(); // Calculate new position const newPosition = position.clone(); newPosition.x += velocity.x * 0.016; newPosition.z += velocity.z * 0.016; newPosition.y += velocity.y * 0.016; // Check collision for horizontal movement const horizontalPos = position.clone(); horizontalPos.x += velocity.x * 0.016; horizontalPos.z += velocity.z * 0.016; if (!checkCollisions(horizontalPos)) { controls.moveRight(velocity.x * 0.016); controls.moveForward(velocity.z * 0.016); } // Ground collision raycaster.set(position, new THREE.Vector3(0, -1, 0)); const intersections = raycaster.intersectObjects(scene.children, true); if (intersections.length > 0) { const distance = intersections[0].distance; if (distance < 2 && velocity.y < 0) { velocity.y = 0; canJump = true; position.y = intersections[0].point.y + 2; } } // Apply vertical movement position.y += velocity.y * 0.016; // Prevent falling through ground if (position.y < 1) { position.y = 1; velocity.y = 0; canJump = true; } } updateBullets(); updateAliens(); updateClouds(); // Handle continuous building if (isRightClickHeld && controls.isLocked) { placeCube(); } // Handle continuous breaking if (isLeftClickHeld && controls.isLocked) { breakBlock(); } renderer.render(scene, camera); }