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> |