<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Breakout</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a1a;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
user-select: none;
}
#gameContainer {
position: relative;
width: 800px;
max-width: 95vw;
aspect-ratio: 4/3;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 0 40px rgba(0, 200, 255, 0.2), 0 0 80px rgba(0, 200, 255, 0.1);
background: #0d0d2b;
}
canvas {
display: block;
width: 100%;
height: 100%;
background: linear-gradient(180deg, #0d0d2b 0%, #1a1a3e 100%);
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(10, 10, 30, 0.85);
backdrop-filter: blur(5px);
z-index: 10;
transition: opacity 0.3s;
}
.overlay.hidden {
opacity: 0;
pointer-events: none;
}
.overlay h1 {
font-size: 3rem;
color: #00e5ff;
text-shadow: 0 0 20px rgba(0, 229, 255, 0.5);
margin-bottom: 20px;
letter-spacing: 4px;
}
.overlay h2 {
font-size: 2rem;
color: #ff6b6b;
text-shadow: 0 0 15px rgba(255, 107, 107, 0.5);
margin-bottom: 20px;
}
.overlay p {
color: #aaa;
font-size: 1.2rem;
margin: 10px 0;
}
.overlay .score-final {
font-size: 2.5rem;
color: #ffd700;
text-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
margin: 20px 0;
}
.overlay .btn {
background: linear-gradient(135deg, #00e5ff, #0077ff);
border: none;
color: white;
padding: 12px 30px;
font-size: 1.2rem;
border-radius: 30px;
cursor: pointer;
margin-top: 20px;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 0 20px rgba(0, 229, 255, 0.3);
}
.overlay .btn:hover {
transform: scale(1.05);
box-shadow: 0 0 30px rgba(0, 229, 255, 0.5);
}
.overlay .btn:active {
transform: scale(0.95);
}
.controls-info {
color: #888;
font-size: 0.9rem;
margin-top: 20px;
text-align: center;
line-height: 1.6;
}
.controls-info span {
color: #00e5ff;
}
#pauseOverlay {
background: rgba(10, 10, 30, 0.7);
}
#pauseOverlay h1 {
font-size: 2.5rem;
color: #ffd700;
}
#winOverlay h1 {
color: #00ff88;
text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
}
#gameOverOverlay h1 {
color: #ff4444;
text-shadow: 0 0 20px rgba(255, 68, 68, 0.5);
}
.particle {
position: absolute;
pointer-events: none;
border-radius: 50%;
animation: particleAnim 0.6s ease-out forwards;
}
@keyframes particleAnim {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(0);
opacity: 0;
}
}
.heart {
color: #ff4444;
font-size: 1.5rem;
text-shadow: 0 0 10px rgba(255, 68, 68, 0.5);
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<!-- Start Overlay -->
<div class="overlay" id="startOverlay">
<h1>NEON BREAKOUT</h1>
<p>Break all the bricks!</p>
<button class="btn" id="startBtn">Press Space to Start</button>
<div class="controls-info">
<span>← →</span> or <span>Mouse</span> to move<br>
<span>Space</span> to launch ball<br>
<span>P</span> to pause
</div>
</div>
<!-- Pause Overlay -->
<div class="overlay hidden" id="pauseOverlay">
<h1>PAUSED</h1>
<p>Press P to resume</p>
</div>
<!-- Game Over Overlay -->
<div class="overlay hidden" id="gameOverOverlay">
<h1>GAME OVER</h1>
<div class="score-final" id="finalScore">0</div>
<button class="btn" id="restartBtn">Play Again</button>
</div>
<!-- Win Overlay -->
<div class="overlay hidden" id="winOverlay">
<h1>YOU WIN!</h1>
<div class="score-final" id="winScore">0</div>
<button class="btn" id="nextLevelBtn">Next Level</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const container = document.getElementById('gameContainer');
// Set canvas size
const WIDTH = 800;
const HEIGHT = 600;
canvas.width = WIDTH;
canvas.height = HEIGHT;
// Game state
let gameState = 'start'; // start, playing, paused, gameover, win
let score = 0;
let lives = 3;
let level = 1;
let highScore = 0;
// Paddle
const paddle = {
width: 100,
height: 15,
x: WIDTH / 2 - 50,
y: HEIGHT - 40,
speed: 8,
color: '#00e5ff'
};
// Ball
const ball = {
x: WIDTH / 2,
y: HEIGHT - 60,
radius: 8,
dx: 4,
dy: -4,
speed: 4,
launched: false,
color: '#ffffff'
};
// Bricks
let bricks = [];
const brickRows = 5;
const brickCols = 8;
const brickWidth = 70;
const brickHeight = 20;
const brickPadding = 10;
const brickOffsetTop = 50;
const brickOffsetLeft = (WIDTH - (brickCols * (brickWidth + brickPadding) - brickPadding)) / 2;
// Colors for bricks
const brickColors = [
'#ff6b6b', '#ffa500', '#ffd700', '#00ff88', '#00e5ff'
];
// Particles
let particles = [];
// Audio context
let audioCtx = null;
// Keyboard state
const keys = {};
// Initialize audio
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
}
// Sound effects
function playSound(freq, duration, type = 'sine', volume = 0.3) {
if (!audioCtx) return;
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.frequency.value = freq;
oscillator.type = type;
gainNode.gain.setValueAtTime(volume, audioCtx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
oscillator.start();
oscillator.stop(audioCtx.currentTime + duration);
}
function playBounceSound() {
playSound(200, 0.1, 'square', 0.2);
}
function playBrickSound() {
playSound(500 + Math.random() * 200, 0.2, 'triangle', 0.3);
}
function playGameOverSound() {
playSound(300, 0.5, 'sawtooth', 0.3);
setTimeout(() => playSound(200, 0.5, 'sawtooth', 0.3), 200);
setTimeout(() => playSound(100, 0.8, 'sawtooth', 0.3), 400);
}
function playWinSound() {
playSound(523, 0.2, 'sine', 0.3);
setTimeout(() => playSound(659, 0.2, 'sine', 0.3), 200);
setTimeout(() => playSound(784, 0.3, 'sine', 0.3), 400);
}
// Create bricks
function createBricks() {
bricks = [];
for (let row = 0; row < brickRows; row++) {
for (let col = 0; col < brickCols; col++) {
bricks.push({
x: brickOffsetLeft + col * (brickWidth + brickPadding),
y: brickOffsetTop + row * (brickHeight + brickPadding),
width: brickWidth,
height: brickHeight,
color: brickColors[row],
alive: true,
row: row
});
}
}
}
// Reset ball
function resetBall() {
ball.x = WIDTH / 2;
ball.y = HEIGHT - 60;
ball.dx = 4;
ball.dy = -4;
ball.speed = 4;
ball.launched = false;
}
// Reset paddle
function resetPaddle() {
paddle.x = WIDTH / 2 - paddle.width / 2;
}
// Particle effects
function createParticles(x, y, color) {
for (let i = 0; i < 8; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random() * 3;
particles.push({
x: x,
y: y,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
size: 2 + Math.random() * 3,
color: color,
life: 1
});
}
}
// Update particles
function updateParticles() {
particles = particles.filter(p => p.life > 0);
particles.forEach(p => {
p.x += p.dx;
p.y += p.dy;
p.dy += 0.1;
p.life -= 0.02;
});
}
// Draw particles
function drawParticles() {
particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
ctx.globalAlpha = 1;
}
// Collision detection
function checkCollisions() {
// Wall collisions
if (ball.x - ball.radius < 0) {
ball.dx = Math.abs(ball.dx);
playBounceSound();
}
if (ball.x + ball.radius > WIDTH) {
ball.dx = -Math.abs(ball.dx);
playBounceSound();
}
if (ball.y - ball.radius < 0) {
ball.dy = Math.abs(ball.dy);
playBounceSound();
}
// Paddle collision
if (ball.dy > 0 &&
ball.y + ball.radius >= paddle.y &&
ball.y + ball.radius <= paddle.y + paddle.height &&
ball.x >= paddle.x - ball.radius &&
ball.x <= paddle.x + paddle.width + ball.radius) {
// Calculate bounce angle based on where ball hits paddle
const hitPos = (ball.x - paddle.x) / paddle.width;
const angle = (hitPos - 0.5) * Math.PI / 3;
const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
ball.dx = Math.sin(angle) * speed;
ball.dy = -Math.cos(angle) * speed;
ball.y = paddle.y - ball.radius;
playBounceSound();
}
// Brick collisions
for (let i = 0; i < bricks.length; i++) {
const brick = bricks[i];
if (!brick.alive) continue;
if (ball.x + ball.radius > brick.x &&
ball.x - ball.radius < brick.x + brick.width &&
ball.y + ball.radius > brick.y &&
ball.y - ball.radius < brick.y + brick.height) {
brick.alive = false;
score += 10;
createParticles(brick.x + brick.width / 2, brick.y + brick.height / 2, brick.color);
playBrickSound();
// Determine bounce direction
const overlapLeft = ball.x + ball.radius - brick.x;
const overlapRight = brick.x + brick.width - (ball.x - ball.radius);
const overlapTop = ball.y + ball.radius - brick.y;
const overlapBottom = brick.y + brick.height - (ball.y - ball.radius);
const minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
if (minOverlap === overlapLeft || minOverlap === overlapRight) {
ball.dx = -ball.dx;
} else {
ball.dy = -ball.dy;
}
// Check win condition
if (bricks.every(b => !b.alive)) {
gameState = 'win';
document.getElementById('winScore').textContent = score;
document.getElementById('winOverlay').classList.remove('hidden');
playWinSound();
}
break;
}
}
// Ball falls below paddle
if (ball.y > HEIGHT + 50) {
lives--;
if (lives <= 0) {
gameState = 'gameover';
document.getElementById('finalScore').textContent = score;
document.getElementById('gameOverOverlay').classList.remove('hidden');
playGameOverSound();
} else {
resetBall();
resetPaddle();
}
}
}
// Draw functions
function drawPaddle() {
// Glow effect
ctx.shadowColor = paddle.color;
ctx.shadowBlur = 20;
const gradient = ctx.createLinearGradient(paddle.x, paddle.y, paddle.x, paddle.y + paddle.height);
gradient.addColorStop(0, '#00ffff');
gradient.addColorStop(1, '#0077ff');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.roundRect(paddle.x, paddle.y, paddle.width, paddle.height, 8);
ctx.fill();
ctx.shadowBlur = 0;
}
function drawBall() {
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 15;
const gradient = ctx.createRadialGradient(
ball.x - 2, ball.y - 2, 1,
ball.x, ball.y, ball.radius
);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(1, '#00e5ff');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
function drawBricks() {
bricks.forEach(brick => {
if (!brick.alive) return;
ctx.shadowColor = brick.color;
ctx.shadowBlur = 10;
const gradient = ctx.createLinearGradient(brick.x, brick.y, brick.x, brick.y + brick.height);
gradient.addColorStop(0, brick.color);
gradient.addColorStop(1, shadeColor(brick.color, -30));
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.roundRect(brick.x, brick.y, brick.width, brick.height, 4);
ctx.fill();
// Highlight
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.beginPath();
ctx.roundRect(brick.x + 2, brick.y + 2, brick.width - 4, brick.height / 2, 3);
ctx.fill();
ctx.shadowBlur = 0;
});
}
function drawHUD() {
// Score
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 20px "Segoe UI", sans-serif';
ctx.textAlign = 'left';
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 10;
ctx.fillText(`SCORE: ${score}`, 20, 35);
ctx.shadowBlur = 0;
// Level
ctx.textAlign = 'center';
ctx.fillStyle = '#ffd700';
ctx.shadowColor = '#ffd700';
ctx.shadowBlur = 10;
ctx.fillText(`LEVEL ${level}`, WIDTH / 2, 35);
ctx.shadowBlur = 0;
// Lives
ctx.textAlign = 'right';
ctx.fillStyle = '#ff4444';
ctx.shadowColor = '#ff4444';
ctx.shadowBlur = 10;
let hearts = '';
for (let i = 0; i < lives; i++) {
hearts += '♥ ';
}
ctx.fillText(hearts, WIDTH - 20, 35);
ctx.shadowBlur = 0;
}
function shadeColor(color, percent) {
const num = parseInt(color.replace('#', ''), 16);
const amt = Math.round(2.55 * percent);
const R = Math.min(255, Math.max(0, (num >> 16) + amt));
const G = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + amt));
const B = Math.min(255, Math.max(0, (num & 0x0000FF) + amt));
return `#${(0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1)}`;
}
// Update game
function update() {
if (gameState !== 'playing') return;
// Move paddle with keyboard
if (keys['ArrowLeft'] && paddle.x > 0) {
paddle.x -= paddle.speed;
}
if (keys['ArrowRight'] && paddle.x + paddle.width < WIDTH) {
paddle.x += paddle.speed;
}
// Launch ball
if (ball.launched) {
ball.x += ball.dx;
ball.y += ball.dy;
// Increase speed slightly over time
const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
if (speed < 8) {
ball.dx *= 1.0005;
ball.dy *= 1.0005;
}
} else {
// Ball follows paddle
ball.x = paddle.x + paddle.width / 2;
ball.y = paddle.y - ball.radius - 2;
}
checkCollisions();
updateParticles();
}
// Draw everything
function draw() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
// Background grid
ctx.strokeStyle = 'rgba(255,255,255,0.03)';
ctx.lineWidth = 1;
for (let i = 0; i < WIDTH; i += 40) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, HEIGHT);
ctx.stroke();
}
for (let i = 0; i < HEIGHT; i += 40) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(WIDTH, i);
ctx.stroke();
}
drawBricks();
drawPaddle();
drawBall();
drawParticles();
drawHUD();
}
// Game loop
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
// Event listeners
document.addEventListener('keydown', (e) => {
keys[e.key] = true;
if (e.key === ' ') {
e.preventDefault();
initAudio();
if (gameState === 'start') {
gameState = 'playing';
document.getElementById('startOverlay').classList.add('hidden');
ball.launched = true;
} else if (gameState === 'playing' && !ball.launched) {
ball.launched = true;
}
}
if (e.key === 'p' || e.key === 'P') {
if (gameState === 'playing') {
gameState = 'paused';
document.getElementById('pauseOverlay').classList.remove('hidden');
} else if (gameState === 'paused') {
gameState = 'playing';
document.getElementById('pauseOverlay').classList.add('hidden');
}
}
});
document.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// Mouse control
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const scaleX = WIDTH / rect.width;
const mouseX = (e.clientX - rect.left) * scaleX;
if (gameState === 'playing') {
paddle.x = mouseX - paddle.width / 2;
paddle.x = Math.max(0, Math.min(WIDTH - paddle.width, paddle.x));
}
});
// Touch control
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const scaleX = WIDTH / rect.width;
const touchX = (e.touches[0].clientX - rect.left) * scaleX;
if (gameState === 'playing') {
paddle.x = touchX - paddle.width / 2;
paddle.x = Math.max(0, Math.min(WIDTH - paddle.width, paddle.x));
}
});
// Button handlers
document.getElementById('startBtn').addEventListener('click', () => {
initAudio();
gameState = 'playing';
document.getElementById('startOverlay').classList.add('hidden');
ball.launched = true;
});
document.getElementById('restartBtn').addEventListener('click', () => {
score = 0;
lives = 3;
level = 1;
createBricks();
resetBall();
resetPaddle();
gameState = 'playing';
document.getElementById('gameOverOverlay').classList.add('hidden');
ball.launched = true;
});
document.getElementById('nextLevelBtn').addEventListener('click', () => {
level++;
lives = Math.min(5, lives + 1);
createBricks();
resetBall();
resetPaddle();
ball.speed = 4 + (level - 1) * 0.5;
gameState = 'playing';
document.getElementById('winOverlay').classList.add('hidden');
ball.launched = true;
});
// Initialize game
createBricks();
resetBall();
resetPaddle();
// Start game loop
gameLoop();
</script>
</body>
</html>
Discussion
0 commentaires