Files
landingpage/games/index.html
admin be212654fd games/index.html aktualisiert
Added HTML sourcecode
2026-01-16 22:03:22 +00:00

899 lines
33 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trench Run: Final Mission</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000;
font-family: 'Courier New', Courier, monospace;
user-select: none;
}
#game-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#ui-layer {
position: absolute;
top: 20px;
left: 20px;
color: #0ff;
font-size: 20px;
z-index: 10;
text-shadow: 0 0 5px #0ff;
pointer-events: none;
width: 95%;
display: flex;
justify-content: space-between;
}
.hud-column {
display: flex;
flex-direction: column;
gap: 5px;
}
.shield-bar-container {
margin-top: 5px;
width: 200px;
height: 15px;
border: 2px solid #00ffff;
background: rgba(0, 50, 50, 0.5);
}
#shield-bar {
width: 100%;
height: 100%;
background-color: #00ffff;
transition: width 0.1s;
}
#distance-display {
color: #ffcc00;
text-align: right;
}
#center-message {
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 30px;
color: #ff0000;
text-shadow: 0 0 10px red;
text-align: center;
opacity: 0;
transition: opacity 0.5s;
pointer-events: none;
}
#damage-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle, transparent 50%, rgba(255,0,0,0.5) 100%);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
z-index: 5;
}
#target-reticle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 2px dashed rgba(255, 0, 0, 0.5);
border-radius: 50%;
display: none;
pointer-events: none;
box-shadow: 0 0 20px rgba(255, 0, 0, 0.2);
transition: border 0.1s, background 0.1s;
}
.locked-on {
border: 4px solid #ff0000 !important;
background: rgba(255, 0, 0, 0.2);
box-shadow: 0 0 50px #ff0000 !important;
}
#start-screen, #game-over-screen, #win-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.85);
color: white;
z-index: 20;
cursor: pointer;
}
h1 {
font-size: 40px;
color: #ffcc00;
text-shadow: 0 0 10px #ffaa00;
margin-bottom: 10px;
text-transform: uppercase;
text-align: center;
}
.instruction { color: #00ffff; font-weight: bold; }
.warning { color: #ff0000; font-weight: bold; animation: blink 0.5s infinite; }
@keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
.hidden { display: none !important; }
</style>
</head>
<body>
<div id="damage-overlay"></div>
<!-- UI Overlay -->
<div id="ui-layer">
<div class="hud-column">
<div>SCORE: <span id="score-display">0</span></div>
<div class="shield-bar-container">
<div id="shield-bar"></div>
</div>
<div style="font-size: 14px;">SCHILD (Blockt Laser)</div>
</div>
<div class="hud-column">
<div id="distance-display">DISTANZ: <span id="dist-val">5000</span></div>
<div id="mode-display" style="color: #0f0; font-size: 16px;">MODUS: ANFLUG</div>
</div>
</div>
<div id="center-message">ACHTUNG: TRENCH RUN GESTARTET</div>
<div id="target-reticle">
<div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:red; font-size:12px;">ZIELCOMPUTER</div>
</div>
<!-- Start Screen -->
<div id="start-screen">
<h1>Star Mission</h1>
<p>1. Weiche Lasern aus (1 Leben)</p>
<p>2. Schild blockt Laser!</p>
<p>3. <span class="warning">LEERTASTE</span> = Schild / Torpedo</p>
<br>
<p class="instruction">Klicken zum Starten</p>
</div>
<!-- Game Over Screen -->
<div id="game-over-screen" class="hidden">
<h1>Zerstört</h1>
<p>Der Todesstern hat gewonnen.</p>
<p>Klicken für Neustart</p>
</div>
<!-- Win Screen -->
<div id="win-screen" class="hidden">
<h1 style="color: #00ff00; text-shadow: 0 0 20px #00ff00;">SIEG!</h1>
<p>Du hast die Galaxis gerettet.</p>
<p>Klicken um erneut zu spielen</p>
</div>
<div id="game-container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// --- Audio System ---
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const Sound = {
playTone: (freq, type, duration, vol = 0.1) => {
if(audioCtx.state === 'suspended') audioCtx.resume();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
},
playExplosion: () => { Sound.playTone(80, 'sawtooth', 0.8, 0.3); },
playShieldBlock: () => { Sound.playTone(800, 'sine', 0.1, 0.2); },
playLaser: () => { Sound.playTone(600, 'sawtooth', 0.1, 0.05); },
playLockOn: () => { Sound.playTone(1200, 'square', 0.1, 0.05); },
playWin: () => {
let now = audioCtx.currentTime;
[440, 554, 659, 880].forEach((freq, i) => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.frequency.value = freq;
gain.gain.setValueAtTime(0.1, now + i*0.2);
gain.gain.exponentialRampToValueAtTime(0.01, now + i*0.2 + 1);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(now + i*0.2);
osc.stop(now + i*0.2 + 1);
});
}
};
// --- Game State ---
const GameState = {
APPROACH: 'APPROACH',
TRANSITION: 'TRANSITION',
TRENCH: 'TRENCH',
BOMB_RUN: 'BOMB_RUN',
WIN: 'WIN',
GAMEOVER: 'GAMEOVER'
};
let currentState = GameState.APPROACH;
let distanceToDeathStar = 5000;
const TRENCH_START_DIST = 1000;
const BOMB_DIST = 200;
// --- Setup ---
let scene, camera, renderer;
let playerGroup, playerShield;
let deathStar, deathStarLaserBeam;
let trenchGroup, floorMesh, wallL, wallR, exhaustPort;
let obstacles = [];
let backgroundShips = [];
let tieMasterMesh;
let score = 0;
let speed = 1.5;
let animationId;
let mouse = { x: 0, y: 0 };
const targetPlayerPos = { x: 0, y: 0 };
let isShieldActive = false;
let shieldEnergy = 100;
let bombReady = false;
let laserState = 'IDLE';
let laserTimer = 0;
// UI
const scoreEl = document.getElementById('score-display');
const distEl = document.getElementById('dist-val');
const modeEl = document.getElementById('mode-display');
const centerMsg = document.getElementById('center-message');
const reticle = document.getElementById('target-reticle');
const shieldBar = document.getElementById('shield-bar');
const damageOverlay = document.getElementById('damage-overlay');
// Screens
const startScreen = document.getElementById('start-screen');
const gameOverScreen = document.getElementById('game-over-screen');
const winScreen = document.getElementById('win-screen');
function init() {
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.002);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.z = 8;
camera.position.y = 2;
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('game-container').appendChild(renderer.domElement);
const ambLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(-10, 20, 10);
scene.add(dirLight);
createTieMasterMesh();
createXWing();
createDeathStar();
createTrenchEnvironment();
createStarfield();
createLaserBeam();
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mousedown', () => isShieldActive = true);
document.addEventListener('mouseup', () => isShieldActive = false);
document.addEventListener('keydown', (e) => {
if(e.code === 'Space') {
if (bombReady && currentState === GameState.BOMB_RUN) {
fireProtonTorpedo();
} else {
isShieldActive = true;
}
}
});
document.addEventListener('keyup', (e) => { if(e.code === 'Space') isShieldActive = false; });
window.addEventListener('resize', onWindowResize);
startScreen.addEventListener('click', startGame);
gameOverScreen.addEventListener('click', resetGame);
winScreen.addEventListener('click', resetGame);
}
function triggerDamageEffect() {
damageOverlay.style.opacity = 1;
setTimeout(() => { damageOverlay.style.opacity = 0; }, 200);
camera.position.x += (Math.random()-0.5) * 1;
camera.position.y += (Math.random()-0.5) * 1;
}
// --- Model Creation ---
function generateDeathStarTexture() {
const canvas = document.createElement('canvas');
canvas.width = 1024; canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#666'; ctx.fillRect(0,0,1024,512);
// Mehr Details
for(let i=0; i<3000; i++) {
const c = Math.floor(Math.random() * 50 + 80);
ctx.fillStyle = `rgb(${c},${c},${c})`;
ctx.fillRect(Math.random()*1024, Math.random()*512, Math.random()*4, Math.random()*4);
}
// Äquator
ctx.fillStyle = '#111'; ctx.fillRect(0, 250, 1024, 12);
return new THREE.CanvasTexture(canvas);
}
function createDeathStar() {
const geo = new THREE.SphereGeometry(100, 64, 64);
const mat = new THREE.MeshPhongMaterial({
map: generateDeathStarTexture(),
color: 0x999999,
specular: 0x111111
});
deathStar = new THREE.Mesh(geo, mat);
deathStar.position.set(0, 0, -1000);
// Superlaser Dish (Detailierter)
const dishGeo = new THREE.SphereGeometry(25, 32, 32);
const dishMat = new THREE.MeshPhongMaterial({ color: 0x555555 });
const dish = new THREE.Mesh(dishGeo, dishMat);
dish.position.set(30, 40, 75);
dish.lookAt(0,0,-1000); // Zur Mitte des Todessterns
dish.scale.z = 0.2; // Eindrücken
deathStar.add(dish);
scene.add(deathStar);
}
function createTrenchEnvironment() {
trenchGroup = new THREE.Group();
const canvas = document.createElement('canvas');
canvas.width = 512; canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#444'; ctx.fillRect(0,0,512,512);
ctx.strokeStyle = '#222'; ctx.lineWidth = 4;
// Tech Grid
for(let i=0; i<512; i+=64) {
ctx.strokeRect(i, 0, 64, 512);
ctx.strokeRect(0, i, 512, 64);
}
// Pipes and Greebles
for(let i=0; i<50; i++) {
ctx.fillStyle = '#555';
ctx.fillRect(Math.random()*500, Math.random()*500, Math.random()*40+10, Math.random()*40+10);
}
const trenchTex = new THREE.CanvasTexture(canvas);
trenchTex.wrapS = THREE.RepeatWrapping;
trenchTex.wrapT = THREE.RepeatWrapping;
trenchTex.repeat.set(5, 20);
const mat = new THREE.MeshPhongMaterial({ map: trenchTex, side: THREE.DoubleSide });
floorMesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 2000), mat);
floorMesh.rotation.x = -Math.PI/2;
floorMesh.position.y = -5;
floorMesh.position.z = -500;
trenchGroup.add(floorMesh);
wallL = new THREE.Mesh(new THREE.PlaneGeometry(2000, 50), mat);
wallL.rotation.y = Math.PI/2;
wallL.position.set(-20, 10, -500);
trenchGroup.add(wallL);
wallR = new THREE.Mesh(new THREE.PlaneGeometry(2000, 50), mat);
wallR.rotation.y = -Math.PI/2;
wallR.position.set(20, 10, -500);
trenchGroup.add(wallR);
const portGeo = new THREE.RingGeometry(2, 4, 32);
const portMat = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
exhaustPort = new THREE.Mesh(portGeo, portMat);
exhaustPort.rotation.x = -Math.PI/2;
exhaustPort.position.set(0, -4.8, -100);
exhaustPort.visible = false;
trenchGroup.add(exhaustPort);
trenchGroup.visible = false;
scene.add(trenchGroup);
}
function createTieMasterMesh() {
const tieGroup = new THREE.Group();
const greyMat = new THREE.MeshPhongMaterial({ color: 0x999999 });
const blackMat = new THREE.MeshPhongMaterial({ color: 0x111111 });
const cockpit = new THREE.Mesh(new THREE.SphereGeometry(0.8, 16, 16), greyMat);
tieGroup.add(cockpit);
const windowFrame = new THREE.Mesh(new THREE.TorusGeometry(0.4, 0.05, 8, 8), greyMat);
windowFrame.position.z = 0.75;
tieGroup.add(windowFrame);
const wingGeo = new THREE.CylinderGeometry(2.2, 2.2, 0.1, 6);
const wingL = new THREE.Mesh(wingGeo, blackMat);
wingL.rotation.x = Math.PI / 2;
wingL.rotation.z = Math.PI / 2;
wingL.position.x = -2;
const pylonGeo = new THREE.CylinderGeometry(0.3, 0.2, 1.2);
const pylonL = new THREE.Mesh(pylonGeo, greyMat);
pylonL.rotation.z = Math.PI/2;
pylonL.position.x = -1.0;
tieGroup.add(pylonL);
const pylonR = pylonL.clone();
pylonR.position.x = 1.0;
tieGroup.add(pylonR);
const wingR = wingL.clone();
wingR.position.x = 2;
tieGroup.add(wingL);
tieGroup.add(wingR);
tieMasterMesh = tieGroup;
}
function createXWing() {
playerGroup = new THREE.Group();
const bodyMat = new THREE.MeshPhongMaterial({ color: 0xeeeeee, flatShading: false });
const darkMat = new THREE.MeshPhongMaterial({ color: 0x333333 });
const redMat = new THREE.MeshPhongMaterial({ color: 0xaa0000 });
const engineGlow = new THREE.MeshBasicMaterial({ color: 0xff6600 });
const fuselage = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.6, 4.5), bodyMat);
playerGroup.add(fuselage);
const nose = new THREE.Mesh(new THREE.ConeGeometry(0.3, 2.5, 8), bodyMat);
nose.rotation.x = -Math.PI / 2;
nose.position.z = -3.5;
playerGroup.add(nose);
const cockpit = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.4, 1.2), darkMat);
cockpit.position.set(0, 0.4, -0.5);
playerGroup.add(cockpit);
const droid = new THREE.Mesh(new THREE.SphereGeometry(0.25, 8, 8), new THREE.MeshPhongMaterial({color:0x0000ff}));
droid.position.set(0, 0.4, 0.5);
playerGroup.add(droid);
const wingGeo = new THREE.BoxGeometry(2.0, 0.1, 1.2);
const engineGeo = new THREE.CylinderGeometry(0.25, 0.25, 1.5, 8);
const wings = [
{ x:1, y:0.2, rot: 0.3 },
{ x:-1, y:0.2, rot: -0.3 },
{ x:1, y:-0.2, rot: -0.3 },
{ x:-1, y:-0.2, rot: 0.3 }
];
wings.forEach(w => {
const wingContainer = new THREE.Group();
const blade = new THREE.Mesh(wingGeo, bodyMat);
blade.position.x = w.x > 0 ? 1.0 : -1.0;
wingContainer.add(blade);
const engine = new THREE.Mesh(engineGeo, bodyMat);
engine.rotation.x = Math.PI/2;
engine.position.set(w.x > 0 ? 0.5 : -0.5, 0, 0.5);
wingContainer.add(engine);
const glow = new THREE.Mesh(new THREE.CircleGeometry(0.2, 8), engineGlow);
glow.position.set(w.x > 0 ? 0.5 : -0.5, 0, 1.26);
wingContainer.add(glow);
const gun = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 3), bodyMat);
gun.rotation.x = Math.PI/2;
gun.position.set(w.x > 0 ? 2.0 : -2.0, 0, -1.0);
wingContainer.add(gun);
const stripe = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.11, 0.4), redMat);
stripe.position.set(w.x > 0 ? 1.0 : -1.0, 0, 0);
wingContainer.add(stripe);
wingContainer.rotation.z = w.rot;
playerGroup.add(wingContainer);
});
const shieldGeo = new THREE.SphereGeometry(3.5, 16, 16);
const shieldMat = new THREE.MeshBasicMaterial({ color: 0x00ffff, transparent: true, opacity: 0 });
playerShield = new THREE.Mesh(shieldGeo, shieldMat);
playerGroup.add(playerShield);
playerGroup.scale.set(0.6, 0.6, 0.6);
scene.add(playerGroup);
}
function createLaserBeam() {
const geo = new THREE.CylinderGeometry(4, 8, 400, 16, 1, true);
const mat = new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0, blending: THREE.AdditiveBlending, side: THREE.DoubleSide });
deathStarLaserBeam = new THREE.Mesh(geo, mat);
deathStarLaserBeam.rotation.x = Math.PI / 2;
deathStarLaserBeam.visible = false;
scene.add(deathStarLaserBeam);
}
function createStarfield() {
const geometry = new THREE.BufferGeometry();
const vertices = [];
for (let i = 0; i < 2000; i++) vertices.push((Math.random()-0.5)*200, (Math.random()-0.5)*200, (Math.random()-0.5)*400-100);
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
particles = new THREE.Points(geometry, new THREE.PointsMaterial({ color: 0xffffff, size: 0.2 }));
scene.add(particles);
}
// --- BACKGROUND TIE LOGIC ---
function createBackgroundTie() {
if(!tieMasterMesh) return;
const tie = tieMasterMesh.clone();
const angle = Math.random() * Math.PI * 2;
const radius = 30 + Math.random() * 50;
tie.position.x = Math.cos(angle) * radius;
tie.position.y = Math.sin(angle) * radius;
tie.position.z = -100 - Math.random() * 200;
tie.userData = {
velX: (Math.random() - 0.5) * 0.4,
velY: (Math.random() - 0.5) * 0.4,
velZ: 0.5 + Math.random() * 1.0,
rotX: (Math.random() - 0.5) * 0.05,
rotY: (Math.random() - 0.5) * 0.05
};
const scale = 0.5 + Math.random() * 0.5;
tie.scale.set(scale, scale, scale);
scene.add(tie);
backgroundShips.push(tie);
}
function updateBackgroundShips() {
if(currentState === GameState.APPROACH && Math.random() < 0.03) {
createBackgroundTie();
}
for (let i = backgroundShips.length - 1; i >= 0; i--) {
const ship = backgroundShips[i];
ship.position.x += ship.userData.velX;
ship.position.y += ship.userData.velY;
ship.position.z += ship.userData.velZ;
ship.rotation.x += ship.userData.rotX;
ship.rotation.y += ship.userData.rotY;
if (ship.position.z > 20 || Math.abs(ship.position.x) > 200) {
scene.remove(ship);
backgroundShips.splice(i, 1);
}
}
}
function createLaserObstacle() {
const isTrench = currentState === GameState.TRENCH || currentState === GameState.BOMB_RUN;
const geometry = new THREE.CylinderGeometry(0.15, 0.15, 8, 8);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, blending: THREE.AdditiveBlending });
const laser = new THREE.Mesh(geometry, material);
laser.rotation.x = Math.PI / 2;
if (isTrench) {
laser.position.x = (Math.random() - 0.5) * 20;
laser.position.y = (Math.random() * 10) - 4;
laser.position.z = -200;
} else {
laser.position.x = (Math.random() - 0.5) * 35;
laser.position.y = (Math.random() - 0.5) * 20;
laser.position.z = -150;
}
scene.add(laser);
obstacles.push(laser);
}
// --- Game Logic ---
function enterTrenchMode() {
currentState = GameState.TRENCH;
modeEl.innerText = "MODUS: TRENCH RUN";
modeEl.style.color = "#ff00ff";
deathStar.visible = false;
trenchGroup.visible = true;
camera.position.y = 0;
centerMsg.innerText = "TRENCH RUN INITIATED";
centerMsg.style.opacity = 1;
setTimeout(() => centerMsg.style.opacity = 0, 2000);
}
function startBombRun() {
currentState = GameState.BOMB_RUN;
modeEl.innerText = "MODUS: ZIELANFLUG";
modeEl.style.color = "#ff0000";
exhaustPort.visible = true;
exhaustPort.position.z = -300;
reticle.style.display = 'block';
centerMsg.innerText = "ZIEL IN REICHWEITE!";
centerMsg.style.opacity = 1;
}
function fireProtonTorpedo() {
const torpedo = new THREE.Mesh(new THREE.SphereGeometry(0.5), new THREE.MeshBasicMaterial({color:0xffff00}));
torpedo.position.copy(playerGroup.position);
scene.add(torpedo);
let t = 0;
const targetPos = new THREE.Vector3(0, -5, exhaustPort.position.z);
const torpedoAnim = setInterval(() => {
t += 0.1;
torpedo.position.lerp(targetPos, 0.2);
if(t > 20 || torpedo.position.distanceTo(targetPos) < 2) {
clearInterval(torpedoAnim);
scene.remove(torpedo);
triggerWin();
}
}, 30);
currentState = GameState.WIN;
}
function triggerWin() {
isGameRunning = false;
Sound.playWin();
const expGeo = new THREE.SphereGeometry(1, 32, 32);
const expMat = new THREE.MeshBasicMaterial({ color: 0xffaa00 });
const explosion = new THREE.Mesh(expGeo, expMat);
explosion.position.set(0,0, -50);
scene.add(explosion);
let scale = 1;
const expInterval = setInterval(() => {
scale += 2;
explosion.scale.set(scale, scale, scale);
explosion.material.opacity -= 0.01;
if(scale > 200) {
clearInterval(expInterval);
winScreen.classList.remove('hidden');
reticle.style.display = 'none';
}
}, 16);
}
function updateGameLogic() {
if (currentState === GameState.APPROACH) {
distanceToDeathStar -= speed;
deathStar.position.z += speed * 0.2;
if (distanceToDeathStar <= TRENCH_START_DIST) {
enterTrenchMode();
}
} else if (currentState === GameState.TRENCH) {
distanceToDeathStar -= speed;
floorMesh.material.map.offset.y -= 0.01 * speed;
wallL.material.map.offset.y -= 0.01 * speed;
wallR.material.map.offset.y -= 0.01 * speed;
if (distanceToDeathStar <= BOMB_DIST) {
startBombRun();
}
} else if (currentState === GameState.BOMB_RUN) {
floorMesh.material.map.offset.y -= 0.01 * speed;
exhaustPort.position.z += speed;
if (exhaustPort.position.z > 10) {
gameOverScreen.querySelector('h1').innerText = "ZIEL VERFEHLT";
gameOver();
}
const distZ = Math.abs(exhaustPort.position.z - playerGroup.position.z);
const distX = Math.abs(playerGroup.position.x);
if (distZ < 20 && distX < 4) {
bombReady = true;
reticle.classList.add('locked-on');
exhaustPort.material.color.setHex(0xff0000);
if(Math.random()<0.2) Sound.playLockOn();
} else {
bombReady = false;
reticle.classList.remove('locked-on');
exhaustPort.material.color.setHex(0xffff00);
}
}
distEl.innerText = Math.max(0, Math.floor(distanceToDeathStar));
}
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function updatePlayer() {
let limitX = 14;
let limitY = 8;
if (currentState === GameState.TRENCH || currentState === GameState.BOMB_RUN) {
limitX = 9;
limitY = 4;
}
targetPlayerPos.x = mouse.x * limitX;
targetPlayerPos.y = mouse.y * limitY;
playerGroup.position.x += (targetPlayerPos.x - playerGroup.position.x) * 0.1;
playerGroup.position.y += (targetPlayerPos.y - playerGroup.position.y) * 0.1;
playerGroup.rotation.z = -playerGroup.position.x * 0.05;
playerGroup.rotation.x = playerGroup.position.y * 0.05;
// Shield
if (isShieldActive && shieldEnergy > 0) {
shieldEnergy -= 0.5;
playerShield.material.opacity = 0.4 + Math.random()*0.1;
} else {
if (shieldEnergy < 100) shieldEnergy += 0.2;
playerShield.material.opacity = 0;
}
shieldBar.style.width = shieldEnergy + "%";
const screenX = 50 + (playerGroup.position.x / 14) * 40;
const screenY = 50 - (playerGroup.position.y / 8) * 40;
reticle.style.left = screenX + '%';
reticle.style.top = screenY + '%';
}
function updateObstacles() {
let chance = 0.015 + (score * 0.00002);
if (currentState === GameState.TRENCH) chance = 0.04;
if (Math.random() < chance && laserState !== 'FIRING') {
createLaserObstacle();
}
for (let i = obstacles.length - 1; i >= 0; i--) {
const obs = obstacles[i];
obs.position.z += speed * 1.5;
const zDiff = Math.abs(playerGroup.position.z - obs.position.z);
const xyDist = Math.sqrt(Math.pow(playerGroup.position.x - obs.position.x, 2) + Math.pow(playerGroup.position.y - obs.position.y, 2));
if (zDiff < 4 && xyDist < 1.2) {
if (isShieldActive && shieldEnergy > 5) {
scene.remove(obs);
obstacles.splice(i, 1);
shieldEnergy -= 15;
Sound.playShieldBlock();
continue;
} else {
// GAME OVER BEI TREFFER (Kein Health System mehr)
triggerDamageEffect();
gameOver();
continue;
}
}
if (obs.position.z > 10) {
scene.remove(obs);
obstacles.splice(i, 1);
score += 50;
scoreEl.innerText = score;
}
}
}
function updateEnvironment() {
if (currentState === GameState.APPROACH) {
const positions = particles.geometry.attributes.position.array;
for(let i=2; i<positions.length; i+=3) {
positions[i] += speed * 5;
if(positions[i] > 50) positions[i] = -300;
}
particles.geometry.attributes.position.needsUpdate = true;
}
}
function animate() {
if (!isGameRunning && currentState !== GameState.WIN) return;
animationId = requestAnimationFrame(animate);
updatePlayer();
updateObstacles();
updateEnvironment();
updateBackgroundShips();
updateGameLogic();
renderer.render(scene, camera);
}
function startGame() {
if(audioCtx.state === 'suspended') audioCtx.resume();
startScreen.classList.add('hidden');
score = 0;
distanceToDeathStar = 5000;
currentState = GameState.APPROACH;
speed = 1.5;
deathStar.visible = true;
deathStar.position.set(0, 0, -1000);
trenchGroup.visible = false;
exhaustPort.visible = false;
reticle.style.display = 'none';
centerMsg.style.opacity = 0;
modeEl.innerText = "MODUS: ANFLUG";
modeEl.style.color = "#0f0";
isGameRunning = true;
animate();
}
function gameOver() {
isGameRunning = false;
cancelAnimationFrame(animationId);
gameOverScreen.classList.remove('hidden');
Sound.playExplosion();
}
function resetGame() {
obstacles.forEach(o => scene.remove(o));
obstacles = [];
backgroundShips.forEach(s => scene.remove(s));
backgroundShips = [];
playerGroup.position.set(0,0,0);
shieldEnergy = 100;
gameOverScreen.classList.add('hidden');
winScreen.classList.add('hidden');
startGame();
}
function onWindowResize() {
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
init();
</script>
</body>
</html>