Tim13ekd's picture
mache es mit three js ein richtiges 3d game
af78ba8 verified
// 3D First Person Game with Physics and Interactions
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('gameCanvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
alert('WebGL not supported in your browser!');
return;
}
// Set canvas to full window size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Camera settings
const camera = {
position: [0, 1.6, 5],
rotation: [0, 0, 0],
fov: 75,
near: 0.1,
far: 1000,
speed: 0.2,
height: 1.6
};
// Game state
const game = {
score: 0,
collectedItems: 0,
maxItems: 10,
isRunning: true,
startTime: Date.now()
};
// Player movement state
const movement = {
forward: false,
backward: false,
left: false,
right: false,
jump: false,
isGrounded: false,
velocityY: 0,
gravity: -0.005
};
// Physics settings
const physics = {
friction: 0.98,
maxSpeed: 0.3,
jumpForce: 0.15
};
// Game controls
let isMouseLocked = false;
let mouseSensitivity = 0.002;
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
// Initialize scene
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(canvas.width, canvas.height);
renderer.setClearColor(0x222222);
// Camera (using Three.js PerspectiveCamera)
const threeCamera = new THREE.PerspectiveCamera(
camera.fov,
canvas.width / canvas.height,
camera.near,
camera.far
);
threeCamera.position.set(...camera.position);
// Enhanced lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 5, 1);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// Hemisphere light for natural outdoor lighting
const hemiLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 0.5);
scene.add(hemiLight);
// Fog for depth
scene.fog = new THREE.FogExp2(0x222222, 0.02);
// Create a simple maze environment
createEnvironment(scene);
// Event listeners for keyboard
// Enhanced controls with jump
window.addEventListener('keydown', (e) => {
switch (e.key.toLowerCase()) {
case 'w': movement.forward = true; break;
case 's': movement.backward = true; break;
case 'a': movement.left = true; break;
case 'd': movement.right = true; break;
case ' ':
if (movement.isGrounded) {
movement.jump = true;
movement.isGrounded = false;
movement.velocityY = physics.jumpForce;
}
break;
case 'e': checkInteraction(); break;
}
});
window.addEventListener('keyup', (e) => {
switch (e.key.toLowerCase()) {
case 'w': movement.forward = false; break;
case 's': movement.backward = false; break;
case 'a': movement.left = false; break;
case 'd': movement.right = false; break;
case ' ': movement.jump = false; break;
}
});
// Mouse movement controls
canvas.addEventListener('click', () => {
canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock ||
canvas.webkitRequestPointerLock;
canvas.requestPointerLock();
});
document.addEventListener('pointerlockchange', lockChangeAlert, false);
document.addEventListener('mozpointerlockchange', lockChangeAlert, false);
function lockChangeAlert() {
isMouseLocked = document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas;
}
document.addEventListener('mousemove', (e) => {
if (!isMouseLocked) return;
camera.rotation[1] -= e.movementX * mouseSensitivity;
camera.rotation[0] = Math.max(-Math.PI/2.5, Math.min(Math.PI/2.5,
camera.rotation[0] - e.movementY * mouseSensitivity
));
});
// Handle window resize
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
threeCamera.aspect = canvas.width / canvas.height;
threeCamera.updateProjectionMatrix();
renderer.setSize(canvas.width, canvas.height);
});
// Enhanced game loop with physics
const clock = new THREE.Clock();
function animate() {
if (!game.isRunning) return;
requestAnimationFrame(animate);
const delta = clock.getDelta();
// Movement vectors
const forwardVector = new THREE.Vector3(
Math.sin(camera.rotation[1]),
0,
Math.cos(camera.rotation[1])
).normalize();
const sideVector = new THREE.Vector3(
Math.sin(camera.rotation[1] + Math.PI/2),
0,
Math.cos(camera.rotation[1] + Math.PI/2)
).normalize();
// Apply forces
const velocity = new THREE.Vector3();
if (movement.forward) velocity.add(forwardVector);
if (movement.backward) velocity.sub(forwardVector);
if (movement.left) velocity.sub(sideVector);
if (movement.right) velocity.add(sideVector);
// Apply physics
velocity.multiplyScalar(camera.speed);
velocity.y = movement.velocityY;
// Limit speed
if (velocity.length() > physics.maxSpeed) {
velocity.normalize().multiplyScalar(physics.maxSpeed);
}
// Apply friction
velocity.multiplyScalar(physics.friction);
// Update position
threeCamera.position.add(velocity);
// Gravity
if (!movement.isGrounded) {
movement.velocityY += movement.gravity;
} else {
movement.velocityY = 0;
}
// Ground check
checkGroundCollision();
// Update camera rotation
threeCamera.rotation.set(
camera.rotation[0],
camera.rotation[1],
0,
'YXZ'
);
renderer.render(scene, threeCamera);
}
animate();
});
// Create game environment with collectibles and obstacles
function createEnvironment(scene) {
const tileSize = 5;
const gridSize = 11;
const wallHeight = 3;
// Floor with texture
const floorGeometry = new THREE.PlaneGeometry(tileSize * gridSize, tileSize * gridSize);
const floorTexture = new THREE.TextureLoader().load('https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/terrain/grasslight-big.jpg');
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(gridSize, gridSize);
const floorMaterial = new THREE.MeshStandardMaterial({
map: floorTexture,
roughness: 0.9,
metalness: 0
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
// Walls with better materials
const wallMaterial = new THREE.MeshStandardMaterial({
color: 0xcccccc,
roughness: 0.7,
metalness: 0.1
});
// Create maze grid
const mazeGrid = Array(gridSize).fill().map(() => Array(gridSize).fill(0));
// Basic maze structure
for (let x = 0; x < gridSize; x++) {
for (let z = 0; z < gridSize; z++) {
// Border walls
if (x === 0 || x === gridSize-1 || z === 0 || z === gridSize-1) {
mazeGrid[x][z] = 1;
}
// Inner pattern
else if (Math.random() > 0.7 && !(x === 1 && z === 1)) {
mazeGrid[x][z] = 1;
}
}
}
// Ensure there's always a path
mazeGrid[1][1] = 0; // Start position
mazeGrid[gridSize-2][gridSize-2] = 0; // Exit position
// Build maze from grid
for (let x = 0; x < gridSize; x++) {
for (let z = 0; z < gridSize; z++) {
if (mazeGrid[x][z]) {
const wallX = (x - (gridSize-1)/2) * tileSize;
const wallZ = (z - (gridSize-1)/2) * tileSize;
const wallGeometry = new THREE.BoxGeometry(
tileSize, wallHeight, tileSize
);
const wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
wallMesh.position.set(wallX, wallHeight/2, wallZ);
wallMesh.castShadow = true;
wallMesh.receiveShadow = true;
scene.add(wallMesh);
}
}
}
walls.forEach(wall => {
const wallGeometry = new THREE.BoxGeometry(...wall.size);
const wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
wallMesh.position.set(...wall.position);
scene.add(wallMesh);
});
// Create collectible items
for (let i = 0; i < game.maxItems; i++) {
let x, z;
do {
x = Math.floor(Math.random() * gridSize);
z = Math.floor(Math.random() * gridSize);
} while (mazeGrid[x][z] || (x === 1 && z === 1));
const itemX = (x - (gridSize-1)/2) * tileSize;
const itemZ = (z - (gridSize-1)/2) * tileSize;
const itemGeometry = new THREE.SphereGeometry(0.5, 16, 16);
const itemMaterial = new THREE.MeshStandardMaterial({
color: 0x00ff00,
emissive: 0x00ff00,
emissiveIntensity: 0.5,
metalness: 0.3,
roughness: 0.4
});
const item = new THREE.Mesh(itemGeometry, itemMaterial);
item.position.set(itemX, 1, itemZ);
item.userData.isCollectible = true;
item.castShadow = true;
scene.add(item);
}
// Create exit door
const exitX = ((gridSize-2) - (gridSize-1)/2) * tileSize;
const exitZ = ((gridSize-2) - (gridSize-1)/2) * tileSize;
const exitGeometry = new THREE.BoxGeometry(tileSize/2, wallHeight/2, tileSize/2);
const exitMaterial = new THREE.MeshStandardMaterial({
color: 0xff0000,
emissive: 0xff0000,
emissiveIntensity: 0.3
});
const exit = new THREE.Mesh(exitGeometry, exitMaterial);
exit.position.set(exitX, wallHeight/4, exitZ);
exit.userData.isExit = true;
scene.add(exit);
}
// Collision detection
function checkGroundCollision() {
const groundRay = new THREE.Raycaster(
threeCamera.position,
new THREE.Vector3(0, -1, 0),
0,
camera.height * 1.1
);
const intersects = groundRay.intersectObjects(scene.children.filter(obj => obj !== floor));
movement.isGrounded = intersects.length > 0;
}
// Interaction system
function checkInteraction() {
raycaster.setFromCamera(pointer, threeCamera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
const obj = intersects[0].object;
if (obj.userData.isCollectible) {
// Collect item
scene.remove(obj);
game.collectedItems++;
game.score += 100;
updateUI();
if (game.collectedItems === game.maxItems) {
document.getElementById('hint').textContent = "Find the red exit!";
}
} else if (obj.userData.isExit && game.collectedItems === game.maxItems) {
// Game completed
game.isRunning = false;
const timeElapsed = Math.floor((Date.now() - game.startTime) / 1000);
document.getElementById('title').innerHTML = `
<h1 class="text-xl font-bold">Game Completed!</h1>
<p>Score: ${game.score + timeElapsed * 10}</p>
<p>Time: ${timeElapsed}s</p>
`;
}
}
}
// UI update
function updateUI() {
document.getElementById('score').textContent = `Score: ${game.score}`;
document.getElementById('items').textContent = `Items: ${game.collectedItems}/${game.maxItems}`;
}
// Initialize UI elements
const ui = document.getElementById('ui');
const gameInfo = document.createElement('div');
gameInfo.id = 'game-info';
gameInfo.style.position = 'absolute';
gameInfo.style.top = '20px';
gameInfo.style.right = '20px';
gameInfo.style.backgroundColor = 'rgba(0,0,0,0.5)';
gameInfo.style.padding = '10px';
gameInfo.style.borderRadius = '5px';
gameInfo.style.color = 'white';
gameInfo.innerHTML = `
<div id="score">Score: 0</div>
<div id="items">Items: 0/${game.maxItems}</div>
<div id="hint">Collect all green spheres!</div>
`;
ui.appendChild(gameInfo);
// Include Three.js and PointerLockControls from CDN
const threeScript = document.createElement('script');
threeScript.src = 'https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js';
document.head.appendChild(threeScript);
const pointerLockScript = document.createElement('script');
pointerLockScript.src = 'https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/PointerLockControls.js';
document.head.appendChild(pointerLockScript);