Aviones
Aviones
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;
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;
// 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();
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
}
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();
// 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 });
// 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);
// 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: 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);
// "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);
}
projectileMesh.position.copy(startPosition);
// 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;
}
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;
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;
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;
p.mesh.position.add(p.direction.clone().multiplyScalar(projectileSpeed));
p.boundingBox.setFromObject(p.mesh);
// 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);
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);
}
// 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);
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
}
}
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];
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 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;
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>