Spaces:
Running
Running
<html lang="pt-BR"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Space Invaders com Three.js</title> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
background-color: #000; | |
font-family: 'Orbitron', 'Arial', sans-serif; | |
--theme-color: #00bfff; | |
--theme-color-dark: #0099cc; | |
--theme-color-bright: #66ccff; | |
transition: background-color 0.5s ease; | |
} | |
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); | |
canvas { display: block; } | |
.planet-transition { | |
animation: flash 0.5s; | |
} | |
@keyframes flash { | |
0% { background-color: rgba(255, 255, 255, 0.1); } | |
50% { background-color: rgba(255, 255, 255, 0.3); } | |
100% { background-color: rgba(0, 0, 0, 0); } | |
} | |
#ui { | |
position: absolute; | |
top: 15px; | |
left: 15px; | |
color: #33ff33; | |
font-family: 'Orbitron', Arial, sans-serif; | |
display: none; | |
background-color: rgba(0, 0, 0, 0.5); | |
padding: 10px 15px; | |
border-radius: 10px; | |
border-left: 3px solid #33ff33; | |
box-shadow: 0 0 10px rgba(51, 255, 51, 0.5); | |
transition: all 0.3s ease; | |
} | |
#ui p { | |
margin: 5px 0; | |
text-shadow: 0 0 5px #33ff33; | |
} | |
#center-ui { | |
position: absolute; | |
top: 15px; | |
left: 50%; | |
transform: translateX(-50%); | |
color: #33ffff; | |
font-family: 'Orbitron', Arial, sans-serif; | |
text-align: center; | |
display: none; | |
background-color: rgba(0, 0, 0, 0.5); | |
padding: 10px 15px; | |
border-radius: 10px; | |
border-bottom: 3px solid #33ffff; | |
box-shadow: 0 0 10px rgba(51, 255, 255, 0.5); | |
transition: all 0.3s ease; | |
} | |
#center-ui p { | |
margin: 5px 0; | |
text-shadow: 0 0 5px #33ffff; | |
} | |
#deaths-ui { | |
position: absolute; | |
top: 15px; | |
right: 15px; | |
color: #ff3333; | |
font-family: 'Orbitron', Arial, sans-serif; | |
text-align: right; | |
display: none; | |
background-color: rgba(0, 0, 0, 0.5); | |
padding: 10px 15px; | |
border-radius: 10px; | |
border-right: 3px solid #ff3333; | |
box-shadow: 0 0 10px rgba(255, 51, 51, 0.5); | |
transition: all 0.3s ease; | |
} | |
#deaths-ui p { | |
margin: 5px 0; | |
text-shadow: 0 0 5px #ff3333; | |
} | |
#powerup-indicator { | |
position: absolute; | |
bottom: 15px; | |
left: 15px; | |
color: white; | |
font-family: 'Orbitron', Arial, sans-serif; | |
display: none; | |
background-color: rgba(0, 0, 0, 0.5); | |
padding: 10px 15px; | |
border-radius: 10px; | |
border-left: 3px solid #ffaa00; | |
box-shadow: 0 0 10px rgba(255, 170, 0, 0.5); | |
} | |
#powerup-indicator h3 { | |
margin: 5px; | |
color: #ffaa00; | |
text-shadow: 0 0 5px #ffaa00; | |
font-size: 16px; | |
} | |
#powerup-list { | |
display: flex; | |
gap: 10px; | |
flex-wrap: wrap; | |
margin-top: 5px; | |
} | |
.powerup-item { | |
display: inline-block; | |
width: 30px; | |
height: 30px; | |
border-radius: 50%; | |
background-color: #333; | |
text-align: center; | |
line-height: 30px; | |
font-weight: bold; | |
border: 1px solid #ffaa00; | |
box-shadow: 0 0 5px #ffaa00; | |
transition: all 0.3s ease; | |
} | |
.powerup-item.active { | |
background-color: #ffaa00; | |
color: #000; | |
transform: scale(1.1); | |
box-shadow: 0 0 10px #ffaa00; | |
} | |
#level-notification { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: white; | |
font-family: 'Orbitron', Arial, sans-serif; | |
text-align: center; | |
display: none; | |
background-color: rgba(0, 0, 0, 0.7); | |
padding: 20px; | |
border-radius: 10px; | |
border: 2px solid #33ffff; | |
box-shadow: 0 0 20px rgba(51, 255, 255, 0.5); | |
z-index: 100; | |
animation: fadeInOut 2s forwards; | |
} | |
#level-notification h2 { | |
margin: 0; | |
font-size: 28px; | |
color: #33ffff; | |
text-shadow: 0 0 10px #33ffff; | |
} | |
#level-notification p { | |
margin: 10px 0; | |
} | |
@keyframes fadeInOut { | |
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); } | |
20% { opacity: 1; transform: translate(-50%, -50%) scale(1); } | |
80% { opacity: 1; transform: translate(-50%, -50%) scale(1); } | |
100% { opacity: 0; transform: translate(-50%, -50%) scale(1.2); } | |
} | |
#mobile-controls { | |
position: absolute; | |
bottom: 20px; | |
left: 0; | |
right: 0; | |
display: none; | |
justify-content: space-between; | |
padding: 0 20px; | |
} | |
.mobile-control-group { | |
display: flex; | |
gap: 20px; | |
} | |
.mobile-btn { | |
width: 60px; | |
height: 60px; | |
background-color: rgba(255, 255, 255, 0.2); | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: white; | |
font-size: 24px; | |
cursor: pointer; | |
user-select: none; | |
touch-action: manipulation; | |
border: 2px solid rgba(255, 255, 255, 0.4); | |
} | |
.mobile-btn:active { | |
background-color: rgba(255, 255, 255, 0.4); | |
transform: scale(0.95); | |
} | |
#gameOver { | |
position: absolute; | |
top: 40%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: white; | |
font-family: 'Orbitron', 'Arial', sans-serif; | |
text-align: center; | |
display: none; | |
background-color: rgba(0, 0, 0, 0.6); | |
padding: 30px; | |
border-radius: 15px; | |
box-shadow: 0 0 30px rgba(255, 0, 0, 0.6); | |
width: 80%; | |
max-width: 600px; | |
border: 2px solid #ff3333; | |
backdrop-filter: blur(5px); | |
-webkit-backdrop-filter: blur(5px); | |
z-index: 10; | |
} | |
#gameOver h1 { | |
font-size: 40px; | |
margin-bottom: 20px; | |
text-shadow: 0 0 10px #ff3333, 0 0 20px #ff3333; | |
color: #ffffff; | |
letter-spacing: 2px; | |
} | |
#gameOver p { | |
font-size: 18px; | |
margin: 15px 0; | |
border-top: 1px solid rgba(255, 51, 51, 0.5); | |
border-bottom: 1px solid rgba(255, 51, 51, 0.5); | |
padding: 15px 0; | |
color: #f5f5f5; | |
} | |
#gameOver .stats { | |
display: flex; | |
justify-content: space-around; | |
flex-wrap: wrap; | |
margin: 20px 0; | |
} | |
#gameOver .stat-item { | |
flex: 1; | |
min-width: 200px; | |
padding: 15px; | |
margin: 5px; | |
border-radius: 10px; | |
background-color: rgba(100, 0, 0, 0.5); | |
box-shadow: 0 0 15px rgba(255, 0, 0, 0.3) inset; | |
backdrop-filter: blur(5px); | |
-webkit-backdrop-filter: blur(5px); | |
} | |
#gameOver button { | |
font-size: 24px; | |
padding: 12px 30px; | |
cursor: pointer; | |
border-radius: 30px; | |
margin-top: 20px; | |
transition: all 0.3s ease; | |
font-weight: bold; | |
letter-spacing: 2px; | |
border: none; | |
margin: 10px; | |
font-family: 'Orbitron', sans-serif; | |
} | |
#gameOver button:first-of-type { | |
background-color: #4CAF50; | |
color: white; | |
box-shadow: 0 0 10px rgba(76, 175, 80, 0.8); | |
} | |
#gameOver button:first-of-type:hover { | |
background-color: #3e8e41; | |
transform: scale(1.05); | |
box-shadow: 0 0 15px rgba(76, 175, 80, 1); | |
} | |
#gameOver button:last-of-type { | |
background-color: #ff5722; | |
color: white; | |
box-shadow: 0 0 10px rgba(255, 87, 34, 0.8); | |
} | |
#gameOver button:last-of-type:hover { | |
background-color: #e64a19; | |
transform: scale(1.05); | |
box-shadow: 0 0 15px rgba(255, 87, 34, 1); | |
} | |
#startScreen { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: white; | |
font-family: 'Orbitron', 'Arial', sans-serif; | |
text-align: center; | |
display: block; | |
background-color: rgba(0, 0, 0, 0.8); | |
padding: 30px; | |
border-radius: 15px; | |
box-shadow: 0 0 30px rgba(0, 255, 255, 0.6); | |
width: 80%; | |
max-width: 600px; | |
border: 2px solid #00bfff; | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
} | |
#startScreen h1 { | |
font-size: 48px; | |
margin-bottom: 20px; | |
text-shadow: 0 0 10px #00bfff, 0 0 20px #00bfff; | |
color: #ffffff; | |
letter-spacing: 3px; | |
animation: glow 1.5s infinite alternate; | |
} | |
@keyframes glow { | |
from { | |
text-shadow: 0 0 5px #00bfff, 0 0 10px #00bfff; | |
} | |
to { | |
text-shadow: 0 0 10px #00bfff, 0 0 20px #00bfff, 0 0 30px #00bfff; | |
} | |
} | |
#startScreen .instructions { | |
display: flex; | |
justify-content: space-around; | |
flex-wrap: wrap; | |
margin: 20px 0; | |
border-top: 1px solid rgba(0, 191, 255, 0.5); | |
border-bottom: 1px solid rgba(0, 191, 255, 0.5); | |
padding: 20px 0; | |
} | |
#startScreen .instruction-item { | |
flex: 1; | |
min-width: 200px; | |
padding: 15px; | |
margin: 5px; | |
border-radius: 10px; | |
background-color: rgba(0, 50, 100, 0.5); | |
box-shadow: 0 0 15px rgba(0, 191, 255, 0.3) inset; | |
backdrop-filter: blur(5px); | |
-webkit-backdrop-filter: blur(5px); | |
} | |
#startScreen .key { | |
display: inline-block; | |
background-color: #222; | |
padding: 8px 12px; | |
border-radius: 5px; | |
margin: 5px; | |
font-weight: bold; | |
border: 1px solid #00bfff; | |
box-shadow: 0 0 5px #00bfff; | |
} | |
#startScreen p { | |
font-size: 18px; | |
margin: 5px 0; | |
color: #f5f5f5; | |
} | |
#startScreen button { | |
font-size: 24px; | |
padding: 12px 30px; | |
cursor: pointer; | |
background-color: #00bfff; | |
color: white; | |
border: none; | |
border-radius: 30px; | |
margin-top: 20px; | |
transition: all 0.3s ease; | |
box-shadow: 0 0 10px rgba(0, 191, 255, 0.8); | |
font-weight: bold; | |
letter-spacing: 2px; | |
font-family: 'Orbitron', sans-serif; | |
} | |
#startScreen button:hover { | |
background-color: #0099cc; | |
transform: scale(1.05); | |
box-shadow: 0 0 15px rgba(0, 191, 255, 1); | |
} | |
#pausedScreen { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: white; | |
font-family: 'Orbitron', 'Arial', sans-serif; | |
text-align: center; | |
display: none; | |
background-color: rgba(0, 0, 0, 0.8); | |
padding: 30px; | |
border-radius: 15px; | |
box-shadow: 0 0 30px rgba(255, 165, 0, 0.6); | |
width: 80%; | |
max-width: 600px; | |
border: 2px solid #ffa500; | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
} | |
#pausedScreen h1 { | |
font-size: 48px; | |
margin-bottom: 20px; | |
text-shadow: 0 0 10px #ffa500, 0 0 20px #ffa500; | |
color: #ffffff; | |
letter-spacing: 3px; | |
animation: pauseGlow 2s infinite alternate; | |
} | |
@keyframes pauseGlow { | |
from { | |
text-shadow: 0 0 5px #ffa500, 0 0 10px #ffa500; | |
} | |
to { | |
text-shadow: 0 0 10px #ffa500, 0 0 20px #ffa500, 0 0 30px #ffa500; | |
} | |
} | |
#pausedScreen .instructions { | |
display: flex; | |
justify-content: space-around; | |
flex-wrap: wrap; | |
margin: 20px 0; | |
border-top: 1px solid rgba(255, 165, 0, 0.5); | |
border-bottom: 1px solid rgba(255, 165, 0, 0.5); | |
padding: 20px 0; | |
} | |
#pausedScreen .instruction-item { | |
flex: 1; | |
min-width: 200px; | |
padding: 15px; | |
margin: 5px; | |
border-radius: 10px; | |
background-color: rgba(100, 50, 0, 0.5); | |
box-shadow: 0 0 15px rgba(255, 165, 0, 0.3) inset; | |
backdrop-filter: blur(5px); | |
-webkit-backdrop-filter: blur(5px); | |
} | |
#pausedScreen .key { | |
display: inline-block; | |
background-color: #222; | |
padding: 8px 12px; | |
border-radius: 5px; | |
margin: 5px; | |
font-weight: bold; | |
border: 1px solid #ffa500; | |
box-shadow: 0 0 5px #ffa500; | |
} | |
#pausedScreen p { | |
font-size: 18px; | |
margin: 5px 0; | |
color: #f5f5f5; | |
} | |
#pausedScreen button { | |
font-size: 24px; | |
padding: 12px 30px; | |
cursor: pointer; | |
background-color: #ffa500; | |
color: white; | |
border: none; | |
border-radius: 30px; | |
margin-top: 20px; | |
transition: all 0.3s ease; | |
box-shadow: 0 0 10px rgba(255, 165, 0, 0.8); | |
font-weight: bold; | |
letter-spacing: 2px; | |
font-family: 'Orbitron', sans-serif; | |
} | |
#pausedScreen button:hover { | |
background-color: #ff8c00; | |
transform: scale(1.05); | |
box-shadow: 0 0 15px rgba(255, 165, 0, 1); | |
} | |
@media (max-width: 768px) { | |
#ui, #center-ui, #deaths-ui { | |
font-size: 14px; | |
padding: 8px 12px; | |
} | |
#gameOver, #startScreen, #pausedScreen { | |
width: 90%; | |
padding: 20px; | |
} | |
#gameOver h1, #startScreen h1, #pausedScreen h1 { | |
font-size: 32px; | |
} | |
#mobile-controls { | |
display: flex; | |
} | |
.powerup-item { | |
width: 24px; | |
height: 24px; | |
line-height: 24px; | |
font-size: 12px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div id="ui"> | |
<p>Score: <span id="score">0</span></p> | |
<p>Planet: <span id="level">1</span></p> | |
</div> | |
<div id="center-ui"> | |
<p>Hi-Score: <span id="hiScore">0</span></p> | |
<p>Hi-Planet: <span id="hiPlanet">1</span></p> | |
</div> | |
<div id="deaths-ui"> | |
<p>Mortes: <span id="deaths">0</span></p> | |
<p>Próximo Power-Up: <span id="nextPowerUp">Planet 2</span></p> | |
</div> | |
<div id="powerup-indicator"> | |
<h3>Power-Ups</h3> | |
<div id="powerup-list"></div> | |
</div> | |
<div id="level-notification"> | |
<h2>Planeta Completo!</h2> | |
</div> | |
<!-- Elementos de áudio para efeitos sonoros --> | |
<audio id="enemy-hit-sound" src="https://assets.codepen.io/21542/howler-push.mp3" preload="auto"></audio> | |
<audio id="player-shot-sound" src="https://assets.codepen.io/21542/howler-sfx-levelup.mp3" preload="auto"></audio> | |
<audio id="shield-hit-sound" src="https://assets.codepen.io/21542/howler-sfx-volume1.mp3" preload="auto"></audio> | |
<audio id="game-over-sound" src="https://assets.codepen.io/21542/howler-sfx-death.mp3" preload="auto"></audio> | |
<audio id="background-music" src="https://assets.codepen.io/21542/epic-space-opera.mp3" loop preload="auto"></audio> | |
<audio id="level-complete-sound" src="https://assets.codepen.io/21542/howler-sfx-levelup.mp3" preload="auto"></audio> | |
<div id="mobile-controls"> | |
<div class="mobile-control-group"> | |
<div class="mobile-btn" id="left-btn">◀</div> | |
<div class="mobile-btn" id="right-btn">▶</div> | |
</div> | |
<div class="mobile-control-group"> | |
<div class="mobile-btn" id="fire-btn">🔥</div> | |
<div class="mobile-btn" id="pause-btn">⏸️</div> | |
</div> | |
</div> | |
<div id="gameOver"> | |
<h1>Game Over</h1> | |
<p>Tentar novamente mantem os power-ups e reduz uma barreira!</p> | |
<div class="stats"> | |
<div class="stat-item"> | |
<p>Pontuação: <span id="finalScore">0</span></p> | |
<p>Planeta: <span id="finalPlanet">1</span></p> | |
</div> | |
<div class="stat-item"> | |
<p>Maior Pontuação: <span id="finalHiScore">0</span></p> | |
<p>Planeta Mais Alto: <span id="finalHiPlanet">1</span></p> | |
</div> | |
</div> | |
<button onclick="retry()">Tentar Novamente</button> | |
<button onclick="restartGame()">Reiniciar Jogo</button> | |
</div> | |
<div id="pausedScreen"> | |
<h1>JOGO PAUSADO</h1> | |
<div class="instructions"> | |
<div class="instruction-item"> | |
<h3>Controles</h3> | |
<p><span class="key">P</span> Continuar Jogo</p> | |
<p><span class="key">R</span> Reiniciar Jogo</p> | |
</div> | |
<div class="instruction-item"> | |
<h3>Estatísticas Atuais</h3> | |
<p>Pontuação: <span id="pauseScore">0</span></p> | |
<p>Planeta: <span id="pausePlanet">1</span></p> | |
</div> | |
</div> | |
<p>Tome um fôlego antes de enfrentar os alienígenas novamente!</p> | |
<button onclick="resumeGame()">CONTINUAR JOGO</button> | |
<button onclick="restartGame()">REINICIAR JOGO</button> | |
</div> | |
<div id="startScreen"> | |
<h1>Space Invaders</h1> | |
<div class="instructions"> | |
<div class="instruction-item"> | |
<h3>Movimento</h3> | |
<p><span class="key">←</span> Mover para Esquerda</p> | |
<p><span class="key">→</span> Mover para Direita</p> | |
</div> | |
<div class="instruction-item"> | |
<h3>Ações</h3> | |
<p><span class="key">Espaço</span> Atirar</p> | |
<p><span class="key">P</span> Pausar</p> | |
<p><span class="key">R</span> Reiniciar</p> | |
</div> | |
</div> | |
<p>Defenda seu planeta contra os invasores alienígenas!</p> | |
<p>Sobreviva para desbloquear novos poderes a cada fase.</p> | |
<button onclick="startGame()">INICIAR JOGO</button> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script> | |
<script> | |
// Configuração da cena | |
const scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0x000022); | |
const camera = new THREE.OrthographicCamera(-8, 8, 8, -8, 0.1, 100); | |
camera.position.z = 10; | |
const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
document.body.appendChild(renderer.domElement); | |
// Função para liberar recursos | |
function disposeObject(obj) { | |
scene.remove(obj); | |
if (obj.geometry) obj.geometry.dispose(); | |
if (obj.material) { | |
if (Array.isArray(obj.material)) { | |
obj.material.forEach(mat => mat.dispose()); | |
} else { | |
obj.material.dispose(); | |
} | |
} | |
} | |
// Variáveis do jogo | |
let player, enemies = [], bullets = [], enemyBullets = [], shields = [], explosions = [], stars = []; | |
let score = 0, hiScore = 0, level = 1, hiPlanet = 1, deaths = 0; | |
let unlockedPowerUps = [0]; | |
let isPaused = false, gameOver = false, isGameStarted = false; | |
let enemyDirection = 1, enemySpeed = 0.01; | |
let lastShotTime = 0, tapShotDelay = 250, holdShotDelay = 600; // Ajustado para balancear | |
let isSpacePressed = false; | |
let floatingPoints = []; | |
const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00]; | |
const powerUpDescriptions = [ | |
"Tiro simples", | |
"Tiro duplo", | |
"Tiro triplo", | |
"Bomba simples", | |
"Bomba dupla", | |
"Tiro diagonal", | |
"Tiro diagonal duplo", | |
"Tiro diagonal triplo" | |
]; | |
// Estrelas (fundo) | |
function createStars() { | |
const geometry = new THREE.SphereGeometry(0.03, 8, 8); | |
const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); | |
const instancedMesh = new THREE.InstancedMesh(geometry, material, 100); | |
const matrix = new THREE.Matrix4(); | |
for (let i = 0; i < 100; i++) { | |
matrix.setPosition( | |
Math.random() * 30 - 15, | |
Math.random() * 20 - 10, | |
-5 | |
); | |
const scale = Math.random() + 0.5; | |
matrix.scale(new THREE.Vector3(scale, scale, scale)); | |
instancedMesh.setMatrixAt(i, matrix); | |
instancedMesh.setColorAt(i, new THREE.Color(Math.random(), Math.random(), Math.random()).multiplyScalar(0.5).add(new THREE.Color(0.5, 0.5, 0.5))); | |
} | |
instancedMesh.twinkle = { | |
speed: 0.02, | |
factor: 0.8, | |
phase: Math.random() * Math.PI * 2 | |
}; | |
stars = [instancedMesh]; | |
scene.add(instancedMesh); | |
} | |
// Jogador | |
function createPlayer() { | |
const bodyGeometry = new THREE.BoxGeometry(0.5, 0.2, 0.3); | |
const bodyMaterial = new THREE.MeshBasicMaterial({ color: 0x3399ff }); | |
const body = new THREE.Mesh(bodyGeometry, bodyMaterial); | |
const wingGeometry = new THREE.BoxGeometry(0.8, 0.1, 0.1); | |
const wingMaterial = new THREE.MeshBasicMaterial({ color: 0x66ccff }); | |
const wings = new THREE.Mesh(wingGeometry, wingMaterial); | |
wings.position.y = -0.1; | |
const cockpitGeometry = new THREE.BoxGeometry(0.2, 0.15, 0.2); | |
const cockpitMaterial = new THREE.MeshBasicMaterial({ color: 0x99ddff }); | |
const cockpit = new THREE.Mesh(cockpitGeometry, cockpitMaterial); | |
cockpit.position.y = 0.1; | |
const thrusterGeometry = new THREE.BoxGeometry(0.1, 0.15, 0.1); | |
const thrusterMaterial = new THREE.MeshBasicMaterial({ color: 0xff3300 }); | |
const thrusterLeft = new THREE.Mesh(thrusterGeometry, thrusterMaterial); | |
thrusterLeft.position.set(-0.2, -0.15, 0); | |
const thrusterRight = new THREE.Mesh(thrusterGeometry, thrusterMaterial); | |
thrusterRight.position.set(0.2, -0.15, 0); | |
player = new THREE.Group(); | |
player.add(body, wings, cockpit, thrusterLeft, thrusterRight); | |
player.position.set(0, -6, 0); | |
player.thrusterLeft = thrusterLeft; | |
player.thrusterRight = thrusterRight; | |
player.animationTime = 0; | |
scene.add(player); | |
} | |
function animatePlayer() { | |
if (!player) return; | |
player.animationTime += 0.1; | |
const scale = 0.8 + Math.sin(player.animationTime * 2) * 0.2; | |
player.thrusterLeft.scale.y = scale; | |
player.thrusterRight.scale.y = scale; | |
if (keys['ArrowLeft'] && player.position.x > -7.5) { | |
player.rotation.z = THREE.MathUtils.lerp(player.rotation.z, 0.2, 0.1); | |
} else if (keys['ArrowRight'] && player.position.x < 7.5) { | |
player.rotation.z = THREE.MathUtils.lerp(player.rotation.z, -0.2, 0.1); | |
} else { | |
player.rotation.z = THREE.MathUtils.lerp(player.rotation.z, 0, 0.1); | |
} | |
} | |
// Inimigos | |
function createEnemies() { | |
enemies = []; | |
const baseColor = colors[(level - 1) % colors.length]; | |
for (let row = 0; row < 3; row++) { | |
for (let col = 0; col < 9; col++) { | |
const x = -6 + col * 1.5; | |
const y = 7 - row * 1.5; | |
const enemy = createEnemyByType(row, x, y, baseColor); | |
enemies.push(enemy); | |
scene.add(enemy); | |
} | |
} | |
} | |
function createEnemyByType(type, x, y, baseColor) { | |
let enemy; | |
if (type === 0) { | |
const group = new THREE.Group(); | |
const bodyGeometry = new THREE.BoxGeometry(0.4, 0.2, 0.2); | |
const bodyMaterial = new THREE.MeshBasicMaterial({ color: baseColor }); | |
const body = new THREE.Mesh(bodyGeometry, bodyMaterial); | |
const wingGeometry = new THREE.BoxGeometry(0.6, 0.1, 0.1); | |
const wingMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color(baseColor).multiplyScalar(0.8) }); | |
const wings = new THREE.Mesh(wingGeometry, wingMaterial); | |
wings.position.y = -0.05; | |
const antennaGeometry = new THREE.BoxGeometry(0.05, 0.15, 0.05); | |
const antennaMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color(baseColor).multiplyScalar(1.2) }); | |
const antenna = new THREE.Mesh(antennaGeometry, antennaMaterial); | |
antenna.position.y = 0.15; | |
group.add(body, wings, antenna); | |
enemy = group; | |
enemy.scoreValue = 30; | |
} else if (type === 1) { | |
const group = new THREE.Group(); | |
const bodyGeometry = new THREE.BoxGeometry(0.3, 0.3, 0.2); | |
const bodyMaterial = new THREE.MeshBasicMaterial({ color: baseColor }); | |
const body = new THREE.Mesh(bodyGeometry, bodyMaterial); | |
const cockpitGeometry = new THREE.BoxGeometry(0.2, 0.1, 0.15); | |
const cockpitMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color(baseColor).multiplyScalar(1.2) }); | |
const cockpit = new THREE.Mesh(cockpitGeometry, cockpitMaterial); | |
cockpit.position.y = 0.1; | |
group.add(body, cockpit); | |
enemy = group; | |
enemy.scoreValue = 20; | |
} else { | |
const group = new THREE.Group(); | |
const bodyGeometry = new THREE.BoxGeometry(0.3, 0.3, 0.2); | |
const bodyMaterial = new THREE.MeshBasicMaterial({ color: baseColor }); | |
const body = new THREE.Mesh(bodyGeometry, bodyMaterial); | |
group.add(body); | |
enemy = group; | |
enemy.scoreValue = 10; | |
} | |
enemy.position.set(x, y, 0); | |
enemy.type = type; | |
enemy.animationTime = Math.random() * Math.PI * 2; | |
return enemy; | |
} | |
function animateEnemies() { | |
enemies.forEach(enemy => { | |
enemy.animationTime += 0.05; | |
if (enemy.type === 0) { | |
enemy.rotation.z = Math.sin(enemy.animationTime * 0.5) * 0.2; | |
} else if (enemy.type === 1) { | |
enemy.position.y += Math.sin(enemy.animationTime) * 0.01; | |
} else { | |
const scale = 1 + Math.sin(enemy.animationTime) * 0.1; | |
enemy.scale.set(scale, scale, scale); | |
} | |
}); | |
} | |
// Proteções | |
function createShields() { | |
shields = []; | |
let positions; | |
if (deaths === 0) { | |
positions = [-6, -2, 2, 6]; | |
} else if (deaths === 1) { | |
positions = [-4, 0, 4]; | |
} else if (deaths === 2) { | |
positions = [-2, 2]; | |
} else if (deaths === 3) { | |
positions = [0]; | |
} else { | |
positions = []; | |
} | |
positions.forEach(x => { | |
const shield = createShieldGroup(x, -4); | |
shields.push(shield); | |
scene.add(shield); | |
}); | |
} | |
function createShieldGroup(x, y) { | |
const group = new THREE.Group(); | |
group.position.set(x, y, 0); | |
group.health = 30; | |
const baseGeometry = new THREE.BoxGeometry(1.4, 0.3, 0.3); | |
const baseMaterial = new THREE.MeshBasicMaterial({ color: 0x007766, transparent: true, opacity: 0.7 }); | |
const base = new THREE.Mesh(baseGeometry, baseMaterial); | |
base.position.y = -0.25; | |
const leftGeometry = new THREE.BoxGeometry(0.3, 0.8, 0.3); | |
const leftMaterial = new THREE.MeshBasicMaterial({ color: 0x006655, transparent: true, opacity: 0.7 }); | |
const left = new THREE.Mesh(leftGeometry, leftMaterial); | |
left.position.set(-0.55, 0.1, 0); | |
const rightGeometry = new THREE.BoxGeometry(0.3, 0.8, 0.3); | |
const rightMaterial = new THREE.MeshBasicMaterial({ color: 0x006655, transparent: true, opacity: 0.7 }); | |
const right = new THREE.Mesh(rightGeometry, rightMaterial); | |
right.position.set(0.55, 0.1, 0); | |
const topGeometry = new THREE.BoxGeometry(0.8, 0.3, 0.3); | |
const topMaterial = new THREE.MeshBasicMaterial({ color: 0x007766, transparent: true, opacity: 0.7 }); | |
const top = new THREE.Mesh(topGeometry, topMaterial); | |
top.position.set(0, 0.45, 0); | |
const glowGeometry = new THREE.BoxGeometry(1.5, 0.9, 0.1); | |
const glowMaterial = new THREE.MeshBasicMaterial({ color: 0x00aa99, transparent: true, opacity: 0.2 }); | |
const glow = new THREE.Mesh(glowGeometry, glowMaterial); | |
glow.position.set(0, 0.05, -0.2); | |
group.add(base, left, right, top, glow); | |
group.components = { base, left, right, top, glow }; | |
return group; | |
} | |
function updateShieldVisual(shield) { | |
const healthPercent = shield.health / 30; | |
if (shield.health <= 22) { | |
const color = new THREE.Color(0xaa7700); | |
shield.components.base.material.color.set(color); | |
shield.components.left.material.color.set(color.clone().multiplyScalar(0.9)); | |
shield.components.right.material.color.set(color.clone().multiplyScalar(0.9)); | |
shield.components.top.material.color.set(color); | |
shield.components.glow.material.color.set(color); | |
shield.components.base.scale.set(0.9, 0.9, 0.9); | |
shield.components.left.scale.set(0.9, 0.85, 0.9); | |
shield.components.right.scale.set(0.9, 0.85, 0.9); | |
shield.components.top.scale.set(0.8, 0.8, 0.8); | |
} | |
if (shield.health <= 14) { | |
const color = new THREE.Color(0xaa5500); | |
shield.components.base.material.color.set(color); | |
shield.components.left.material.color.set(color.clone().multiplyScalar(0.9)); | |
shield.components.right.material.color.set(color.clone().multiplyScalar(0.9)); | |
shield.components.top.material.color.set(color); | |
shield.components.glow.material.color.set(color); | |
shield.components.base.scale.set(0.8, 0.7, 0.8); | |
shield.components.left.scale.set(0.8, 0.7, 0.8); | |
shield.components.right.scale.set(0.8, 0.7, 0.8); | |
shield.components.top.scale.set(0.7, 0.6, 0.7); | |
if (shield.health === 14 && shield.components.top.visible) { | |
shield.components.top.visible = Math.random() > 0.5; | |
} | |
} | |
if (shield.health <= 7) { | |
const color = new THREE.Color(0xaa3300); | |
shield.components.base.material.color.set(color); | |
shield.components.left.material.color.set(color.clone().multiplyScalar(0.9)); | |
shield.components.right.material.color.set(color.clone().multiplyScalar(0.9)); | |
shield.components.top.material.color.set(color); | |
shield.components.glow.material.color.set(color); | |
shield.components.base.scale.set(0.5, 0.5, 0.5); | |
shield.components.left.scale.set(0.5, 0.4, 0.5); | |
shield.components.right.scale.set(0.5, 0.4, 0.5); | |
shield.components.top.scale.set(0.4, 0.3, 0.4); | |
shield.components.glow.material.opacity = 0.15; | |
if (shield.health === 7) { | |
if (Math.random() > 0.3) shield.components.left.visible = false; | |
if (Math.random() > 0.3) shield.components.right.visible = false; | |
shield.components.top.visible = false; | |
} | |
} | |
if (shield.health < 15) { | |
shield.position.x += (Math.random() - 0.5) * 0.05 * (1 - healthPercent); | |
shield.position.y += (Math.random() - 0.5) * 0.05 * (1 - healthPercent); | |
setTimeout(() => { | |
if (shield.position) { | |
shield.position.x = Math.round(shield.position.x * 20) / 20; | |
shield.position.y = Math.round(shield.position.y * 20) / 20; | |
} | |
}, 16); | |
} | |
} | |
// Tiros | |
function shoot(isTap = false) { | |
const now = Date.now(); | |
const delay = isTap ? tapShotDelay : holdShotDelay; | |
if (now - lastShotTime < delay || bullets.length > 50) return; | |
lastShotTime = now; | |
// Reproduzir o som de tiro do jogador | |
// document.getElementById('player-shot-sound').currentTime = 0; | |
// document.getElementById('player-shot-sound').play(); | |
// Iterar sobre todos os power-ups desbloqueados | |
unlockedPowerUps.forEach(powerUp => { | |
// Garantir que o power-up esteja dentro do intervalo válido | |
if (powerUp >= powerUpDescriptions.length) return; | |
if (powerUp >= 5) { | |
const offsets = powerUp === 5 ? [0] : powerUp === 6 ? [-0.3, 0.3] : [-0.5, 0, 0.5]; | |
offsets.forEach(offset => { | |
const bulletLeft = createBullet(offset); | |
bulletLeft.velocity = new THREE.Vector3(-0.075, 0.1, 0); | |
bulletLeft.isDiagonal = true; | |
bullets.push(bulletLeft); | |
scene.add(bulletLeft); | |
const bulletRight = createBullet(offset); | |
bulletRight.velocity = new THREE.Vector3(0.075, 0.1, 0); | |
bulletRight.isDiagonal = true; | |
bullets.push(bulletRight); | |
scene.add(bulletRight); | |
}); | |
} else if (powerUp >= 3) { | |
const offsets = powerUp === 3 ? [0] : [-0.3, 0.3]; | |
offsets.forEach(offset => { | |
const geometry = new THREE.SphereGeometry(0.3, 8, 8); | |
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); | |
const bomb = new THREE.Mesh(geometry, material); | |
bomb.position.set(player.position.x + offset, player.position.y, 0); | |
bomb.velocity = new THREE.Vector3(0, 0.1, 0); | |
bomb.isBomb = true; | |
bomb.updateTrail = function() {}; // Função vazia para bombas | |
bullets.push(bomb); | |
scene.add(bomb); | |
}); | |
} else { | |
const offsets = powerUp === 0 ? [0] : powerUp === 1 ? [-0.3, 0.3] : [-0.5, 0, 0.5]; | |
offsets.forEach(offset => { | |
const bullet = createBullet(offset); | |
bullet.velocity = new THREE.Vector3(0, 0.1, 0); | |
bullet.isNormal = true; | |
bullets.push(bullet); | |
scene.add(bullet); | |
}); | |
} | |
}); | |
function createBullet(offset) { | |
const geometry = new THREE.BoxGeometry(0.15, 0.4, 0.15); | |
const material = new THREE.MeshBasicMaterial({ color: 0x33ff33, emissive: 0x33ff33, transparent: true, opacity: 0.9 }); | |
const bullet = new THREE.Mesh(geometry, material); | |
bullet.position.set(player.position.x + offset, player.position.y, 0); | |
const glowGeometry = new THREE.BoxGeometry(0.25, 0.5, 0.1); | |
const glowMaterial = new THREE.MeshBasicMaterial({ color: 0x66ff66, transparent: true, opacity: 0.5 }); | |
const glow = new THREE.Mesh(glowGeometry, glowMaterial); | |
bullet.add(glow); | |
bullet.updateTrail = function() { | |
if (Math.random() > 0.8) { | |
const trailGeometry = new THREE.SphereGeometry(0.05, 6, 6); | |
const trailMaterial = new THREE.MeshBasicMaterial({ color: 0x66ff66, transparent: true, opacity: 0.8 }); | |
const trail = new THREE.Mesh(trailGeometry, trailMaterial); | |
trail.position.set( | |
this.position.x + (Math.random() - 0.5) * 0.05, | |
this.position.y - 0.2, | |
this.position.z | |
); | |
trail.life = 0; | |
trail.maxLife = 0.5; | |
trail.scale.set(1, 1, 1); | |
scene.add(trail); | |
explosions.push(trail); | |
} | |
}; | |
return bullet; | |
} | |
} | |
function enemyShoot(enemy) { | |
const geometry = new THREE.BoxGeometry(0.15, 0.5, 0.15); | |
const material = new THREE.MeshBasicMaterial({ color: 0xff00ff, transparent: true, opacity: 0.9 }); | |
const bullet = new THREE.Mesh(geometry, material); | |
bullet.position.set(enemy.position.x, enemy.position.y - 0.3, 0); | |
bullet.velocity = new THREE.Vector3(0, -0.05, 0); | |
const glowGeometry = new THREE.BoxGeometry(0.3, 0.7, 0.1); | |
const glowMaterial = new THREE.MeshBasicMaterial({ color: 0xff55ff, transparent: true, opacity: 0.5 }); | |
const glow = new THREE.Mesh(glowGeometry, glowMaterial); | |
bullet.add(glow); | |
bullet.animationTime = 0; | |
bullet.updateAnimation = function() { | |
this.animationTime += 0.15; | |
const pulseFactor = 0.8 + Math.sin(this.animationTime) * 0.2; | |
glow.scale.set(pulseFactor, pulseFactor, 1); | |
if (Math.random() > 0.8) this.createTrail(); | |
}; | |
bullet.createTrail = function() { | |
const trailGeometry = new THREE.SphereGeometry(0.05, 6, 6); | |
const trailMaterial = new THREE.MeshBasicMaterial({ color: 0xff77ff, transparent: true, opacity: 0.7 }); | |
const trail = new THREE.Mesh(trailGeometry, trailMaterial); | |
trail.position.set( | |
this.position.x + (Math.random() - 0.5) * 0.05, | |
this.position.y + 0.2 + (Math.random() - 0.5) * 0.05, | |
this.position.z | |
); | |
trail.life = 0; | |
trail.maxLife = 0.4; | |
scene.add(trail); | |
explosions.push(trail); | |
}; | |
enemyBullets.push(bullet); | |
scene.add(bullet); | |
} | |
// Explosões | |
function createExplosion(x, y) { | |
const particles = []; | |
const particleCount = 8; | |
const colors = [0xffa500, 0xff4500, 0xff0000, 0xffff00]; | |
for (let i = 0; i < particleCount; i++) { | |
const size = Math.random() * 0.2 + 0.1; | |
const geometry = new THREE.SphereGeometry(size, 8, 8); | |
const material = new THREE.MeshBasicMaterial({ color: colors[Math.floor(Math.random() * colors.length)], transparent: true, opacity: 1 }); | |
const particle = new THREE.Mesh(geometry, material); | |
particle.position.set(x + (Math.random() - 0.5) * 0.3, y + (Math.random() - 0.5) * 0.3, 0); | |
const speed = Math.random() * 0.03 + 0.01; | |
const angle = Math.random() * Math.PI * 2; | |
particle.velocity = new THREE.Vector3(Math.cos(angle) * speed, Math.sin(angle) * speed, 0); | |
particle.life = 0; | |
particle.maxLife = Math.random() * 0.5 + 0.5; | |
explosions.push(particle); | |
scene.add(particle); | |
} | |
} | |
// UI | |
function updateUI() { | |
document.getElementById('score').textContent = score; | |
document.getElementById('hiScore').textContent = hiScore; | |
document.getElementById('level').textContent = level; | |
document.getElementById('hiPlanet').textContent = hiPlanet; | |
document.getElementById('deaths').textContent = deaths; | |
const nextPowerUpPhase = unlockedPowerUps.length + 1; | |
document.getElementById('nextPowerUp').textContent = nextPowerUpPhase <= 8 ? `Planet ${nextPowerUpPhase}` : 'Todos Desbloqueados'; | |
updatePowerUpIndicator(); | |
} | |
function updatePowerUpIndicator() { | |
const powerupList = document.getElementById('powerup-list'); | |
powerupList.innerHTML = ''; | |
for (let i = 0; i < 8; i++) { | |
const powerupItem = document.createElement('div'); | |
powerupItem.className = 'powerup-item'; | |
powerupItem.textContent = i + 1; | |
powerupItem.title = powerUpDescriptions[i] || 'Desconhecido'; | |
if (unlockedPowerUps.includes(i)) { | |
powerupItem.classList.add('active'); | |
} | |
powerupList.appendChild(powerupItem); | |
} | |
document.getElementById('powerup-indicator').style.display = isGameStarted && !gameOver ? 'block' : 'none'; | |
} | |
function showLevelNotification() { | |
const levelNotification = document.getElementById('level-notification'); | |
if (level <= powerUpDescriptions.length) { | |
levelNotification.style.display = 'block'; | |
setTimeout(() => { | |
levelNotification.style.display = 'none'; | |
}, 2000); | |
} | |
} | |
function updateGameTheme() { | |
const baseColor = colors[(level - 1) % colors.length]; | |
const baseColorHex = '#' + baseColor.toString(16).padStart(6, '0'); | |
const darkColor = new THREE.Color(baseColor).multiplyScalar(0.5).getHex(); | |
const darkColorHex = '#' + darkColor.toString(16).padStart(6, '0'); | |
const brightColor = new THREE.Color(baseColor).multiplyScalar(1.5).getHex(); | |
const brightColorHex = '#' + brightColor.toString(16).padStart(6, '0'); | |
const bgColor = new THREE.Color(baseColor).multiplyScalar(0.1); | |
scene.background = bgColor; | |
document.documentElement.style.setProperty('--theme-color', baseColorHex); | |
document.documentElement.style.setProperty('--theme-color-dark', darkColorHex); | |
document.documentElement.style.setProperty('--theme-color-bright', brightColorHex); | |
document.body.classList.add('planet-transition'); | |
setTimeout(() => { | |
document.body.classList.remove('planet-transition'); | |
}, 1000); | |
} | |
// Jogo | |
function startGame() { | |
isGameStarted = true; | |
document.getElementById('startScreen').style.display = 'none'; | |
document.getElementById('ui').style.display = 'block'; | |
document.getElementById('center-ui').style.display = 'block'; | |
document.getElementById('deaths-ui').style.display = 'block'; | |
document.getElementById('powerup-indicator').style.display = 'block'; | |
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { | |
document.getElementById('mobile-controls').style.display = 'flex'; | |
setupMobileControls(); | |
} | |
createPlayer(); | |
createEnemies(); | |
createShields(); | |
createStars(); | |
updateUI(); | |
} | |
function playLevelCompleteSound() { | |
const levelCompleteSound = document.getElementById('level-complete-sound'); | |
if (levelCompleteSound) { | |
levelCompleteSound.currentTime = 0; | |
levelCompleteSound.play().catch(e => console.log("Erro ao reproduzir som de nível completo:", e)); | |
} | |
} | |
// Adicionar chamada ao som no final do nível | |
function nextLevel() { | |
showLevelNotification(); | |
playLevelCompleteSound(); // Reproduzir som de nível completo | |
level++; | |
hiPlanet = Math.max(hiPlanet, level); | |
// Adicionar novo power-up, se válido | |
const nextPowerUpIndex = level - 1; | |
if (nextPowerUpIndex < powerUpDescriptions.length && !unlockedPowerUps.includes(nextPowerUpIndex)) { | |
unlockedPowerUps.push(nextPowerUpIndex); | |
} | |
enemySpeed = Math.min(enemySpeed + 0.005, 0.05); | |
enemyBullets.forEach(bullet => disposeObject(bullet)); | |
enemyBullets = []; | |
createEnemies(); | |
updateGameTheme(); | |
updateUI(); | |
} | |
// Certifique-se de que o som de game over seja carregado corretamente | |
const gameOverSound = document.getElementById('game-over-sound'); | |
gameOverSound.volume = 0.5; // Ajustar o volume para garantir que seja audível | |
function playGameOverSound() { | |
if (gameOverSound) { | |
gameOverSound.currentTime = 0; // Reinicia o som | |
gameOverSound.play().catch(e => console.error("Erro ao reproduzir o som de game over:", e)); | |
} | |
} | |
// Substituir a chamada existente no método endGame | |
function endGame() { | |
playGameOverSound(); // Reproduzir o som de Game Over | |
deaths++; | |
gameOver = true; | |
if (player) { | |
createExplosion(player.position.x, player.position.y); | |
disposeObject(player); | |
player = null; | |
} | |
updateUI(); | |
document.getElementById('finalScore').textContent = score; | |
document.getElementById('finalPlanet').textContent = level; | |
document.getElementById('finalHiScore').textContent = hiScore; | |
document.getElementById('finalHiPlanet').textContent = hiPlanet; | |
document.getElementById('gameOver').style.display = 'block'; | |
} | |
window.restartGame = function() { | |
score = 0; | |
level = 1; | |
deaths = 0; | |
unlockedPowerUps = [0]; | |
enemySpeed = 0.01; | |
gameOver = false; | |
isPaused = false; | |
isGameStarted = true; | |
lastShotTime = 0; | |
isSpacePressed = false; | |
// Limpar todos os objetos | |
bullets.forEach(bullet => disposeObject(bullet)); | |
enemyBullets.forEach(bullet => disposeObject(bullet)); | |
enemies.forEach(enemy => disposeObject(enemy)); | |
shields.forEach(shield => disposeObject(shield)); | |
explosions.forEach(explosion => disposeObject(explosion)); | |
stars.forEach(star => disposeObject(star)); | |
floatingPoints.forEach(point => disposeObject(point)); | |
if (player) disposeObject(player); | |
// Limpar arrays | |
bullets = []; | |
enemyBullets = []; | |
enemies = []; | |
shields = []; | |
explosions = []; | |
stars = []; | |
floatingPoints = []; | |
player = null; | |
// Limpar cena | |
while (scene.children.length > 0) { | |
scene.remove(scene.children[0]); | |
} | |
// Esconder interfaces | |
document.getElementById('gameOver').style.display = 'none'; | |
document.getElementById('pausedScreen').style.display = 'none'; | |
document.getElementById('startScreen').style.display = 'none'; | |
// Exibir interface do jogo | |
document.getElementById('ui').style.display = 'block'; | |
document.getElementById('center-ui').style.display = 'block'; | |
document.getElementById('deaths-ui').style.display = 'block'; | |
document.getElementById('powerup-indicator').style.display = 'block'; | |
// Recriar elementos | |
scene.background = new THREE.Color(0x000022); | |
createPlayer(); | |
createEnemies(); | |
createShields(); | |
createStars(); | |
updateUI(); | |
}; | |
window.retry = function() { | |
score = 0; | |
level = 1; | |
enemySpeed = 0.01; | |
gameOver = false; | |
isPaused = false; | |
isGameStarted = true; | |
lastShotTime = 0; | |
isSpacePressed = false; | |
// Limpar todos os objetos | |
bullets.forEach(bullet => disposeObject(bullet)); | |
enemyBullets.forEach(bullet => disposeObject(bullet)); | |
enemies.forEach(enemy => disposeObject(enemy)); | |
shields.forEach(shield => disposeObject(shield)); | |
explosions.forEach(explosion => disposeObject(explosion)); | |
stars.forEach(star => disposeObject(star)); | |
floatingPoints.forEach(point => disposeObject(point)); | |
if (player) disposeObject(player); | |
// Limpar arrays | |
bullets = []; | |
enemyBullets = []; | |
enemies = []; | |
shields = []; | |
explosions = []; | |
stars = []; | |
floatingPoints = []; | |
player = null; | |
// Limpar cena | |
while (scene.children.length > 0) { | |
scene.remove(scene.children[0]); | |
} | |
// Esconder interfaces | |
document.getElementById('gameOver').style.display = 'none'; | |
document.getElementById('pausedScreen').style.display = 'none'; | |
document.getElementById('startScreen').style.display = 'none'; | |
// Exibir interface do jogo | |
document.getElementById('ui').style.display = 'block'; | |
document.getElementById('center-ui').style.display = 'block'; | |
document.getElementById('deaths-ui').style.display = 'block'; | |
document.getElementById('powerup-indicator').style.display = 'block'; | |
// Recriar elementos | |
scene.background = new THREE.Color(0x000022); | |
createPlayer(); | |
createEnemies(); | |
createShields(); | |
createStars(); | |
updateUI(); | |
}; | |
window.resumeGame = function() { | |
if (isPaused) { | |
isPaused = false; | |
document.getElementById('pausedScreen').style.display = 'none'; | |
startBackgroundMusic(); // Reinicia a música ao continuar o jogo | |
} | |
}; | |
// Controles | |
const keys = {}; | |
document.addEventListener('keydown', e => { | |
if (e.code === 'KeyR') { | |
if (isGameStarted && !gameOver) { | |
restartGame(); | |
} | |
return; | |
} | |
if (!isGameStarted || gameOver) return; | |
keys[e.code] = true; | |
if (e.code === 'Space' && !isSpacePressed) { | |
isSpacePressed = true; | |
shoot(true); | |
} else if (e.code === 'KeyP') { | |
isPaused = !isPaused; | |
document.getElementById('pausedScreen').style.display = isPaused ? 'block' : 'none'; | |
} | |
}); | |
document.addEventListener('keyup', e => { | |
keys[e.code] = false; | |
if (e.code === 'Space') { | |
isSpacePressed = false; | |
} | |
}); | |
function setupMobileControls() { | |
const leftBtn = document.getElementById('left-btn'); | |
let leftInterval; | |
leftBtn.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
keys['ArrowLeft'] = true; | |
leftInterval = setInterval(() => { | |
if (player && player.position.x > -7.5) { | |
player.position.x -= 0.1; | |
} | |
}, 16); | |
}); | |
leftBtn.addEventListener('touchend', () => { | |
keys['ArrowLeft'] = false; | |
clearInterval(leftInterval); | |
}); | |
const rightBtn = document.getElementById('right-btn'); | |
let rightInterval; | |
rightBtn.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
keys['ArrowRight'] = true; | |
rightInterval = setInterval(() => { | |
if (player && player.position.x < 7.5) { | |
player.position.x += 0.1; | |
} | |
}, 16); | |
}); | |
rightBtn.addEventListener('touchend', () => { | |
keys['ArrowRight'] = false; | |
clearInterval(rightInterval); | |
}); | |
const fireBtn = document.getElementById('fire-btn'); | |
let fireInterval; | |
fireBtn.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
isSpacePressed = true; | |
shoot(true); | |
fireInterval = setInterval(() => { | |
shoot(false); | |
}, holdShotDelay); | |
}); | |
fireBtn.addEventListener('touchend', () => { | |
isSpacePressed = false; | |
clearInterval(fireInterval); | |
}); | |
const pauseBtn = document.getElementById('pause-btn'); | |
pauseBtn.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
isPaused = !isPaused; | |
document.getElementById('pausedScreen').style.display = isPaused ? 'block' : 'none'; | |
}); | |
} | |
// Animação | |
function animate() { | |
if (!isGameStarted) { | |
renderer.render(scene, camera); | |
requestAnimationFrame(animate); | |
return; | |
} | |
if (gameOver) { | |
document.getElementById('gameOver').style.display = 'block'; | |
renderer.render(scene, camera); | |
requestAnimationFrame(animate); | |
return; | |
} | |
if (isPaused) { | |
document.getElementById('pauseScore').textContent = score; | |
document.getElementById('pausePlanet').textContent = level; | |
document.getElementById('pausedScreen').style.display = 'block'; | |
renderer.render(scene, camera); | |
requestAnimationFrame(animate); | |
return; | |
} | |
document.getElementById('pausedScreen').style.display = 'none'; | |
// Animar estrelas | |
stars.forEach(star => { | |
if (star.isInstancedMesh) { | |
star.twinkle.phase += star.twinkle.speed; | |
for (let i = 0; i < star.count; i++) { | |
const scale = star.twinkle.factor + Math.sin(star.twinkle.phase + i) * (1 - star.twinkle.factor); | |
const matrix = new THREE.Matrix4(); | |
star.getMatrixAt(i, matrix); | |
matrix.scale(new THREE.Vector3(scale, scale, scale)); | |
star.setMatrixAt(i, matrix); | |
} | |
star.instanceMatrix.needsUpdate = true; | |
} | |
}); | |
animatePlayer(); | |
animateEnemies(); | |
if (player && keys['ArrowLeft'] && player.position.x > -7.5) { | |
player.position.x -= 0.05; | |
} | |
if (player && keys['ArrowRight'] && player.position.x < 7.5) { | |
player.position.x += 0.05; | |
} | |
if (keys['Space'] && isSpacePressed) { | |
shoot(false); | |
} | |
let moveDown = false; | |
enemies.forEach(enemy => { | |
enemy.position.x += enemySpeed * enemyDirection; | |
if (enemy.position.x > 7.5 || enemy.position.x < -7.5) moveDown = true; | |
if (enemy.position.y < -5.5) endGame(); | |
if (Math.random() < 0.005) { | |
enemyShoot(enemy); | |
} | |
}); | |
if (moveDown) { | |
enemyDirection *= -1; | |
enemies.forEach(enemy => enemy.position.y -= 0.5); | |
} | |
bullets.forEach((bullet, bIndex) => { | |
if (!bullet.position || !bullet.velocity) return; | |
bullet.position.add(bullet.velocity); | |
bullet.updateTrail(); | |
if (bullet.position.y > 8 || bullet.position.x < -8 || bullet.position.x > 8) { | |
disposeObject(bullet); | |
bullets.splice(bIndex, 1); | |
} | |
}); | |
enemyBullets.forEach((bullet, bIndex) => { | |
if (!bullet.position || !bullet.velocity) return; | |
bullet.position.add(bullet.velocity); | |
if (bullet.updateAnimation) { | |
bullet.updateAnimation(); | |
} | |
if (bullet.position.y < -8) { | |
disposeObject(bullet); | |
enemyBullets.splice(bIndex, 1); | |
} | |
}); | |
bullets.forEach((bullet, bIndex) => { | |
enemies.forEach((enemy, eIndex) => { | |
if (bullet.position && enemy.position && bullet.position.distanceTo(enemy.position) < 0.3) { | |
// Reproduzir o som quando um inimigo é atingido | |
document.getElementById('enemy-hit-sound').currentTime = 0; | |
document.getElementById('enemy-hit-sound').play(); | |
createExplosion(enemy.position.x, enemy.position.y); | |
disposeObject(enemy); | |
enemies.splice(eIndex, 1); | |
if (!bullet.isBomb) { | |
disposeObject(bullet); | |
bullets.splice(bIndex, 1); | |
} | |
const points = enemy.scoreValue || 10; | |
score += points; | |
createFloatingPoints(enemy.position.x, enemy.position.y, points); | |
hiScore = Math.max(hiScore, score); | |
updateUI(); | |
} | |
}); | |
}); | |
enemyBullets.forEach((bullet, bIndex) => { | |
shields.forEach((shield, sIndex) => { | |
if (bullet.position && shield.position && bullet.position.distanceTo(shield.position) < 0.5) { | |
disposeObject(bullet); // Remove o tiro inimigo | |
enemyBullets.splice(bIndex, 1); // Remove da lista | |
return; | |
} | |
}); | |
if (player && bullet.position && bullet.position.distanceTo(player.position) < 0.3) { | |
disposeObject(bullet); | |
enemyBullets.splice(bIndex, 1); | |
for (let i = 0; i < 20; i++) { | |
createExplosion(player.position.x, player.position.y); | |
} | |
endGame(); | |
} | |
}); | |
explosions.forEach((explosion, index) => { | |
if (!explosion.position) return; | |
explosion.position.add(explosion.velocity || new THREE.Vector3(0, 0, 0)); | |
explosion.life += 0.05; | |
if (explosion.material.opacity) { | |
explosion.material.opacity = Math.max(0, 1 - explosion.life / explosion.maxLife); | |
} | |
if (explosion.life > explosion.maxLife) { | |
disposeObject(explosion); | |
explosions.splice(index, 1); | |
} | |
}); | |
floatingPoints.forEach((point, index) => { | |
if (!point.position) return; | |
point.position.y += 0.03; | |
point.life += 0.05; | |
if (point.life > 1) { | |
disposeObject(point); | |
floatingPoints.splice(index, 1); | |
} | |
}); | |
if (enemies.length === 0) nextLevel(); | |
renderer.render(scene, camera); | |
requestAnimationFrame(animate); | |
} | |
// Redimensionamento | |
window.addEventListener('resize', () => { | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
const aspectRatio = window.innerWidth / window.innerHeight; | |
const scale = 8; | |
if (aspectRatio >= 1) { | |
camera.left = -scale * aspectRatio; | |
camera.right = scale * aspectRatio; | |
camera.top = scale; | |
camera.bottom = -scale; | |
} else { | |
camera.left = -scale; | |
camera.right = scale; | |
camera.top = scale / aspectRatio; | |
camera.bottom = -scale / aspectRatio; | |
} | |
camera.updateProjectionMatrix(); | |
}); | |
// Pontuação flutuante | |
function createFloatingPoints(x, y, points) { | |
const geometry = new THREE.BufferGeometry(); | |
const vertices = new Float32Array([0, 0, 0]); | |
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); | |
const material = new THREE.PointsMaterial({ | |
color: 0xffff00, | |
size: 0.5, | |
map: createTextTexture(`+${points}`), | |
transparent: true, | |
alphaTest: 0.5 | |
}); | |
const pointsObj = new THREE.Points(geometry, material); | |
pointsObj.position.set(x, y, 0); | |
pointsObj.life = 0; | |
floatingPoints.push(pointsObj); | |
scene.add(pointsObj); | |
} | |
function createTextTexture(text) { | |
const canvas = document.createElement('canvas'); | |
const size = 128; | |
canvas.width = size; | |
canvas.height = size; | |
const context = canvas.getContext('2d'); | |
context.fillStyle = 'transparent'; | |
context.fillRect(0, 0, size, size); | |
context.font = 'Bold 64px Orbitron, Arial'; | |
context.textAlign = 'center'; | |
context.textBaseline = 'middle'; | |
context.fillStyle = '#000000'; | |
context.fillText(text, size/2 + 2, size/2 + 2); | |
context.fillStyle = '#ffff00'; | |
context.fillText(text, size/2, size/2); | |
const texture = new THREE.CanvasTexture(canvas); | |
texture.needsUpdate = true; | |
return texture; | |
} | |
// Música de fundo | |
let bgMusic = document.getElementById('background-music'); | |
bgMusic.volume = 0.3; // Volume mais baixo para não sobrepor efeitos sonoros | |
function startBackgroundMusic() { | |
bgMusic.currentTime = 0; | |
bgMusic.play().catch(e => console.log("Erro ao iniciar música:", e)); | |
} | |
function stopBackgroundMusic() { | |
bgMusic.pause(); | |
} | |
window.startGame = function() { | |
isGameStarted = true; | |
document.getElementById('startScreen').style.display = 'none'; | |
document.getElementById('ui').style.display = 'block'; | |
document.getElementById('center-ui').style.display = 'block'; | |
document.getElementById('deaths-ui').style.display = 'block'; | |
document.getElementById('powerup-indicator').style.display = 'block'; | |
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { | |
document.getElementById('mobile-controls').style.display = 'flex'; | |
setupMobileControls(); | |
} | |
createPlayer(); | |
createEnemies(); | |
createShields(); | |
createStars(); | |
updateUI(); | |
startBackgroundMusic(); | |
}; | |
// Inicialização | |
animate(); | |
</script> | |
</body> | |
</html> |