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