| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Space Shooter</title> |
| <style> |
| body { |
| margin: 0; |
| overflow: hidden; |
| background: black; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| height: 100vh; |
| font-family: Arial; |
| color: white; |
| } |
| |
| #gameCanvas { |
| border: 2px solid white; |
| background-image: |
| radial-gradient(white 1px, transparent 0), |
| radial-gradient(white 1px, transparent 0); |
| background-size: 50px 50px; |
| background-position: 0 0, 25px 25px; |
| display: none; |
| } |
| |
| #menu { |
| text-align: center; |
| } |
| |
| .difficulty-btn { |
| padding: 10px 20px; |
| margin: 10px; |
| font-size: 18px; |
| cursor: pointer; |
| background: #333; |
| color: white; |
| border: 2px solid white; |
| border-radius: 5px; |
| } |
| |
| .difficulty-btn:hover { |
| background: #555; |
| } |
| |
| #score { |
| position: absolute; |
| top: 20px; |
| left: 20px; |
| font-size: 24px; |
| } |
| |
| #gameOver { |
| position: absolute; |
| font-size: 48px; |
| text-align: center; |
| display: none; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="menu"> |
| <h1>Space Shooter</h1> |
| <h2>Select Difficulty:</h2> |
| <button class="difficulty-btn" onclick="startGame('easy')">Easy</button> |
| <button class="difficulty-btn" onclick="startGame('medium')">Medium</button> |
| <button class="difficulty-btn" onclick="startGame('hard')">Hard</button> |
| </div> |
| <div id="score">Score: <span id="scoreValue">0</span></div> |
| <canvas id="gameCanvas" width="800" height="600"></canvas> |
| <div id="gameOver">Game Over!<br>Press R to Restart</div> |
|
|
| <script> |
| const canvas = document.getElementById('gameCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const scoreEl = document.getElementById('scoreValue'); |
| const gameOverEl = document.getElementById('gameOver'); |
| const menuEl = document.getElementById('menu'); |
| |
| let audioCtx; |
| let player = { |
| x: canvas.width/2, |
| y: 500, |
| speed: 5, |
| size: 30 |
| }; |
| let bullets = []; |
| let enemies = []; |
| let score = 0; |
| let gameOver = false; |
| let keys = { |
| left: false, |
| right: false |
| }; |
| |
| let gameConfig = { |
| easy: { |
| enemySpeed: 2, |
| spawnRate: 0.01, |
| playerSpeed: 5 |
| }, |
| medium: { |
| enemySpeed: 3, |
| spawnRate: 0.02, |
| playerSpeed: 6 |
| }, |
| hard: { |
| enemySpeed: 4, |
| spawnRate: 0.03, |
| playerSpeed: 7 |
| } |
| }; |
| |
| let currentConfig; |
| |
| function startGame(difficulty) { |
| menuEl.style.display = 'none'; |
| canvas.style.display = 'block'; |
| currentConfig = gameConfig[difficulty]; |
| player.speed = currentConfig.playerSpeed; |
| resetGame(); |
| if (!audioCtx) { |
| audio = setupAudio(); |
| } |
| } |
| |
| function setupAudio() { |
| audioCtx = new (window.AudioContext || window.webkitAudioContext)(); |
| |
| function playNote(freq, startTime, duration) { |
| const osc = audioCtx.createOscillator(); |
| const gain = audioCtx.createGain(); |
| |
| osc.connect(gain); |
| gain.connect(audioCtx.destination); |
| |
| osc.type = 'square'; |
| osc.frequency.setValueAtTime(freq, startTime); |
| |
| gain.gain.setValueAtTime(0.1, startTime); |
| gain.gain.exponentialRampToValueAtTime(0.01, startTime + duration); |
| |
| osc.start(startTime); |
| osc.stop(startTime + duration); |
| } |
| |
| function playBGM() { |
| const notes = [440, 523, 659, 784]; |
| setInterval(() => { |
| if (!gameOver) { |
| playNote(notes[Math.floor(Math.random() * notes.length)], |
| audioCtx.currentTime, |
| 0.1); |
| } |
| }, 200); |
| } |
| |
| function playShoot() { |
| playNote(880, audioCtx.currentTime, 0.1); |
| } |
| |
| playBGM(); |
| return { playShoot }; |
| } |
| |
| function drawPlayer() { |
| ctx.fillStyle = 'yellow'; |
| ctx.beginPath(); |
| for (let i = 0; i < 5; i++) { |
| const angle = (i * 4 * Math.PI) / 5 - Math.PI / 2; |
| const x = player.x + player.size * Math.cos(angle); |
| const y = player.y + player.size * Math.sin(angle); |
| i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); |
| } |
| ctx.closePath(); |
| ctx.fill(); |
| } |
| |
| function createEnemy() { |
| if (Math.random() < currentConfig.spawnRate) { |
| enemies.push({ |
| x: Math.random() * (canvas.width - 30) + 15, |
| y: -30, |
| speed: currentConfig.enemySpeed |
| }); |
| } |
| } |
| |
| function updatePlayer() { |
| if (keys.left) { |
| player.x = Math.max(player.size, player.x - player.speed); |
| } |
| if (keys.right) { |
| player.x = Math.min(canvas.width - player.size, player.x + player.speed); |
| } |
| } |
| |
| function updateGame() { |
| updatePlayer(); |
| |
| bullets = bullets.filter(bullet => { |
| bullet.y -= 8; |
| return bullet.y > 0; |
| }); |
| |
| enemies = enemies.filter(enemy => { |
| enemy.y += enemy.speed; |
| |
| if (Math.abs(enemy.x - player.x) < 30 && |
| Math.abs(enemy.y - player.y) < 30) { |
| gameOver = true; |
| gameOverEl.style.display = 'block'; |
| } |
| |
| return enemy.y < canvas.height; |
| }); |
| |
| bullets.forEach((bullet, bi) => { |
| enemies.forEach((enemy, ei) => { |
| if (Math.abs(bullet.x - enemy.x) < 30 && |
| Math.abs(bullet.y - enemy.y) < 30) { |
| bullets.splice(bi, 1); |
| enemies.splice(ei, 1); |
| score += 100; |
| scoreEl.textContent = score; |
| } |
| }); |
| }); |
| |
| createEnemy(); |
| } |
| |
| function draw() { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| drawPlayer(); |
| |
| ctx.fillStyle = 'white'; |
| bullets.forEach(bullet => { |
| ctx.fillRect(bullet.x - 2, bullet.y - 8, 4, 16); |
| }); |
| |
| ctx.font = '30px Arial'; |
| enemies.forEach(enemy => { |
| ctx.fillText('🐱', enemy.x - 15, enemy.y + 10); |
| }); |
| } |
| |
| function gameLoop() { |
| if (!gameOver) { |
| updateGame(); |
| draw(); |
| requestAnimationFrame(gameLoop); |
| } |
| } |
| |
| function resetGame() { |
| player.x = canvas.width/2; |
| bullets = []; |
| enemies = []; |
| score = 0; |
| gameOver = false; |
| scoreEl.textContent = '0'; |
| gameOverEl.style.display = 'none'; |
| gameLoop(); |
| } |
| |
| function returnToMenu() { |
| canvas.style.display = 'none'; |
| menuEl.style.display = 'block'; |
| gameOverEl.style.display = 'none'; |
| } |
| |
| let audio; |
| |
| document.addEventListener('keydown', (e) => { |
| if (gameOver) { |
| if (e.key.toLowerCase() === 'r') { |
| returnToMenu(); |
| } |
| return; |
| } |
| |
| switch(e.key) { |
| case 'ArrowLeft': |
| keys.left = true; |
| break; |
| case 'ArrowRight': |
| keys.right = true; |
| break; |
| case ' ': |
| bullets.push({ |
| x: player.x, |
| y: player.y - 20 |
| }); |
| if (audio) audio.playShoot(); |
| break; |
| } |
| }); |
| |
| document.addEventListener('keyup', (e) => { |
| switch(e.key) { |
| case 'ArrowLeft': |
| keys.left = false; |
| break; |
| case 'ArrowRight': |
| keys.right = false; |
| break; |
| } |
| }); |
| </script> |
| </body> |
| </html><script async data-explicit-opt-in="true" data-cookie-opt-in="true" src="https://vercel.live/_next-live/feedback/feedback.js"></script> |