音效管理模块:
audioContext:创建音频上下文,用于处理音频。
playSound:生成简单的音调,通过设置频率和持续时间播放声音。
gameSounds:存储预定义的音效,使用 loadSound 函数加载音频数据。
loadSound:通过 XMLHttpRequest 加载音频数据,并使用 decodeAudioData 解码。 playPredefinedSound:播放预定义的音效。
游戏配置:
DIFFICULTY_SETTINGS:定义不同难度级别下的游戏参数,如砖块行数、球的速度、挡板宽度和生命数量。 game:存储游戏的状态、时间、分数、粒子和游戏元素(球、挡板、砖块)的信息。 初始化新游戏: initGame:初始化游戏状态,设置挡板宽度、球的初始速度、时间、分数和生命数量,生成砖块,并更新显示信息。 generateBricks:根据当前难度级别生成砖块,并设置砖块的位置、颜色和大小。 输入处理: canvas.addEventListener('mousemove'):监听鼠标移动事件,根据鼠标位置移动挡板。 canvas.addEventListener('mousedown'):监听鼠标点击事件,开始游戏时设置球的初始速度,并播放音效。 document.getElementById('difficulty').addEventListener('change'):监听难度选择框的变化,更新游戏配置并重新初始化游戏。 粒子特效: createParticles:在指定位置创建粒子,设置粒子的初始位置、速度、生命周期和颜色。 游戏逻辑: update:更新游戏状态,包括球的移动、边界碰撞检测、胜利和失败检测、挡板和砖块碰撞检测,更新粒子状态和计时信息。 resetBall:重置球的位置和速度。 渲染系统: draw:绘制游戏画面,包括挡板、球、砖块和粒子。 游戏循环: gameLoop:游戏主循环,不断更新游戏状态并绘制画面。 模态框处理: showModal:显示模态框,设置模态框的消息和得分。 closeModal:关闭模态框。
音效管理: loadSound 函数添加了错误处理,当音频加载失败或者解码失败时会进行错误提示。 playPredefinedSound 函数也添加了错误处理,确保在播放音效出错时能在控制台输出错误信息。 渲染系统优化: 新增了 drawRect 和 drawCircle 函数,将绘制矩形和圆形的代码封装起来,提高了代码的复用性和可维护性。 显示更新优化: 新增了 updateDisplay 函数,将更新时间、分数和生命数量显示的代码封装起来,避免代码重复。 这个完整的代码实现了一个功能较为完善的有声打砖块游戏,同时在代码结构和错误处理方面进行了一定的优化
body {
background: #2c3e50;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
.panel {
display: flex;
gap: 10px;
color: white;
margin-bottom: 10px;
}
canvas {
border: 2px solid #333;
background: #1a1a1a;
}
#modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #34495e;
padding: 20px;
border-radius: 5px;
color: white;
text-align: center;
}
⏳ 时间: 0秒
❤️ 生命: 3
🏆 分数: 0
得分:
// 音效管理模块
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
function playSound(frequency = 440, duration = 0.1) {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = 'square';
oscillator.frequency.value = frequency;
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + duration);
}
const gameSounds = {
hit: loadSound('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU'),
break: loadSound('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAHBuZmZmZmZmZmZmZmZmZmQ==')
};
function loadSound(url) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = () => {
audioContext.decodeAudioData(request.response)
.then((buffer) => {
resolve(buffer);
})
.catch((error) => {
reject(error);
});
};
request.onerror = () => {
reject(new Error('音频加载失败'));
};
request.send();
});
}
async function playPredefinedSound(sound) {
try {
const buffer = await gameSounds[sound];
const source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(audioContext.destination);
source.start();
} catch (error) {
console.error('播放音效失败:', error);
}
}
// 游戏配置
const DIFFICULTY_SETTINGS = {
easy: { rows: 4, speed: 4, paddle: 120, lives: 5 },
normal: { rows: 6, speed: 5, paddle: 100, lives: 3 },
hard: { rows: 8, speed: 6, paddle: 80, lives: 2 }
};
const game = {
state: 'menu',
time: 0,
score: 0,
particles: [],
config: DIFFICULTY_SETTINGS.normal,
elements: {
ball: { x: 400, y: 550, r: 8, dx: 0, dy: 0 },
paddle: { x: 350, y: 580, w: 100, h: 12 },
bricks: []
}
};
// 初始化新游戏
function initGame() {
const config = game.config;
game.elements.paddle.w = config.paddle;
game.elements.ball.dx = 0;
game.elements.ball.dy = 0;
game.time = 0;
game.score = 0;
game.lives = config.lives;
game.state = 'running';
generateBricks();
updateDisplay();
}
// 生成砖块
function generateBricks() {
game.elements.bricks = [];
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4'];
const brickWidth = 85;
const brickHeight = 25;
for (let i = 0; i < 8; i++) {
for (let j = 0; j < game.config.rows; j++) {
game.elements.bricks.push({
x: 20 + i * (brickWidth + 10),
y: 50 + j * (brickHeight + 10),
w: brickWidth,
h: brickHeight,
color: colors[j % colors.length]
});
}
}
}
// 输入处理
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
game.elements.paddle.x = Math.max(0,
Math.min(e.clientX - rect.left - game.elements.paddle.w / 2,
canvas.width - game.elements.paddle.w));
});
canvas.addEventListener('mousedown', (e) => {
if (e.button === 0 && !game.elements.ball.dx) {
playSound(300.25);
const speed = game.config.speed;
game.elements.ball.dx = speed * (Math.random() > 0.5 ? 1 : -1);
game.elements.ball.dy = -speed;
}
});
document.getElementById('difficulty').addEventListener('change', () => {
game.config = DIFFICULTY_SETTINGS[document.getElementById('difficulty').value];
initGame();
});
// 粒子特效
const MAX_PARTICLES = 100;
function createParticles(x, y, color) {
for (let i = 0; i < 8; i++) {
if (game.particles.length < MAX_PARTICLES) {
game.particles.push({
x,
y,
dx: (Math.random() - 0.5) * 8,
dy: (Math.random() - 0.5) * 8,
life: 1,
color
});
}
}
}
// 游戏逻辑
function update() {
const ball = game.elements.ball;
// 球移动
ball.x += ball.dx;
ball.y += ball.dy;
// 边界碰撞
if (ball.x < ball.r || ball.x > canvas.width - ball.r) {
ball.dx *= -1;
playPredefinedSound('hit');
playSound(392);
createParticles(ball.x, ball.y, '#FFD700');
}
if (ball.y < ball.r) {
ball.dy *= -1;
playPredefinedSound('hit');
playSound(220);
createParticles(ball.x, ball.y, '#FFD700');
}
// 胜利检测
if (game.elements.bricks.length === 0) {
showModal('恭喜通关!', game.score);
initGame();
return;
}
// 挡板碰撞
if (ball.y + ball.r > game.elements.paddle.y &&
ball.x > game.elements.paddle.x &&
ball.x < game.elements.paddle.x + game.elements.paddle.w) {
ball.dy = -Math.abs(ball.dy);
const hitPos = (ball.x - (game.elements.paddle.x + game.elements.paddle.w / 2)) / (game.elements.paddle.w / 2);
ball.dx = hitPos * game.config.speed;
playPredefinedSound('hit');
playSound(523.25);
createParticles(ball.x, ball.y, '#666');
}
// 砖块碰撞
game.elements.bricks = game.elements.bricks.filter((brick) => {
if (ball.x + ball.r > brick.x &&
ball.x - ball.r < brick.x + brick.w &&
ball.y + ball.r > brick.y &&
ball.y - ball.r < brick.y + brick.h) {
ball.dy *= -1;
game.score += 100;
playPredefinedSound('break');
playSound(659.25);
createParticles(brick.x + brick.w / 2, brick.y + brick.h / 2, brick.color);
return false;
}
return true;
});
// 失败检测
if (ball.y > canvas.height) {
if (--game.lives <= 0) {
playSound(100.25);
showModal('游戏结束!', game.score);
initGame();
} else {
playSound(150.25);
resetBall();
}
}
// 更新粒子
game.particles = game.particles.filter((p) => {
p.x += p.dx;
p.y += p.dy;
p.life -= 0.02;
return p.life > 0;
});
// 更新计时
game.time += 1 / 60;
updateDisplay();
}
function resetBall() {
const paddle = game.elements.paddle;
game.elements.ball.x = paddle.x + paddle.w / 2;
game.elements.ball.y = paddle.y - 20;
game.elements.ball.dx = 0;
game.elements.ball.dy = 0;
}
// 渲染系统
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制挡板
drawRect(game.elements.paddle.x, game.elements.paddle.y, game.elements.paddle.w, game.elements.paddle.h, '#2ecc71');
// 绘制球
drawCircle(game.elements.ball.x, game.elements.ball.y, game.elements.ball.r, '#FFD700');
// 绘制砖块
game.elements.bricks.forEach((brick) => {
drawRect(brick.x, brick.y, brick.w, brick.h, brick.color);
});
// 绘制粒子
game.particles.forEach((p) => {
drawCircle(p.x, p.y, 3 * p.life, `${p.color}${Math.floor(p.life * 255).toString(16).padStart(2, '0')}`);
});
}
function drawRect(x, y, width, height, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, width, height);
}
function drawCircle(x, y, radius, color) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
// 游戏循环
function gameLoop() {
if (game.state === 'running') update();
draw();
requestAnimationFrame(gameLoop);
}
// 模态框处理
function showModal(message, score) {
const modal = document.getElementById('modal');
const messageElement = document.getElementById('modal-message');
const scoreElement = document.getElementById('modal-score');
messageElement.textContent = message;
scoreElement.textContent = score;
modal.style.display = 'block';
}
function closeModal() {
const modal = document.getElementById('modal');
modal.style.display = 'none';
}
function updateDisplay() {
document.getElementById('timer').textContent = Math.floor(game.time);
document.getElementById('score').textContent = game.score;
document.getElementById('lives').textContent = game.lives;
}
initGame();
gameLoop();