270 lines
10 KiB
HTML
270 lines
10 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="UTF-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
<title>Three.js City Walker</title>
|
||
|
<style>
|
||
|
body {
|
||
|
margin: 0;
|
||
|
overflow: hidden;
|
||
|
font-family: Arial, sans-serif;
|
||
|
}
|
||
|
#info {
|
||
|
position: absolute;
|
||
|
top: 10px;
|
||
|
left: 10px;
|
||
|
color: white;
|
||
|
background: rgba(0,0,0,0.7);
|
||
|
padding: 10px;
|
||
|
border-radius: 5px;
|
||
|
font-size: 14px;
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<div id="info">
|
||
|
Use WASD or Arrow Keys to move<br>
|
||
|
Mouse to look around<br>
|
||
|
Click to lock pointer
|
||
|
</div>
|
||
|
|
||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||
|
<script>
|
||
|
// Scene setup
|
||
|
const scene = new THREE.Scene();
|
||
|
scene.background = new THREE.Color(0x87CEEB); // Sky blue
|
||
|
scene.fog = new THREE.Fog(0x87CEEB, 10, 500);
|
||
|
|
||
|
// Camera
|
||
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||
|
camera.position.set(0, 1.6, 10); // Eye height
|
||
|
|
||
|
// Renderer
|
||
|
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
|
renderer.shadowMap.enabled = true;
|
||
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||
|
document.body.appendChild(renderer.domElement);
|
||
|
|
||
|
// Lighting
|
||
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||
|
scene.add(ambientLight);
|
||
|
|
||
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||
|
directionalLight.position.set(50, 100, 50);
|
||
|
directionalLight.castShadow = true;
|
||
|
directionalLight.shadow.camera.left = -100;
|
||
|
directionalLight.shadow.camera.right = 100;
|
||
|
directionalLight.shadow.camera.top = 100;
|
||
|
directionalLight.shadow.camera.bottom = -100;
|
||
|
directionalLight.shadow.camera.near = 0.1;
|
||
|
directionalLight.shadow.camera.far = 200;
|
||
|
directionalLight.shadow.mapSize.width = 2048;
|
||
|
directionalLight.shadow.mapSize.height = 2048;
|
||
|
scene.add(directionalLight);
|
||
|
|
||
|
// Ground
|
||
|
const groundGeometry = new THREE.PlaneGeometry(200, 200);
|
||
|
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x3a3a3a });
|
||
|
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
||
|
ground.rotation.x = -Math.PI / 2;
|
||
|
ground.receiveShadow = true;
|
||
|
scene.add(ground);
|
||
|
|
||
|
// Create buildings
|
||
|
const buildings = [];
|
||
|
const buildingColors = [0x8B4513, 0x696969, 0x778899, 0x2F4F4F, 0x483D8B];
|
||
|
|
||
|
function createBuilding(x, z, width, height, depth, color) {
|
||
|
const geometry = new THREE.BoxGeometry(width, height, depth);
|
||
|
const material = new THREE.MeshLambertMaterial({ color: color });
|
||
|
const building = new THREE.Mesh(geometry, material);
|
||
|
building.position.set(x, height / 2, z);
|
||
|
building.castShadow = true;
|
||
|
building.receiveShadow = true;
|
||
|
scene.add(building);
|
||
|
buildings.push(building);
|
||
|
|
||
|
// Add windows (simple emissive rectangles)
|
||
|
const windowMaterial = new THREE.MeshBasicMaterial({ color: 0xffff99, emissive: 0xffff99 });
|
||
|
const windowSize = 0.8;
|
||
|
const windowSpacing = 2;
|
||
|
|
||
|
for (let floor = 1; floor < height / 3; floor++) {
|
||
|
for (let i = 0; i < width / windowSpacing - 1; i++) {
|
||
|
const windowGeometry = new THREE.PlaneGeometry(windowSize, windowSize);
|
||
|
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
|
||
|
window1.position.set(x - width/2 + (i + 1) * windowSpacing, floor * 3, z + depth/2 + 0.01);
|
||
|
scene.add(window1);
|
||
|
|
||
|
const window2 = new THREE.Mesh(windowGeometry, windowMaterial);
|
||
|
window2.position.set(x - width/2 + (i + 1) * windowSpacing, floor * 3, z - depth/2 - 0.01);
|
||
|
window2.rotation.y = Math.PI;
|
||
|
scene.add(window2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create city layout
|
||
|
createBuilding(-20, -20, 10, 25, 10, buildingColors[0]);
|
||
|
createBuilding(20, -20, 8, 30, 8, buildingColors[1]);
|
||
|
createBuilding(-20, 20, 12, 20, 12, buildingColors[2]);
|
||
|
createBuilding(20, 20, 15, 35, 15, buildingColors[3]);
|
||
|
createBuilding(0, 0, 10, 40, 10, buildingColors[4]);
|
||
|
createBuilding(-40, 0, 8, 15, 8, buildingColors[0]);
|
||
|
createBuilding(40, 0, 10, 25, 10, buildingColors[1]);
|
||
|
createBuilding(0, -40, 12, 18, 12, buildingColors[2]);
|
||
|
createBuilding(0, 40, 8, 22, 8, buildingColors[3]);
|
||
|
createBuilding(-40, -40, 10, 28, 10, buildingColors[4]);
|
||
|
createBuilding(40, -40, 8, 20, 8, buildingColors[0]);
|
||
|
createBuilding(-40, 40, 15, 32, 15, buildingColors[1]);
|
||
|
createBuilding(40, 40, 10, 25, 10, buildingColors[2]);
|
||
|
|
||
|
// Add some street lights
|
||
|
function createStreetLight(x, z) {
|
||
|
const poleGeometry = new THREE.CylinderGeometry(0.1, 0.1, 4);
|
||
|
const poleMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
|
||
|
const pole = new THREE.Mesh(poleGeometry, poleMaterial);
|
||
|
pole.position.set(x, 2, z);
|
||
|
pole.castShadow = true;
|
||
|
scene.add(pole);
|
||
|
|
||
|
const lightGeometry = new THREE.SphereGeometry(0.3);
|
||
|
const lightMaterial = new THREE.MeshBasicMaterial({ color: 0xffffaa, emissive: 0xffffaa });
|
||
|
const lightBulb = new THREE.Mesh(lightGeometry, lightMaterial);
|
||
|
lightBulb.position.set(x, 4, z);
|
||
|
scene.add(lightBulb);
|
||
|
|
||
|
const pointLight = new THREE.PointLight(0xffffaa, 0.5, 10);
|
||
|
pointLight.position.set(x, 4, z);
|
||
|
scene.add(pointLight);
|
||
|
}
|
||
|
|
||
|
createStreetLight(-30, 0);
|
||
|
createStreetLight(30, 0);
|
||
|
createStreetLight(0, -30);
|
||
|
createStreetLight(0, 30);
|
||
|
|
||
|
// Movement controls
|
||
|
const moveSpeed = 0.1;
|
||
|
const lookSpeed = 0.002;
|
||
|
let moveForward = false;
|
||
|
let moveBackward = false;
|
||
|
let moveLeft = false;
|
||
|
let moveRight = false;
|
||
|
|
||
|
const velocity = new THREE.Vector3();
|
||
|
const direction = new THREE.Vector3();
|
||
|
|
||
|
// Pointer lock controls
|
||
|
let isPointerLocked = false;
|
||
|
const euler = new THREE.Euler(0, 0, 0, 'YXZ');
|
||
|
|
||
|
renderer.domElement.addEventListener('click', () => {
|
||
|
renderer.domElement.requestPointerLock();
|
||
|
});
|
||
|
|
||
|
document.addEventListener('pointerlockchange', () => {
|
||
|
isPointerLocked = document.pointerLockElement === renderer.domElement;
|
||
|
});
|
||
|
|
||
|
document.addEventListener('mousemove', (event) => {
|
||
|
if (!isPointerLocked) return;
|
||
|
|
||
|
euler.setFromQuaternion(camera.quaternion);
|
||
|
euler.y -= event.movementX * lookSpeed;
|
||
|
euler.x -= event.movementY * lookSpeed;
|
||
|
euler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, euler.x));
|
||
|
camera.quaternion.setFromEuler(euler);
|
||
|
});
|
||
|
|
||
|
// Keyboard controls
|
||
|
document.addEventListener('keydown', (event) => {
|
||
|
switch (event.code) {
|
||
|
case 'KeyW':
|
||
|
case 'ArrowUp':
|
||
|
moveForward = true;
|
||
|
break;
|
||
|
case 'KeyS':
|
||
|
case 'ArrowDown':
|
||
|
moveBackward = true;
|
||
|
break;
|
||
|
case 'KeyA':
|
||
|
case 'ArrowLeft':
|
||
|
moveLeft = true;
|
||
|
break;
|
||
|
case 'KeyD':
|
||
|
case 'ArrowRight':
|
||
|
moveRight = true;
|
||
|
break;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
document.addEventListener('keyup', (event) => {
|
||
|
switch (event.code) {
|
||
|
case 'KeyW':
|
||
|
case 'ArrowUp':
|
||
|
moveForward = false;
|
||
|
break;
|
||
|
case 'KeyS':
|
||
|
case 'ArrowDown':
|
||
|
moveBackward = false;
|
||
|
break;
|
||
|
case 'KeyA':
|
||
|
case 'ArrowLeft':
|
||
|
moveLeft = false;
|
||
|
break;
|
||
|
case 'KeyD':
|
||
|
case 'ArrowRight':
|
||
|
moveRight = false;
|
||
|
break;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Window resize handler
|
||
|
window.addEventListener('resize', () => {
|
||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||
|
camera.updateProjectionMatrix();
|
||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
|
});
|
||
|
|
||
|
// Animation loop
|
||
|
function animate() {
|
||
|
requestAnimationFrame(animate);
|
||
|
|
||
|
// Update movement
|
||
|
direction.z = Number(moveForward) - Number(moveBackward);
|
||
|
direction.x = Number(moveRight) - Number(moveLeft);
|
||
|
direction.normalize();
|
||
|
|
||
|
if (moveForward || moveBackward) {
|
||
|
velocity.z -= direction.z * moveSpeed;
|
||
|
}
|
||
|
if (moveLeft || moveRight) {
|
||
|
velocity.x -= direction.x * moveSpeed;
|
||
|
}
|
||
|
|
||
|
// Apply movement with friction
|
||
|
velocity.x *= 0.9;
|
||
|
velocity.z *= 0.9;
|
||
|
|
||
|
// Move camera
|
||
|
const moveVector = new THREE.Vector3();
|
||
|
moveVector.x = -velocity.x;
|
||
|
moveVector.z = -velocity.z;
|
||
|
moveVector.applyQuaternion(camera.quaternion);
|
||
|
|
||
|
camera.position.add(moveVector);
|
||
|
|
||
|
// Keep camera at eye height
|
||
|
camera.position.y = 1.6;
|
||
|
|
||
|
renderer.render(scene, camera);
|
||
|
}
|
||
|
|
||
|
animate();
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|