Mouse Shadow Fight: Gatos vs Perros 3D
- samuel gaitan
- 27 jun 2025
- 14 Min. de lectura

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow Fight: Gatos vs Perros - Modo Infinito</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Orbitron', monospace;
overflow: hidden;
color: white;
cursor: crosshair;
}
position: relative;
width: 100vw;
height: 100vh;
}
display: block;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
.health-bar-container {
position: absolute;
top: 20px;
width: 350px;
height: 35px;
border: 3px solid #fff;
border-radius: 18px;
background: rgba(0, 0, 0, 0.8);
overflow: hidden;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
left: 20px;
}
right: 20px;
}
.health-bar {
width: 100%;
height: 100%;
border-radius: 15px;
transition: width 0.3s ease;
position: relative;
}
.health-cat {
box-shadow: inset 0 0 10px rgba(52, 152, 219, 0.5);
}
.health-dog {
box-shadow: inset 0 0 10px rgba(231, 76, 60, 0.5);
}
.health-bar::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent 30%, rgba(255,255,255,0.3) 50%, transparent 70%);
animation: shine 2s infinite;
}
@keyframes shine {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.player-name {
position: absolute;
bottom: -30px;
width: 100%;
text-align: center;
font-size: 16px;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}
#round-counter {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.9);
padding: 15px 25px;
border-radius: 25px;
border: 3px solid #ffd700;
font-size: 20px;
font-weight: bold;
color: #ffd700;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.8);
box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
}
#battle-counter {
position: absolute;
top: 140px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.9);
padding: 10px 20px;
border-radius: 15px;
border: 2px solid #e74c3c;
font-size: 16px;
font-weight: bold;
color: #e74c3c;
text-shadow: 0 0 10px rgba(231, 76, 60, 0.8);
box-shadow: 0 0 20px rgba(231, 76, 60, 0.3);
}
.combo-display {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 60px;
font-weight: 900;
color: #ffd700;
text-shadow:
0 0 10px rgba(255, 215, 0, 0.8),
0 0 20px rgba(255, 215, 0, 0.6),
0 0 30px rgba(255, 215, 0, 0.4);
opacity: 0;
transition: all 0.5s ease;
pointer-events: none;
z-index: 1000;
}
.combo-display.show {
opacity: 1;
transform: translate(-50%, -50%) scale(1.3);
}
.power-indicator {
position: absolute;
bottom: 80px;
width: 220px;
height: 25px;
border: 3px solid #fff;
border-radius: 12px;
background: rgba(0, 0, 0, 0.8);
overflow: hidden;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
}
left: 20px;
}
right: 20px;
}
.power-bar {
height: 100%;
border-radius: 9px;
transition: width 0.3s ease;
width: 0%;
box-shadow: inset 0 0 10px rgba(155, 89, 182, 0.5);
}
#ai-indicator {
position: absolute;
top: 80px;
right: 20px;
background: rgba(0, 0, 0, 0.9);
padding: 12px 18px;
border-radius: 12px;
border: 3px solid #3498db;
font-size: 13px;
color: #3498db;
box-shadow: 0 0 20px rgba(52, 152, 219, 0.3);
}
#avatar-display {
position: absolute;
top: 80px;
left: 20px;
background: rgba(0, 0, 0, 0.9);
padding: 15px 20px;
border-radius: 15px;
border: 3px solid #ffd700;
font-size: 14px;
color: #ffd700;
box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
text-align: center;
}
#next-round-timer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.95);
padding: 30px 50px;
border-radius: 25px;
border: 4px solid #ffd700;
text-align: center;
font-size: 24px;
color: #ffd700;
text-shadow: 0 0 20px rgba(255, 215, 0, 0.8);
box-shadow: 0 0 50px rgba(255, 215, 0, 0.5);
opacity: 0;
pointer-events: none;
z-index: 1000;
transition: opacity 0.3s ease;
}
#next-round-timer.show {
opacity: 1;
}
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.9);
padding: 15px 25px;
border-radius: 15px;
border: 2px solid #3498db;
font-size: 14px;
text-align: center;
max-width: 500px;
}
.hidden {
display: none !important;
}
.energy-wave {
position: absolute;
pointer-events: none;
z-index: 500;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="ui">
<!-- Barras de vida -->
<div id="player1Health" class="health-bar-container">
<div class="health-bar health-cat" id="p1HealthBar"></div>
<div class="player-name" id="p1Name">GATO AZUL</div>
</div>
<div id="player2Health" class="health-bar-container">
<div class="health-bar health-dog" id="p2HealthBar"></div>
<div class="player-name" id="p2Name">PERRO ROJO</div>
</div>
<!-- Contador de rondas -->
<div id="round-counter">RONDA 1</div>
<!-- Contador de batallas -->
<div id="battle-counter" class="battle-count">BATALLA #1</div>
<!-- Indicadores de poder -->
<div id="player1Power" class="power-indicator">
<div class="power-bar" id="p1PowerBar"></div>
</div>
<div id="player2Power" class="power-indicator">
<div class="power-bar" id="p2PowerBar"></div>
</div>
<!-- Display de combos -->
<div id="comboDisplay" class="combo-display"></div>
<!-- Display de avatar actual -->
<div id="avatar-display">
<div><strong>🎭 LUCHADOR ACTUAL:</strong></div>
<div id="current-avatar">🐱 GATO AZUL</div>
<div style="font-size: 11px; margin-top: 5px; color: #bdc3c7;">
Cambia automáticamente
</div>
</div>
<!-- Timer para próxima ronda -->
<div id="next-round-timer">
<div><strong>⚔️ PRÓXIMA BATALLA ⚔️</strong></div>
<div id="timer-text">Preparando nuevo luchador...</div>
<div id="countdown" style="font-size: 48px; margin-top: 15px;">3</div>
</div>
<!-- Instrucciones -->
<div id="instructions">
<strong>🖱️ CONTROLES:</strong><br>
<span style="color: #1abc9c;">1 CLICK = PATADA</span> |
<span style="color: #3498db;">2 CLICKS = PUÑO</span> |
<span style="color: #9b59b6;">3 CLICKS = ONDA VITAL</span><br>
<span style="color: #e74c3c;">CLICK MANTENIDO = RECARGAR VIDA</span>
</div>
<!-- Indicador de IA -->
<div id="ai-indicator">
<strong>🤖 IA ACTIVA</strong><br>
Dificultad: <span id="ai-difficulty">NORMAL</span>
</div>
</div>
</div>
<script>
// Variables globales del juego
let scene, camera, renderer, clock;
let player1, player2;
let gameState = 'playing'; // always playing now
let selectedAvatar = { type: 'cat', color: 'blue' };
let round = 1;
let battleCount = 1;
let wins = { player1: 0, player2: 0 };
// Lista de avatares disponibles
const catColors = ['blue', 'yellow', 'orange'];
const dogColors = ['red', 'black', 'white'];
// Contador de clicks
let clickCount = 0;
let lastClickTime = 0;
const clickThreshold = 300; // ms entre clicks para considerarlos consecutivos
let isLongClick = false;
let longClickTimer = null;
const longClickThreshold = 1000; // ms para considerar un click largo
// IA del oponente
let aiData = {
lastAction: 0,
difficulty: 'normal',
reactionTime: 1000,
aggressiveness: 0.4,
lastPlayerPosition: { x: 0, z: 0 },
comboCooldown: 0,
strategy: 'balanced',
nextAction: null,
actionDelay: 0
};
// Estados de los jugadores
let gameData = {
player1: {
health: 100,
power: 0,
position: { x: -3, y: 0, z: 0 },
isAttacking: false,
isBlocking: false,
combo: 0,
lastAttack: 0
},
player2: {
health: 100,
power: 0,
position: { x: 3, y: 0, z: 0 },
isAttacking: false,
isBlocking: false,
combo: 0,
lastAttack: 0
}
};
// Configuración de colores para gatos
const catColorSettings = {
blue: { primary: 0x3498db, secondary: 0x2980b9, accent: 0x5dade2, name: 'GATO AZUL' },
yellow: { primary: 0xf1c40f, secondary: 0xf39c12, accent: 0xffd700, name: 'GATO AMARILLO' },
orange: { primary: 0xe67e22, secondary: 0xd35400, accent: 0xe74c3c, name: 'GATO NARANJA' }
};
const dogColorSettings = {
red: { primary: 0xe74c3c, secondary: 0xc0392b, accent: 0xff6b6b, name: 'PERRO ROJO' },
black: { primary: 0x2c3e50, secondary: 0x34495e, accent: 0x7f8c8d, name: 'PERRO NEGRO' },
white: { primary: 0xecf0f1, secondary: 0xbdc3c7, accent: 0xffffff, name: 'PERRO BLANCO' }
};
function selectRandomAvatar() {
// Seleccionar color aleatorio para gato y perro
const randomCatIndex = Math.floor(Math.random() * catColors.length);
const randomDogIndex = Math.floor(Math.random() * dogColors.length);
const catColor = catColors[randomCatIndex];
const dogColor = dogColors[randomDogIndex];
selectedAvatar = { type: 'cat', color: catColor };
// Actualizar display
document.getElementById('current-avatar').textContent = `🐱 ${catColorSettings[catColor].name}`;
document.getElementById('p1Name').textContent = catColorSettings[catColor].name;
document.getElementById('p2Name').textContent = dogColorSettings[dogColor].name;
return { catColor, dogColor };
}
function resetGameState() {
// Resetear estadísticas de jugadores
gameData.player1.health = 100;
gameData.player1.power = 0;
gameData.player1.position = { x: -3, y: 0, z: 0 };
gameData.player1.isAttacking = false;
gameData.player1.isBlocking = false;
gameData.player1.combo = 0;
gameData.player2.health = 100;
gameData.player2.power = 0;
gameData.player2.position = { x: 3, y: 0, z: 0 };
gameData.player2.isAttacking = false;
gameData.player2.isBlocking = false;
gameData.player2.combo = 0;
round = 1;
wins = { player1: 0, player2: 0 };
updateUI();
}
function showNextRoundTimer(callback) {
const timerEl = document.getElementById('next-round-timer');
const countdownEl = document.getElementById('countdown');
const timerTextEl = document.getElementById('timer-text');
// Seleccionar nuevo avatar
const { catColor, dogColor } = selectRandomAvatar();
timerTextEl.textContent = `Nuevos luchadores: 🐱 ${catColorSettings[catColor].name} vs 🐶 ${dogColorSettings[dogColor].name}`;
timerEl.classList.add('show');
let countdown = 3;
countdownEl.textContent = countdown;
const countdownInterval = setInterval(() => {
countdown--;
if (countdown > 0) {
countdownEl.textContent = countdown;
} else {
countdownEl.textContent = '¡LUCHA!';
setTimeout(() => {
timerEl.classList.remove('show');
if (callback) callback();
}, 500);
clearInterval(countdownInterval);
}
}, 1000);
}
function startNewBattle() {
battleCount++;
document.getElementById('battle-counter').textContent = `BATALLA #${battleCount}`;
// Limpiar escena
if (player1) {
scene.remove(player1);
player1 = null;
}
if (player2) {
scene.remove(player2);
player2 = null;
}
// Resetear estado
resetGameState();
// Crear nuevos personajes
createCharacters();
// Volver al estado de juego
gameState = 'playing';
}
function checkGameOver() {
if (gameData.player1.health <= 0) {
wins.player2++;
setTimeout(() => {
showNextRoundTimer(() => {
startNewBattle();
});
}, 1500);
return true;
} else if (gameData.player2.health <= 0) {
wins.player1++;
setTimeout(() => {
showNextRoundTimer(() => {
startNewBattle();
});
}, 1500);
return true;
}
return false;
}
// Inicialización
function init() {
// Seleccionar avatar inicial aleatorio
selectRandomAvatar();
// Configurar Three.js
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('gameCanvas'),
antialias: true,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setClearColor(0x0a0a0a, 1);
renderer.physicallyCorrectLights = true;
clock = new THREE.Clock();
// Configurar escena
setupScene();
setupLights();
createCharacters();
// Event listeners
document.addEventListener('mousedown', onMouseDown);
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('click', onClick);
window.addEventListener('resize', onWindowResize);
// Configurar IA
setupAI();
// Actualizar UI inicial
updateUI();
// Iniciar loop de renderizado
animate();
}
function onMouseDown(e) {
// Iniciar temporizador para click largo
isLongClick = false;
longClickTimer = setTimeout(() => {
isLongClick = true;
handleLongClick();
}, longClickThreshold);
}
function onMouseUp(e) {
// Cancelar temporizador de click largo
clearTimeout(longClickTimer);
}
function onClick(e) {
if (gameState !== 'playing') return;
const currentTime = Date.now();
// Verificar si es un click consecutivo
if (currentTime - lastClickTime < clickThreshold) {
clickCount++;
} else {
clickCount = 1;
}
lastClickTime = currentTime;
// Esperar un poco para ver si hay más clicks
setTimeout(() => {
if (Date.now() - lastClickTime >= clickThreshold) {
// No hay más clicks, procesar el combo
if (clickCount === 1 && !isLongClick) {
handlePlayerAction('kick');
} else if (clickCount === 2) {
handlePlayerAction('punch');
} else if (clickCount >= 3) {
handlePlayerAction('energyWave');
}
clickCount = 0;
}
}, clickThreshold + 50);
}
function handleLongClick() {
if (gameState !== 'playing') return;
// Recargar vida
gameData.player1.health = Math.min(100, gameData.player1.health + 30);
gameData.player1.power = Math.min(100, gameData.player1.power + 20);
// Animación de recarga
animateHeal(player1);
updateUI();
showCombo('+30 VIDA');
}
function animateHeal(character) {
if (!character || character.userData.isAnimating) return;
character.userData.isAnimating = true;
// Efecto de curación
for (let i = 0; i < 15; i++) {
setTimeout(() => {
const particle = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 8, 8),
new THREE.MeshBasicMaterial({ color: 0x1abc9c })
);
particle.position.copy(character.position);
particle.position.y += 0.5;
particle.position.x += (Math.random() - 0.5) * 2;
particle.position.z += (Math.random() - 0.5) * 2;
scene.add(particle);
// Animar partícula hacia arriba
const animateParticle = () => {
particle.position.y += 0.1;
particle.material.opacity -= 0.02;
if (particle.material.opacity > 0) {
requestAnimationFrame(animateParticle);
} else {
scene.remove(particle);
}
};
animateParticle();
}, i * 100);
}
setTimeout(() => {
character.userData.isAnimating = false;
}, 1500);
}
function setupScene() {
// Crear suelo
const floorGeometry = new THREE.PlaneGeometry(20, 20);
const floorMaterial = new THREE.MeshLambertMaterial({
color: 0x2c3e50,
transparent: true,
opacity: 0.8
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
// Configurar cámara
camera.position.set(0, 8, 10);
camera.lookAt(0, 0, 0);
}
function setupLights() {
// Luz ambiental
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
scene.add(ambientLight);
// Luz direccional principal
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// Luz de relleno
const fillLight = new THREE.DirectionalLight(0x3498db, 0.3);
fillLight.position.set(-5, 5, -5);
scene.add(fillLight);
}
function createCharacters() {
// Crear jugador 1 (gato) con avatar seleccionado
const catColor = catColorSettings[selectedAvatar.color];
player1 = createCatCharacter(catColor);
player1.position.set(gameData.player1.position.x, gameData.player1.position.y, gameData.player1.position.z);
player1.castShadow = true;
scene.add(player1);
// Crear jugador 2 (perro) - color aleatorio
const dogColor = dogColorSettings[Object.keys(dogColorSettings)[0]]; // Comienza con rojo
player2 = createDogCharacter(dogColor);
player2.position.set(gameData.player2.position.x, gameData.player2.position.y, gameData.player2.position.z);
player2.castShadow = true;
scene.add(player2);
}
function createCatCharacter(colors) {
const catGroup = new THREE.Group();
// Cuerpo principal
const bodyGeometry = new THREE.CylinderGeometry(0.6, 0.8, 1.5, 8);
const bodyMaterial = new THREE.MeshLambertMaterial({ color: colors.primary });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 1.2;
catGroup.add(body);
// Cabeza
const headGeometry = new THREE.SphereGeometry(0.6, 8, 6);
const headMaterial = new THREE.MeshLambertMaterial({ color: colors.secondary });
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 2.3;
catGroup.add(head);
// Orejas
const earGeometry = new THREE.ConeGeometry(0.25, 0.5, 4);
const earMaterial = new THREE.MeshLambertMaterial({ color: colors.accent });
const leftEar = new THREE.Mesh(earGeometry, earMaterial);
leftEar.position.set(-0.35, 2.7, 0.1);
leftEar.rotation.z = -0.3;
catGroup.add(leftEar);
const rightEar = new THREE.Mesh(earGeometry, earMaterial);
rightEar.position.set(0.35, 2.7, 0.1);
rightEar.rotation.z = 0.3;
catGroup.add(rightEar);
// Cola
const tailGeometry = new THREE.CylinderGeometry(0.1, 0.2, 1.5, 6);
const tailMaterial = new THREE.MeshLambertMaterial({ color: colors.primary });
const tail = new THREE.Mesh(tailGeometry, tailMaterial);
tail.position.set(0, 1, -1);
tail.rotation.x = Math.PI / 3;
catGroup.add(tail);
return catGroup;
}
function createDogCharacter(colors) {
const dogGroup = new THREE.Group();
// Cuerpo principal
const bodyGeometry = new THREE.BoxGeometry(1.2, 1, 1.8);
const bodyMaterial = new THREE.MeshLambertMaterial({ color: colors.primary });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 1.2;
dogGroup.add(body);
// Cabeza
const headGeometry = new THREE.BoxGeometry(0.8, 0.6, 0.8);
const headMaterial = new THREE.MeshLambertMaterial({ color: colors.secondary });
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 1.8;
head.position.z = 0.8;
dogGroup.add(head);
// Orejas
const earGeometry = new THREE.SphereGeometry(0.2, 8, 8);
const earMaterial = new THREE.MeshLambertMaterial({ color: colors.accent });
const leftEar = new THREE.Mesh(earGeometry, earMaterial);
leftEar.position.set(-0.3, 1.9, 0.8);
leftEar.scale.set(0.5, 1, 0.5);
dogGroup.add(leftEar);
const rightEar = new THREE.Mesh(earGeometry, earMaterial);
rightEar.position.set(0.3, 1.9, 0.8);
rightEar.scale.set(0.5, 1, 0.5);
dogGroup.add(rightEar);
// Cola
const tailGeometry = new THREE.CylinderGeometry(0.1, 0.15, 0.8);
const tailMaterial = new THREE.MeshLambertMaterial({ color: colors.primary });
const tail = new THREE.Mesh(tailGeometry, tailMaterial);
tail.position.set(0, 1, -0.8);
tail.rotation.x = Math.PI / 4;
dogGroup.add(tail);
return dogGroup;
}
function setupAI() {
switch(aiData.difficulty) {
case 'easy':
aiData.reactionTime = 1800;
aiData.aggressiveness = 0.2;
break;
case 'normal':
aiData.reactionTime = 1200;
aiData.aggressiveness = 0.4;
break;
case 'hard':
aiData.reactionTime = 800;
aiData.aggressiveness = 0.7;
break;
}
}
function handlePlayerAction(action) {
if (gameState !== 'playing' || !player1 || !player2) return;
const player = 'player1';
const character = player1;
const opponent = 'player2';
const opponentChar = player2;
switch(action) {
case 'punch':
performAttack(player, opponent, character, opponentChar, 'punch', 15);
break;
case 'kick':
performAttack(player, opponent, character, opponentChar, 'kick', 22);
break;
case 'energyWave':
if (gameData[player].power >= 60) {
performEnergyWave(player, opponent, character, opponentChar);
gameData[player].power -= 60;
}
break;
}
updateUI();
}
function performAttack(attackerKey, defenderKey, attackerChar, defenderChar, type, damage) {
if (gameData[attackerKey].isAttacking) return;
gameData[attackerKey].isAttacking = true;
setTimeout(() => gameData[attackerKey].isAttacking = false, 500);
// Animación de ataque
animateAttack(attackerChar, type);
// Verificar distancia
const distance = Math.abs(attackerChar.position.x - defenderChar.position.x);
if (distance < 2) {
// Golpe exitoso
gameData[defenderKey].health -= damage;
gameData[attackerKey].power = Math.min(100, gameData[attackerKey].power + 10);
gameData[attackerKey].combo++;
// Mostrar combo
if (gameData[attackerKey].combo > 1) {
showCombo(gameData[attackerKey].combo + ' HITS');
}
// Animación de impacto
animateHit(defenderChar);
// Partículas de impacto
createImpactEffect(defenderChar.position);
}
updateUI();
checkGameOver();
}
function performEnergyWave(attackerKey, defenderKey, attackerChar, defenderChar) {
if (gameData[attackerKey].isAttacking) return;
gameData[attackerKey].isAttacking = true;
setTimeout(() => gameData[attackerKey].isAttacking = false, 1200);
// Animación del lanzador
animateEnergyWave(attackerChar);
// Crear onda de energía visual
createEnergyWaveEffect(attackerChar.position, defenderChar.position);
// El ataque de onda vital siempre impacta
const damage = 40;
gameData[defenderKey].health -= damage;
gameData[attackerKey].combo += 3;
showCombo('🌊 ONDA VITAL! 🌊');
animateHit(defenderChar);
// Efecto especial más dramático
createWaveImpactEffect(defenderChar.position);
updateUI();
checkGameOver();
}
function animateAttack(character, type) {
if (!character || character.userData.isAnimating) return;
character.userData.isAnimating = true;
const originalX = character.position.x;
// Movimiento hacia adelante
const direction = character.position.x < 0 ? 1 : -1;
character.position.x += direction * 0.5;
// Rotación de ataque
const targetRotation = type === 'kick' ? Math.PI / 4 : Math.PI / 6;
character.rotation.y = direction * targetRotation;
setTimeout(() => {
character.position.x = originalX;
character.rotation.y = 0;
character.userData.isAnimating = false;
}, 300);
}
function animateEnergyWave(character) {
if (!character || character.userData.isAnimating) return;
character.userData.isAnimating = true;
const originalY = character.position.y;
const originalScale = { ...character.scale };
// Levitar y cargar energía
character.position.y += 0.8;
character.scale.set(1.1, 1.1, 1.1);
// Efecto de rotación
let rotation = 0;
const animateCharge = () => {
rotation += 0.1;
character.rotation.y = Math.sin(rotation) * 0.3;
if (rotation < Math.PI * 4) {
requestAnimationFrame(animateCharge);
}
};
animateCharge();
setTimeout(() => {
character.position.y = originalY;
character.scale.copy(originalScale);
character.rotation.set(0, 0, 0);
character.userData.isAnimating = false;
}, 1000);
}
function animateHit(character) {
if (!character) return;
const originalX = character.position.x;
const direction = character.position.x < 0 ? -1 : 1;
character.position.x += direction * 0.3;
setTimeout(() => {
character.position.x = originalX;
}, 200);
}
function createImpactEffect(position) {
const particleCount = 10;
for (let i = 0; i < particleCount; i++) {
const geometry = new THREE.SphereGeometry(0.05, 8, 8);
const material = new THREE.MeshBasicMaterial({
color: Math.random() > 0.5 ? 0xff6b35 : 0xffd700,
transparent: true,
opacity: 0.8
});
const particle = new THREE.Mesh(geometry, material);
particle.position.copy(position);
particle.position.x += (Math.random() - 0.5) * 2;
particle.position.y += Math.random() + 0.5;
particle.position.z += (Math.random() - 0.5) * 2;
scene.add(particle);
// Animar partícula
const velocity = {
x: (Math.random() - 0.5) * 0.2,
y: Math.random() * 0.3 + 0.1,
z: (Math.random() - 0.5) * 0.2
};
let opacity = 0.8;
const animateParticle = () => {
particle.position.x += velocity.x;
particle.position.y += velocity.y;
particle.position.z += velocity.z;
velocity.y -= 0.01; // Gravedad
opacity -= 0.02;
particle.material.opacity = opacity;
if (opacity > 0) {
requestAnimationFrame(animateParticle);
} else {
scene.remove(particle);
}
};
animateParticle();
}
}
function createEnergyWaveEffect(startPos, targetPos) {
// Crear múltiples ondas de energía
for (let wave = 0; wave < 3; wave++) {
setTimeout(() => {
const waveGeometry = new THREE.RingGeometry(0.2, 1.5 + wave * 0.5, 16);
const waveMaterial = new THREE.MeshBasicMaterial({
color: wave % 2 === 0 ? 0x1abc9c : 0x3498db,
transparent: true,
opacity: 0.8 - wave * 0.2,
side: THREE.DoubleSide
});
const waveRing = new THREE.Mesh(waveGeometry, waveMaterial);
waveRing.position.copy(startPos);
waveRing.position.y += 1;
waveRing.rotation.x = -Math.PI / 2;
scene.add(waveRing);
// Animar onda
let distance = 0;
const totalDistance = Math.sqrt(
Math.pow(targetPos.x - startPos.x, 2) +
Math.pow(targetPos.z - startPos.z, 2)
);
const animateWave = () => {
distance += 0.2;
const progress = distance / totalDistance;
if (progress < 1) {
waveRing.position.x = startPos.x + (targetPos.x - startPos.x) * progress;
waveRing.position.z = startPos.z + (targetPos.z - startPos.z) * progress;
waveRing.scale.set(1 + progress, 1 + progress, 1 + progress);
requestAnimationFrame(animateWave);
} else {
scene.remove(waveRing);
}
};
animateWave();
}, wave * 200);
}
}
function createWaveImpactEffect(position) {
// Explosión de energía al impactar
const explosionGeometry = new THREE.SphereGeometry(1, 16, 16);
const explosionMaterial = new THREE.MeshBasicMaterial({
color: 0x9b59b6,
transparent: true,
opacity: 0.8
});
const explosion = new THREE.Mesh(explosionGeometry, explosionMaterial);
explosion.position.copy(position);
explosion.position.y += 1;
scene.add(explosion);
// Animar explosión
let scale = 0.5;
const animateExplosion = () => {
scale += 0.1;
explosion.scale.set(scale, scale, scale);
explosion.material.opacity = Math.max(0, 0.8 - scale * 0.1);
if (explosion.material.opacity > 0) {
requestAnimationFrame(animateExplosion);
} else {
scene.remove(explosion);
}
};
animateExplosion();
}
function showCombo(text) {
const comboDisplay = document.getElementById('comboDisplay');
comboDisplay.textContent = text;
comboDisplay.classList.add('show');
setTimeout(() => {
comboDisplay.classList.remove('show');
}, 1000);
}
function updateAI() {
if (gameState !== 'playing' || !player2) return;
const currentTime = Date.now();
// Verificar si es momento de que la IA actúe
if (currentTime - aiData.lastAction < aiData.reactionTime) return;
const distance = Math.abs(player1.position.x - player2.position.x);
const playerHealth = gameData.player1.health;
const aiHealth = gameData.player2.health;
// Determinar estrategia de IA
let strategy = aiData.strategy;
if (aiHealth < 30) strategy = 'aggressive';
else if (playerHealth < 30) strategy = 'defensive';
// Decidir acción de la IA
const actionProbability = Math.random();
if (distance > 3) {
// Moverse hacia el jugador
if (player2.position.x > player1.position.x && player2.position.x > -8) {
player2.position.x -= 0.15;
} else if (player2.position.x < player1.position.x && player2.position.x < 8) {
player2.position.x += 0.15;
}
// Movimiento en Z ocasional
if (Math.random() < 0.3) {
if (player2.position.z > player1.position.z && player2.position.z > -8) {
player2.position.z -= 0.1;
} else if (player2.position.z < player1.position.z && player2.position.z < 8) {
player2.position.z += 0.1;
}
}
} else {
// En rango de ataque
if (strategy === 'aggressive' || actionProbability < aiData.aggressiveness) {
// Decidir tipo de ataque
if (gameData.player2.power >= 100 && Math.random() < 0.3) {
// Super ataque
performSpecialAttack('player2', 'player1', player2, player1, 'super', 50);
gameData.player2.power = 0;
} else if (gameData.player2.power >= 50 && Math.random() < 0.4) {
// Ataque de rayo
performSpecialAttack('player2', 'player1', player2, player1, 'lightning', 35);
gameData.player2.power -= 50;
} else if (Math.random() < 0.6) {
// Ataque básico
const attackType = Math.random() < 0.5 ? 'punch' : 'kick';
const damage = attackType === 'punch' ? 15 : 20;
performAttack('player2', 'player1', player2, player1, attackType, damage);
}
} else {
// Movimiento defensivo/evasivo
const evadeDirection = Math.random() < 0.5 ? -1 : 1;
if (player2.position.x + evadeDirection * 0.2 > -8 && player2.position.x + evadeDirection * 0.2 < 8) {
player2.position.x += evadeDirection * 0.2;
}
}
}
// Actualizar datos de IA
aiData.lastAction = currentTime;
aiData.lastPlayerPosition = { x: player1.position.x, z: player1.position.z };
gameData.player2.position = { ...player2.position };
}
function performSpecialAttack(attackerKey, defenderKey, attackerChar, defenderChar, type, damage) {
if (gameData[attackerKey].isAttacking) return;
gameData[attackerKey].isAttacking = true;
setTimeout(() => gameData[attackerKey].isAttacking = false, 1000);
// Animación especial
animateSpecialAttack(attackerChar, type);
// Efecto siempre golpea en ataques especiales
gameData[defenderKey].health -= damage;
gameData[attackerKey].combo += 2;
showCombo(`${type.toUpperCase()}!`);
animateHit(defenderChar);
createSpecialEffect(attackerChar.position, type);
updateUI();
checkGameOver();
}
function animateSpecialAttack(character, type) {
if (!character || character.userData.isAnimating) return;
character.userData.isAnimating = true;
const originalY = character.position.y;
if (type === 'lightning') {
// Salto con rayo
character.position.y += 1;
character.rotation.z = Math.PI * 2;
} else if (type === 'super') {
// Giro épico
character.rotation.y = Math.PI * 4;
character.scale.set(1.2, 1.2, 1.2);
}
setTimeout(() => {
character.position.y = originalY;
character.rotation.set(0, 0, 0);
character.scale.set(1, 1, 1);
character.userData.isAnimating = false;
}, 800);
}
function createSpecialEffect(position, type) {
if (type === 'lightning') {
// Efecto de rayo
for (let i = 0; i < 20; i++) {
const geometry = new THREE.CylinderGeometry(0.02, 0.02, Math.random() * 2 + 1);
const material = new THREE.MeshBasicMaterial({
color: 0x00ffff,
transparent: true,
opacity: 0.9
});
const bolt = new THREE.Mesh(geometry, material);
bolt.position.copy(position);
bolt.position.y += Math.random() * 3;
bolt.rotation.z = Math.random() * Math.PI;
scene.add(bolt);
setTimeout(() => scene.remove(bolt), 500);
}
} else if (type === 'super') {
// Efecto de super combo - explosión de energía
const ringGeometry = new THREE.RingGeometry(0.5, 2, 16);
const ringMaterial = new THREE.MeshBasicMaterial({
color: 0xff0080,
transparent: true,
opacity: 0.7,
side: THREE.DoubleSide
});
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
ring.position.copy(position);
ring.rotation.x = -Math.PI / 2;
scene.add(ring);
let scale = 0.1;
const animateRing = () => {
scale += 0.1;
ring.scale.set(scale, scale, scale);
ring.material.opacity = Math.max(0, 0.7 - scale * 0.1);
if (ring.material.opacity > 0) {
requestAnimationFrame(animateRing);
} else {
scene.remove(ring);
}
};
animateRing();
}
}
function updateUI() {
// Actualizar barras de vida
const p1Health = Math.max(0, gameData.player1.health);
const p2Health = Math.max(0, gameData.player2.health);
document.getElementById('p1HealthBar').style.width = p1Health + '%';
document.getElementById('p2HealthBar').style.width = p2Health + '%';
// Actualizar barras de poder
document.getElementById('p1PowerBar').style.width = gameData.player1.power + '%';
document.getElementById('p2PowerBar').style.width = gameData.player2.power + '%';
// Actualizar contador de rondas
document.getElementById('round-counter').textContent = `RONDA ${round}`;
}
function animate() {
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
// Actualizar IA del oponente
updateAI();
// Animaciones de idle para los personajes
if (player1 && !player1.userData.isAnimating) {
player1.position.y = Math.sin(Date.now() * 0.005) * 0.1;
player1.rotation.z = Math.sin(Date.now() * 0.003) * 0.05;
}
if (player2 && !player2.userData.isAnimating) {
player2.position.y = Math.sin(Date.now() * 0.005 + Math.PI) * 0.1;
player2.rotation.z = Math.sin(Date.now() * 0.003 + Math.PI) * 0.05;
}
// Efectos de luces dinámicas
const time = Date.now() * 0.001;
if (scene.children.length > 0) {
scene.children.forEach(child => {
if (child.type === 'PointLight') {
child.intensity = 0.8 + Math.sin(time * 2) * 0.3;
}
});
}
// Renderizar
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Inicializar el juego
init();
</script>
</body>
</html>



Comentarios