Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Infinite Auto-Generating FPV Open World Simulation</title> | |
| <style> | |
| body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; } | |
| #gameContainer { position: relative; width: 100vw; height: 100vh; } | |
| #instructions { | |
| position: absolute; top: 10px; left: 10px; color: white; | |
| background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="gameContainer"> | |
| <div id="instructions"> | |
| Use WASD to move, and mouse to look around.<br> | |
| Press ESC to toggle mouse lock. | |
| </div> | |
| </div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script> | |
| <script> | |
| const scene = new THREE.Scene(); | |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| const renderer = new THREE.WebGLRenderer(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| document.getElementById('gameContainer').appendChild(renderer.domElement); | |
| scene.background = new THREE.Color(0x87CEEB); // Sky blue | |
| const chunkSize = 16; | |
| const renderDistance = 5; | |
| const chunks = new Map(); | |
| const simplex = new SimplexNoise(); | |
| // Player object | |
| const player = new THREE.Object3D(); | |
| player.position.set(0, 10, 0); | |
| scene.add(player); | |
| player.add(camera); | |
| function getTerrainHeight(x, z) { | |
| return simplex.noise2D(x / 100, z / 100) * 10; | |
| } | |
| function generateTerrain(x, z) { | |
| const geometry = new THREE.PlaneGeometry(chunkSize, chunkSize, chunkSize - 1, chunkSize - 1); | |
| geometry.rotateX(-Math.PI / 2); | |
| const vertices = geometry.attributes.position.array; | |
| for (let i = 0; i < vertices.length; i += 3) { | |
| const vx = vertices[i] + x * chunkSize; | |
| const vz = vertices[i + 2] + z * chunkSize; | |
| vertices[i + 1] = getTerrainHeight(vx, vz); | |
| } | |
| geometry.computeVertexNormals(); | |
| const material = new THREE.MeshPhongMaterial({ color: 0x3d9970, wireframe: false }); | |
| const terrain = new THREE.Mesh(geometry, material); | |
| terrain.position.set(x * chunkSize, 0, z * chunkSize); | |
| return terrain; | |
| } | |
| function generateChunk(x, z) { | |
| const chunk = new THREE.Group(); | |
| chunk.add(generateTerrain(x, z)); | |
| // Add trees | |
| for (let i = 0; i < 5; i++) { | |
| const treeX = Math.random() * chunkSize + x * chunkSize; | |
| const treeZ = Math.random() * chunkSize + z * chunkSize; | |
| const treeY = getTerrainHeight(treeX, treeZ); | |
| const trunk = new THREE.Mesh( | |
| new THREE.CylinderGeometry(0.2, 0.2, 2, 8), | |
| new THREE.MeshPhongMaterial({ color: 0x8B4513 }) | |
| ); | |
| trunk.position.set(treeX, treeY + 1, treeZ); | |
| const leaves = new THREE.Mesh( | |
| new THREE.ConeGeometry(1, 2, 8), | |
| new THREE.MeshPhongMaterial({ color: 0x228B22 }) | |
| ); | |
| leaves.position.set(treeX, treeY + 3, treeZ); | |
| chunk.add(trunk, leaves); | |
| } | |
| return chunk; | |
| } | |
| function updateChunks() { | |
| const playerChunkX = Math.floor(player.position.x / chunkSize); | |
| const playerChunkZ = Math.floor(player.position.z / chunkSize); | |
| for (let x = -renderDistance; x <= renderDistance; x++) { | |
| for (let z = -renderDistance; z <= renderDistance; z++) { | |
| const chunkX = playerChunkX + x; | |
| const chunkZ = playerChunkZ + z; | |
| const chunkKey = `${chunkX},${chunkZ}`; | |
| if (!chunks.has(chunkKey)) { | |
| const chunk = generateChunk(chunkX, chunkZ); | |
| chunks.set(chunkKey, chunk); | |
| scene.add(chunk); | |
| } | |
| } | |
| } | |
| // Remove far chunks | |
| for (const [key, chunk] of chunks) { | |
| const [x, z] = key.split(',').map(Number); | |
| if (Math.abs(x - playerChunkX) > renderDistance || Math.abs(z - playerChunkZ) > renderDistance) { | |
| scene.remove(chunk); | |
| chunks.delete(key); | |
| } | |
| } | |
| } | |
| // Lighting | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); | |
| directionalLight.position.set(1, 1, 1); | |
| scene.add(directionalLight); | |
| // Improved movement controls | |
| const moveSpeed = 0.1; | |
| const movement = new THREE.Vector3(); | |
| const playerDirection = new THREE.Vector3(); | |
| function handleMovement() { | |
| movement.set(0, 0, 0); | |
| if (moveForward) movement.z -= 1; | |
| if (moveBackward) movement.z += 1; | |
| if (moveLeft) movement.x -= 1; | |
| if (moveRight) movement.x += 1; | |
| movement.normalize().multiplyScalar(moveSpeed); | |
| player.getWorldDirection(playerDirection); | |
| playerDirection.y = 0; | |
| playerDirection.normalize(); | |
| const sideways = new THREE.Vector3(-playerDirection.z, 0, playerDirection.x); | |
| player.position.addScaledVector(playerDirection, movement.z); | |
| player.position.addScaledVector(sideways, movement.x); | |
| // Adjust player height based on terrain | |
| const terrainHeight = getTerrainHeight(player.position.x, player.position.z); | |
| player.position.y = terrainHeight + 2; // Keep player 2 units above terrain | |
| } | |
| let moveForward = false; | |
| let moveBackward = false; | |
| let moveLeft = false; | |
| let moveRight = false; | |
| const onKeyDown = (event) => { | |
| switch (event.code) { | |
| case 'KeyW': moveForward = true; break; | |
| case 'KeyS': moveBackward = true; break; | |
| case 'KeyA': moveLeft = true; break; | |
| case 'KeyD': moveRight = true; break; | |
| } | |
| }; | |
| const onKeyUp = (event) => { | |
| switch (event.code) { | |
| case 'KeyW': moveForward = false; break; | |
| case 'KeyS': moveBackward = false; break; | |
| case 'KeyA': moveLeft = false; break; | |
| case 'KeyD': moveRight = false; break; | |
| } | |
| }; | |
| document.addEventListener('keydown', onKeyDown); | |
| document.addEventListener('keyup', onKeyUp); | |
| const gameContainer = document.getElementById('gameContainer'); | |
| let isLocked = false; | |
| gameContainer.addEventListener('click', () => { | |
| if (!isLocked) { | |
| gameContainer.requestPointerLock(); | |
| } | |
| }); | |
| document.addEventListener('pointerlockchange', () => { | |
| isLocked = document.pointerLockElement === gameContainer; | |
| }); | |
| document.addEventListener('mousemove', (event) => { | |
| if (isLocked) { | |
| player.rotation.y -= event.movementX * 0.002; | |
| camera.rotation.x -= event.movementY * 0.002; | |
| camera.rotation.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, camera.rotation.x)); | |
| } | |
| }); | |
| const animate = () => { | |
| requestAnimationFrame(animate); | |
| handleMovement(); | |
| updateChunks(); | |
| renderer.render(scene, camera); | |
| }; | |
| animate(); | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| document.addEventListener('keydown', (event) => { | |
| if (event.key === 'Escape') { | |
| if (isLocked) { | |
| document.exitPointerLock(); | |
| } else { | |
| gameContainer.requestPointerLock(); | |
| } | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |