0% found this document useful (0 votes)
10 views15 pages

Aviones

The document is an HTML file for a 3D aerial combat game titled 'Combate Aéreo 3D'. It includes scripts for rendering graphics using Three.js and styles for the game's user interface, such as score and lives display. The game features various enemy types, player controls, and a starfield background, with a structured setup for game initialization and event handling.

Uploaded by

kenypp24
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views15 pages

Aviones

The document is an HTML file for a 3D aerial combat game titled 'Combate Aéreo 3D'. It includes scripts for rendering graphics using Three.js and styles for the game's user interface, such as score and lives display. The game features various enemy types, player controls, and a starfield background, with a structured setup for game initialization and event handling.

Uploaded by

kenypp24
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 15

<!

DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Combate Aéreo 3D</title>
<script src="https://cdn.tailwindcss.com"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body { margin: 0; overflow: hidden; background-color: #000; color: white;
font-family: 'Arial', sans-serif; }
#gameContainer { width: 100vw; height: 100vh; display: block; }
canvas { display: block; }
#uiContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* Allow clicks to pass through to the game */
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 20px;
box-sizing: border-box;
}
.ui-top, .ui-bottom {
display: flex;
justify-content: space-between;
width: 100%;
}
.ui-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background-color: rgba(0,0,0,0.7);
padding: 20px;
border-radius: 10px;
font-size: 2em;
display: none; /* Hidden by default */
}
.ui-element {
background-color: rgba(0,0,0,0.5);
padding: 10px 15px;
border-radius: 8px;
font-size: 1.2em;
}
#startButton {
pointer-events: all; /* Enable clicks for the button */
font-size: 1.5em;
padding: 15px 30px;
cursor: pointer;
background-color: #4CAF50;
border: none;
color: white;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
#startButton:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div id="gameContainer"></div>
<div id="uiContainer">
<div class="ui-top">
<div id="score" class="ui-element">Puntuación: 0</div>
<div id="lives" class="ui-element">Vidas: 3</div>
</div>
<div id="messageCenter" class="ui-center">
<p id="messageText">¡Bienvenido!</p>
<button id="startButton">Iniciar Juego</button>
</div>
<div class="ui-bottom">
<div id="waveInfo" class="ui-element">Oleada: 1</div>
</div>
</div>

<script>
// Variables globales del juego
let scene, camera, renderer, player, skybox;
let enemies = [];
let playerProjectiles = [];
let enemyProjectiles = [];
let boss = null;

const keys = {}; // Para manejar las teclas presionadas

let score = 0;
let lives = 3;
let currentWave = 0;
const waves = [
{ enemies: 3, type: 'fighter' },
{ enemies: 5, type: 'fighter' },
{ enemies: 1, type: 'bomber' },
{ enemies: 7, type: 'fighter' },
{ enemies: 1, type: 'boss' }
];
let gameSpeed = 0.05; // Velocidad base del movimiento de los objetos
let playerSpeed = 0.2;
let projectileSpeed = 0.8;
let enemySpeed = 0.03;

let gameState = 'MENU'; // MENU, PLAYING, BOSS_FIGHT, GAME_OVER, VICTORY

// Elementos UI
const scoreUI = document.getElementById('score');
const livesUI = document.getElementById('lives');
const waveInfoUI = document.getElementById('waveInfo');
const messageCenterUI = document.getElementById('messageCenter');
const messageTextUI = document.getElementById('messageText');
const startButtonUI = document.getElementById('startButton');

// Inicialización
function init() {
// Escena
scene = new THREE.Scene();

// Cámara
camera = new THREE.PerspectiveCamera(75, window.innerWidth /
window.innerHeight, 0.1, 2000);
camera.position.set(0, 5, 15); // Posición inicial de la cámara
camera.lookAt(0, 0, 0);

// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);

document.getElementById('gameContainer').appendChild(renderer.domElement);

// Luces
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7);
scene.add(directionalLight);

// Skybox (Estrellas)
createStarfield();

// Event listeners para controles


document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
startButtonUI.addEventListener('click', startGame);

// Manejo de redimensionamiento de ventana


window.addEventListener('resize', onWindowResize, false);

updateUI();
showMenu("¡Bienvenido a Combate Aéreo 3D!");
}

function startGame() {
if (gameState === 'PLAYING' || gameState === 'BOSS_FIGHT') return;

score = 0;
lives = 3;
currentWave = 0;
enemies = [];
playerProjectiles = [];
enemyProjectiles = [];
if (boss) scene.remove(boss.mesh);
boss = null;

if (!player) {
player = createPlayer();
scene.add(player.mesh);
}
player.mesh.position.set(0, 0, 0); // Reset player position
player.mesh.rotation.set(0,0,0);
player.health = 100; // Si el jugador tiene salud

gameState = 'PLAYING';
messageCenterUI.style.display = 'none';
nextWave();
if (!animationFrameId) animate(); // Asegurar que el bucle de animación
se inicie solo una vez
}

let animationFrameId = null; // Para controlar el requestAnimationFrame

function createStarfield() {
const starGeometry = new THREE.BufferGeometry();
const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size:
0.1 });
const starVertices = [];
for (let i = 0; i < 10000; i++) {
const x = THREE.MathUtils.randFloatSpread(1500); // Extiende las
estrellas más lejos
const y = THREE.MathUtils.randFloatSpread(1500);
const z = THREE.MathUtils.randFloatSpread(1500);
starVertices.push(x, y, z);
}
starGeometry.setAttribute('position', new
THREE.Float32BufferAttribute(starVertices, 3));
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
}

// Creación de modelos
function createPlayer() {
const group = new THREE.Group();

// Cuerpo principal (fuselaje)


const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0x777799,
metalness: 0.5, roughness: 0.5 });
const bodyGeometry = new THREE.CylinderGeometry(0.5, 0.3, 4, 16);
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.rotation.x = Math.PI / 2; // Orientar horizontalmente
group.add(body);

// Cabina
const cockpitMaterial = new THREE.MeshStandardMaterial({ color:
0x333355, transparent: true, opacity: 0.7 });
const cockpitGeometry = new THREE.SphereGeometry(0.4, 16, 8, 0, Math.PI
* 2, 0, Math.PI / 2);
const cockpit = new THREE.Mesh(cockpitGeometry, cockpitMaterial);
cockpit.position.z = 1.2; // Posicionar en la parte delantera del
cuerpo
cockpit.rotation.x = Math.PI / 2;
group.add(cockpit);

// Alas principales
const wingMaterial = new THREE.MeshStandardMaterial({ color: 0x666688,
metalness: 0.5, roughness: 0.5 });
const wingShape = new THREE.Shape();
wingShape.moveTo(0, 0);
wingShape.lineTo(3, 0.5); // Ancho y forma del ala
wingShape.lineTo(3, 1);
wingShape.lineTo(0, 0.8);
wingShape.lineTo(0,0);
const wingGeometry = new THREE.ExtrudeGeometry(wingShape, { depth: 0.1,
bevelEnabled: false });

const leftWing = new THREE.Mesh(wingGeometry, wingMaterial);


leftWing.rotation.x = Math.PI / 2;
leftWing.rotation.y = Math.PI / 2;
leftWing.position.set(-0.1, 0, 0); // Ajustar posición
group.add(leftWing);

const rightWing = leftWing.clone();


rightWing.rotation.y = -Math.PI / 2;
rightWing.position.x = 0.1;
group.add(rightWing);

// Estabilizadores traseros (cola)


const tailWingShape = new THREE.Shape();
tailWingShape.moveTo(0,0);
tailWingShape.lineTo(1.5, 0.2);
tailWingShape.lineTo(1.5, 0.5);
tailWingShape.lineTo(0, 0.4);
tailWingShape.lineTo(0,0);
const tailWingGeometry = new THREE.ExtrudeGeometry(tailWingShape,
{ depth: 0.05, bevelEnabled: false });

const leftTailWing = new THREE.Mesh(tailWingGeometry, wingMaterial);


leftTailWing.rotation.x = Math.PI / 2;
leftTailWing.rotation.y = Math.PI / 2;
leftTailWing.position.set(-0.05, 0, -1.8);
group.add(leftTailWing);

const rightTailWing = leftTailWing.clone();


rightTailWing.rotation.y = -Math.PI / 2;
rightTailWing.position.x = 0.05;
group.add(rightTailWing);

// Estabilizador vertical
const verticalTailShape = new THREE.Shape();
verticalTailShape.moveTo(0,0);
verticalTailShape.lineTo(1.2, 0);
verticalTailShape.lineTo(0.8, 1);
verticalTailShape.lineTo(0, 0.8);
verticalTailShape.lineTo(0,0);
const verticalTailGeometry = new
THREE.ExtrudeGeometry(verticalTailShape, { depth: 0.05, bevelEnabled: false });
const verticalTail = new THREE.Mesh(verticalTailGeometry,
wingMaterial);
verticalTail.position.set(0, 0.2, -1.8); // Ajustar posición
verticalTail.rotation.y = Math.PI;

group.add(verticalTail);

group.scale.set(0.7, 0.7, 0.7); // Escalar el modelo completo si es


necesario

// Propulsor (simple)
const engineMaterial = new THREE.MeshStandardMaterial({ color: 0xFFD700
}); // Color dorado/amarillo para el fuego
const engineGeometry = new THREE.ConeGeometry(0.2, 0.5, 8);
const engineFlame = new THREE.Mesh(engineGeometry, engineMaterial);
engineFlame.position.z = -2.2; // Detrás del cuerpo
engineFlame.rotation.x = Math.PI / 2;
engineFlame.visible = false; // Inicialmente apagado
group.add(engineFlame);

return { mesh: group, health: 100, engineFlame: engineFlame,


boundingBox: new THREE.Box3().setFromObject(group) };
}

function createEnemy(type = 'fighter') {


const group = new THREE.Group();
let enemyMaterial, bodyGeo, wingGeo, scaleFactor;

if (type === 'fighter') {


enemyMaterial = new THREE.MeshStandardMaterial({ color: 0xAA3333,
metalness: 0.4, roughness: 0.6 });
bodyGeo = new THREE.CylinderGeometry(0.3, 0.2, 2.5, 12);
wingGeo = new THREE.BoxGeometry(2.5, 0.1, 0.8); // Alas más simples
scaleFactor = 0.6;
} else if (type === 'bomber') {
enemyMaterial = new THREE.MeshStandardMaterial({ color: 0x446644,
metalness: 0.3, roughness: 0.7 });
bodyGeo = new THREE.CylinderGeometry(0.6, 0.5, 4, 16);
wingGeo = new THREE.BoxGeometry(4, 0.15, 1.2);
scaleFactor = 0.8;
}

const body = new THREE.Mesh(bodyGeo, enemyMaterial);


body.rotation.x = Math.PI / 2;
group.add(body);

const wings = new THREE.Mesh(wingGeo, enemyMaterial);


wings.position.y = 0; // Centradas con el cuerpo
group.add(wings);

group.scale.set(scaleFactor, scaleFactor, scaleFactor);


const randomX = THREE.MathUtils.randFloatSpread(100); // Más dispersión
inicial
const randomY = THREE.MathUtils.randFloat(5, 20); // Altura inicial
const randomZ = -THREE.MathUtils.randFloat(100, 200); // Lejos del
jugador
group.position.set(randomX, randomY, randomZ);

// Los enemigos miran hacia el jugador (o hacia el origen si el jugador


no está definido)
if (player) {
group.lookAt(player.mesh.position);
} else {
group.lookAt(new THREE.Vector3(0,0,0));
}

return { mesh: group, health: type === 'bomber' ? 200 : 50, type: type,
boundingBox: new THREE.Box3().setFromObject(group), lastShotTime: 0 };
}

function createAlienBoss() {
const group = new THREE.Group();
const bossMaterial = new THREE.MeshStandardMaterial({ color: 0x33FF33,
metalness: 0.7, roughness: 0.3, emissive: 0x22AA22 });
// Cuerpo principal esférico
const mainBodyGeo = new THREE.SphereGeometry(3, 32, 32);
const mainBody = new THREE.Mesh(mainBodyGeo, bossMaterial);
group.add(mainBody);

// Anillos alrededor del cuerpo


const ringGeo = new THREE.TorusGeometry(4.5, 0.3, 16, 100);
const ring1 = new THREE.Mesh(ringGeo, bossMaterial);
ring1.rotation.x = Math.PI / 2;
group.add(ring1);

const ring2 = new THREE.Mesh(ringGeo, bossMaterial);


ring2.rotation.y = Math.PI / 2;
group.add(ring2);

// "Brazos" o tentáculos
for (let i = 0; i < 6; i++) {
const armGeo = new THREE.CylinderGeometry(0.2, 0.1, 3, 8);
const arm = new THREE.Mesh(armGeo, bossMaterial);
const angle = (i / 6) * Math.PI * 2;
arm.position.set(Math.cos(angle) * 3, 0, Math.sin(angle) * 3);
arm.lookAt(mainBody.position);
arm.rotation.x += Math.PI / 2; // Orientar correctamente
group.add(arm);
}

group.position.set(0, 10, -150); // Posición inicial del jefe


return { mesh: group, health: 1000, type: 'boss', boundingBox: new
THREE.Box3().setFromObject(group), lastShotTime: 0, attackPattern: 'idle',
patternTimer: 0 };
}

function createProjectile(isPlayerProjectile, startPosition, direction) {


const color = isPlayerProjectile ? 0xFFFF00 : 0xFF0000; // Amarillo
para jugador, rojo para enemigo
const projectileMaterial = new THREE.MeshBasicMaterial({ color:
color });
const projectileGeometry = new THREE.SphereGeometry(0.2, 8, 8);
const projectileMesh = new THREE.Mesh(projectileGeometry,
projectileMaterial);

projectileMesh.position.copy(startPosition);

return { mesh: projectileMesh, direction: direction.normalize(),


isPlayer: isPlayerProjectile, boundingBox: new
THREE.Box3().setFromObject(projectileMesh) };
}

// Controles
function onKeyDown(event) {
keys[event.key.toLowerCase()] = true;
if (event.key === ' ' && (gameState === 'PLAYING' || gameState ===
'BOSS_FIGHT')) { // Barra espaciadora
shoot();
}
if (event.key === 'p' || event.key === 'P') { // Pausa
togglePause();
}
}
function onKeyUp(event) {
keys[event.key.toLowerCase()] = false;
}

let isPaused = false;


function togglePause() {
if (gameState !== 'PLAYING' && gameState !== 'BOSS_FIGHT' && !isPaused)
return;

isPaused = !isPaused;
if (isPaused) {
showMenu("Juego Pausado", false); // No mostrar botón de inicio
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
} else {
messageCenterUI.style.display = 'none';
if (!animationFrameId) animate();
}
}

function handlePlayerMovement() {
if (!player || isPaused) return;

const moveSpeed = playerSpeed;


const rotationSpeed = 0.03;

// Movimiento adelante/atrás (simplificado, siempre adelante)


// player.mesh.translateZ(-moveSpeed * 2); // Movimiento constante
hacia adelante

// Propulsor visible al moverse


player.engineFlame.visible = false;

if (keys['w'] || keys['arrowup']) { // Mover arriba (Pitch up)


player.mesh.rotateX(rotationSpeed);
player.engineFlame.visible = true;
}
if (keys['s'] || keys['arrowdown']) { // Mover abajo (Pitch down)
player.mesh.rotateX(-rotationSpeed);
player.engineFlame.visible = true;
}
if (keys['a'] || keys['arrowleft']) { // Mover izquierda (Yaw left)
player.mesh.rotateY(rotationSpeed);
player.engineFlame.visible = true;
}
if (keys['d'] || keys['arrowright']) { // Mover derecha (Yaw right)
player.mesh.rotateY(-rotationSpeed);
player.engineFlame.visible = true;
}
if (keys['q']) { // Rotar izquierda (Roll left)
player.mesh.rotateZ(rotationSpeed);
player.engineFlame.visible = true;
}
if (keys['e']) { // Rotar derecha (Roll right)
player.mesh.rotateZ(-rotationSpeed);
player.engineFlame.visible = true;
}

// Movimiento hacia adelante en la dirección que mira el avión


const forward = new THREE.Vector3(0, 0, -1);
forward.applyQuaternion(player.mesh.quaternion);
player.mesh.position.add(forward.multiplyScalar(moveSpeed * 1.5)); //
Ajustar velocidad de avance

// Mantener al jugador dentro de ciertos límites (opcional)


const limit = 80;
player.mesh.position.x = Math.max(-limit, Math.min(limit,
player.mesh.position.x));
player.mesh.position.y = Math.max(-limit/2, Math.min(limit/2,
player.mesh.position.y));
// No limitar Z para que pueda avanzar

// Actualizar la caja de colisión del jugador


player.boundingBox.setFromObject(player.mesh);
}

let lastShotTime = 0;
const shootCooldown = 200; // milisegundos

function shoot() {
if (!player || isPaused) return;
const currentTime = Date.now();
if (currentTime - lastShotTime < shootCooldown) return; // Respetar
cooldown
lastShotTime = currentTime;

const projectileStartPos = player.mesh.position.clone();


const direction = new THREE.Vector3(0,0,-1); // Disparar hacia adelante
direction.applyQuaternion(player.mesh.quaternion); // Aplicar rotación
del jugador

// Ajustar la posición inicial del proyectil para que salga de la nariz


del avión
const offset = direction.clone().multiplyScalar(2.5); // Distancia
desde el centro del avión
projectileStartPos.add(offset);

const projectile = createProjectile(true, projectileStartPos,


direction);
playerProjectiles.push(projectile);
scene.add(projectile.mesh);
}

function enemyShoot(enemy) {
if (!enemy || isPaused || !player) return;
const currentTime = Date.now();
const enemyShootCooldown = enemy.type === 'boss' ? 1000 : 2000; // Jefe
dispara más rápido
if (currentTime - enemy.lastShotTime < enemyShootCooldown) return;
enemy.lastShotTime = currentTime;

const projectileStartPos = enemy.mesh.position.clone();


const direction =
player.mesh.position.clone().sub(enemy.mesh.position).normalize();
const projectile = createProjectile(false, projectileStartPos,
direction);
enemyProjectiles.push(projectile);
scene.add(projectile.mesh);
}

// Lógica del juego


function updateProjectiles() {
if (isPaused) return;
// Proyectiles del jugador
for (let i = playerProjectiles.length - 1; i >= 0; i--) {
const p = playerProjectiles[i];

p.mesh.position.add(p.direction.clone().multiplyScalar(projectileSpeed));
p.boundingBox.setFromObject(p.mesh);

// Eliminar si está muy lejos


if (p.mesh.position.z < -300 || Math.abs(p.mesh.position.x) > 200
|| Math.abs(p.mesh.position.y) > 200) {
scene.remove(p.mesh);
playerProjectiles.splice(i, 1);
continue;
}

// Colisión con enemigos


for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
if (p.boundingBox.intersectsBox(enemy.boundingBox)) {
scene.remove(p.mesh);
playerProjectiles.splice(i, 1);
enemy.health -= 50; // Daño del proyectil
if (enemy.health <= 0) {
scene.remove(enemy.mesh);
enemies.splice(j, 1);
score += 100;
}
break; // El proyectil solo golpea a un enemigo
}
}
// Colisión con el jefe
if (boss && p.boundingBox.intersectsBox(boss.boundingBox)) {
scene.remove(p.mesh);
playerProjectiles.splice(i, 1);
boss.health -= 25; // Daño al jefe
if (boss.health <= 0 && gameState !== 'VICTORY') {
score += 1000;
winGame();
}
}
}

// Proyectiles enemigos
for (let i = enemyProjectiles.length - 1; i >= 0; i--) {
const p = enemyProjectiles[i];

p.mesh.position.add(p.direction.clone().multiplyScalar(projectileSpeed * 0.7)); //
Más lentos
p.boundingBox.setFromObject(p.mesh);

if (p.mesh.position.z > 50 || Math.abs(p.mesh.position.x) > 200 ||


Math.abs(p.mesh.position.y) > 200) {
scene.remove(p.mesh);
enemyProjectiles.splice(i, 1);
continue;
}

if (player && p.boundingBox.intersectsBox(player.boundingBox)) {


scene.remove(p.mesh);
enemyProjectiles.splice(i, 1);
playerHit();
}
}
}

function updateEnemies() {
if (isPaused || !player) return;
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
// Movimiento simple: hacia el jugador
const direction =
player.mesh.position.clone().sub(enemy.mesh.position).normalize();
enemy.mesh.position.add(direction.multiplyScalar(enemySpeed));
enemy.mesh.lookAt(player.mesh.position); // Siempre miran al
jugador
enemy.boundingBox.setFromObject(enemy.mesh);

// Disparar al jugador
if (Math.random() < 0.01) { // Probabilidad de disparo
enemyShoot(enemy);
}

// Colisión enemigo con jugador


if (player && enemy.boundingBox.intersectsBox(player.boundingBox))
{
scene.remove(enemy.mesh);
enemies.splice(i,1);
playerHit(50); // Daño por colisión
}

// Si se alejan demasiado
if (enemy.mesh.position.z > camera.position.z + 20) {
scene.remove(enemy.mesh);
enemies.splice(i,1);
}
}
}

function updateBoss() {
if (!boss || isPaused || !player) return;

boss.mesh.lookAt(player.mesh.position);
boss.boundingBox.setFromObject(boss.mesh);

// Movimiento y patrones de ataque del jefe


boss.patternTimer += 0.016; // Aproximadamente 1/60 de segundo
if (boss.attackPattern === 'idle' && boss.patternTimer > 5) { //
Cambiar patrón cada 5 seg
const patterns = ['charge', 'circle_strafe', 'rapid_fire'];
boss.attackPattern = patterns[Math.floor(Math.random() *
patterns.length)];
boss.patternTimer = 0;
}

switch (boss.attackPattern) {
case 'charge':
const chargeDirection =
player.mesh.position.clone().sub(boss.mesh.position).normalize();

boss.mesh.position.add(chargeDirection.multiplyScalar(enemySpeed * 1.5));
if (boss.patternTimer > 3) boss.attackPattern = 'idle'; //
Duración de la carga
break;
case 'circle_strafe':
// Moverse en círculo alrededor del jugador (simplificado)
boss.mesh.position.x += Math.sin(boss.patternTimer) * 0.2;
boss.mesh.position.z += Math.cos(boss.patternTimer) * 0.2;
if (Math.random() < 0.05) enemyShoot(boss);
if (boss.patternTimer > 8) boss.attackPattern = 'idle';
break;
case 'rapid_fire':
if (Math.random() < 0.1) enemyShoot(boss); // Disparar más
frecuentemente
if (boss.patternTimer > 4) boss.attackPattern = 'idle';
break;
}
// Colisión jefe con jugador
if (player && boss.boundingBox.intersectsBox(player.boundingBox)) {
playerHit(100); // Mucho daño si el jefe te toca
}
}

function playerHit(damage = 25) {


if (gameState === 'GAME_OVER' || gameState === 'VICTORY') return;
lives--; // O reducir salud del jugador si se implementa
// Efecto visual simple (parpadeo)
if (player) {
player.mesh.visible = false;
setTimeout(() => { if(player) player.mesh.visible = true; }, 100);
setTimeout(() => { if(player) player.mesh.visible = false; }, 200);
setTimeout(() => { if(player) player.mesh.visible = true; }, 300);
}

if (lives <= 0) {
gameOver();
}
updateUI();
}

function nextWave() {
if (gameState === 'GAME_OVER' || gameState === 'VICTORY') return;

currentWave++;
if (currentWave > waves.length) {
winGame(); // Si no hay más olas definidas y no hay jefe, o si el
jefe era la última ola
return;
}

updateUI();
const waveData = waves[currentWave - 1];

if (waveData.type === 'boss') {


gameState = 'BOSS_FIGHT';
showTemporaryMessage(`¡JEFE FINAL!`, 3000);
if (boss) scene.remove(boss.mesh); // Limpiar jefe anterior si
existe
boss = createAlienBoss();
scene.add(boss.mesh);
} else {
gameState = 'PLAYING';
showTemporaryMessage(`Oleada ${currentWave}`, 2000);
for (let i = 0; i < waveData.enemies; i++) {
const enemy = createEnemy(waveData.type);
enemies.push(enemy);
scene.add(enemy.mesh);
}
}
}

function checkWaveCompletion() {
if (gameState === 'PLAYING' && enemies.length === 0) {
nextWave();
}
}

function updateUI() {
scoreUI.textContent = `Puntuación: ${score}`;
livesUI.textContent = `Vidas: ${lives}`;
if (gameState === 'BOSS_FIGHT' && boss) {
waveInfoUI.textContent = `Jefe: ${Math.max(0, boss.health)} HP`;
} else if (gameState === 'PLAYING') {
waveInfoUI.textContent = `Oleada: ${currentWave} / $
{waves.filter(w => w.type !== 'boss').length} (Enemigos: ${enemies.length})`;
} else {
waveInfoUI.textContent = `Oleada: -`;
}
}

function showMenu(message, showStartBtn = true) {


messageTextUI.textContent = message;
startButtonUI.style.display = showStartBtn ? 'block' : 'none';
messageCenterUI.style.display = 'block';
}

function showTemporaryMessage(message, duration) {


const tempMessageDiv = document.createElement('div');
tempMessageDiv.textContent = message;
tempMessageDiv.style.position = 'absolute';
tempMessageDiv.style.top = '40%';
tempMessageDiv.style.left = '50%';
tempMessageDiv.style.transform = 'translate(-50%, -50%)';
tempMessageDiv.style.fontSize = '2.5em';
tempMessageDiv.style.color = 'white';
tempMessageDiv.style.backgroundColor = 'rgba(0,0,0,0.7)';
tempMessageDiv.style.padding = '20px';
tempMessageDiv.style.borderRadius = '10px';
tempMessageDiv.style.zIndex = '100';
document.body.appendChild(tempMessageDiv);
setTimeout(() => {
if (document.body.contains(tempMessageDiv)) {
document.body.removeChild(tempMessageDiv);
}
}, duration);
}

function gameOver() {
gameState = 'GAME_OVER';
showMenu(`¡Fin del Juego! Puntuación Final: ${score}`);
if (player) {
scene.remove(player.mesh);
player = null;
}
// Limpiar enemigos y proyectiles restantes
enemies.forEach(e => scene.remove(e.mesh));
enemies = [];
playerProjectiles.forEach(p => scene.remove(p.mesh));
playerProjectiles = [];
enemyProjectiles.forEach(p => scene.remove(p.mesh));
enemyProjectiles = [];
if (boss) scene.remove(boss.mesh);
boss = null;

if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}

function winGame() {
gameState = 'VICTORY';
showMenu(`¡VICTORIA! Has derrotado al jefe. Puntuación Final: $
{score}`);
if (player) {
// Quizás alguna animación de victoria para el jugador
}
// Limpiar enemigos y proyectiles restantes
enemies.forEach(e => scene.remove(e.mesh));
enemies = [];
playerProjectiles.forEach(p => scene.remove(p.mesh));
playerProjectiles = [];
enemyProjectiles.forEach(p => scene.remove(p.mesh));
enemyProjectiles = [];
if (boss) scene.remove(boss.mesh); // Asegurarse que el jefe se quite
boss = null;

if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}
// Bucle de animación
function animate() {
animationFrameId = requestAnimationFrame(animate);
if (isPaused) return;

if (gameState === 'PLAYING' || gameState === 'BOSS_FIGHT') {


handlePlayerMovement();
updateProjectiles();
updateEnemies();
if (boss) updateBoss();
checkWaveCompletion();
updateUI(); // Actualizar UI constantemente durante el juego
}

// Mover la cámara para seguir al jugador de forma más suave


if (player) {
const targetPosition = player.mesh.position.clone().add(new
THREE.Vector3(0, 3, 12).applyQuaternion(player.mesh.quaternion));
camera.position.lerp(targetPosition, 0.05); // Suavizar el
movimiento de la cámara
camera.lookAt(player.mesh.position); // Cámara siempre mira al
jugador
}

renderer.render(scene, camera);
}

function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

// Iniciar el juego
init();

</script>
</body>
</html>

You might also like