一、前言
弹球游戏是一款很经典的游戏了,小时候无论是在掌机还是电脑都有玩过这款游戏,简简单单朴实无华,接下来我们通过前端代码来简单实现一下这个游戏吧。
这是一个基于 HTML5 Canvas 的弹球游戏的实现,通过 JavaScript 语言实现游戏的核心逻辑。主要包括以下部分:
- 定义了 canvas 元素并获取其上下文对象,用于在画布上绘制图形。
- 定义了小球(ball)、挡板(paddle)和砖块(bricks)的属性,包括位置、大小、颜色、运动方向和速度等。
- 初始化砖块数组,用于存储所有的砖块,并设置每个砖块的位置和状态(其中,状态为1表示砖块未被撞击,状态为0表示砖块已经被撞击)。
- 定义键盘事件处理函数,实现挡板的左右移动。
- 定义 move() 函数,用于更新小球和挡板的位置和状态,并检测边界碰撞和砖块与小球的碰撞,以及游戏结束和胜利的条件。
- 定义 draw() 函数,用于在画布上绘制小球、挡板、砖块和分数。
- 最后使用 setInterval() 函数调用 move() 和 draw() 函数,让游戏运行起来。
总的来说,通过前端代码实现了一个简单的弹球游戏,并且同时也涉及到了很多 JavaScript 和 HTML5 Canvas 的基本用法。主要的核心逻辑是对游戏元素的状态更新和游戏规则判断,以及绘制游戏图形的相关代码实现。
二、全部代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>弹球游戏</title>
<style type="text/css">
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
margin: 20px auto;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>
</body>
<script type="text/javascript">
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// 定义全局变量
var ball = {
x: canvas.width / 2,
y: canvas.height - 30,
radius: 10,
color: "#000",
dx: 2,
dy: -2
};
var paddle = {
x: canvas.width / 2 - 50,
y: canvas.height - 20,
width: 100,
height: 10,
color: "#0095DD",
speed: 7,
dx: 0
};
var bricks = [];
var brickRowCount = 3;
var brickColumnCount = 5;
var brickWidth = 75;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = (canvas.width - (brickWidth + brickPadding) * brickColumnCount) / 2;
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = {
x: 0,
y: 0,
status: 1
};
}
}
var score = 0;
// 按键事件处理
document.addEventListener("keydown", function (event) {
if (event.code === "ArrowLeft") {
paddle.dx = -paddle.speed;
} else if (event.code === "ArrowRight") {
paddle.dx = paddle.speed;
}
});
document.addEventListener("keyup", function (event) {
if (event.code === "ArrowLeft" || event.code === "ArrowRight") {
paddle.dx = 0;
}
});
function move() {
// 更新小球位置
ball.x += ball.dx;
ball.y += ball.dy;
// 检测边界碰撞
if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
ball.dx = -ball.dx;
}
if (ball.y - ball.radius < 0) {
ball.dy = -ball.dy;
} else if (ball.y + ball.radius > canvas.height - paddle.height) {
if (ball.x > paddle.x && ball.x < paddle.x + paddle.width) {
ball.dy = -ball.dy;
} else {
alert("Game Over");
document.location.reload();
clearInterval(interval);
}
}
// 更新挡板位置
paddle.x += paddle.dx;
if (paddle.x < 0) {
paddle.x = 0;
} else if (paddle.x + paddle.width > canvas.width) {
paddle.x = canvas.width - paddle.width;
}
// 检测砖块和小球的碰撞
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status === 1) {
if (ball.x > b.x && ball.x < b.x + brickWidth && ball.y > b.y && ball.y < b.y + brickHeight) {
ball.dy = -ball.dy;
b.status = 0;
score++;
if (score === brickRowCount * brickColumnCount) {
alert("You Win!");
document.location.reload();
clearInterval(interval);
}
}
}
}
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制小球
drawCircle(ball.x, ball.y, ball.radius, ball.color);
// 绘制挡板
drawRectangle(paddle.x, paddle.y, paddle.width, paddle.height, paddle.color);
// 绘制砖块
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status === 1) {
var brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
var brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
drawRectangle(brickX, brickY, brickWidth, brickHeight, "#0095DD");
}
}
}
// 绘制分数
ctx.font = "16px Arial";
ctx.fillStyle = "#000";
ctx.fillText("Score: " + score, 8, 20);
}
function drawRectangle(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();
ctx.closePath();
}
var interval = setInterval(function () {
move();
draw();
}, 10);
</script>
</html>
一些优化建议:
- 可以将砖块(bricks)的属性以及初始化操作封装成一个单独的构造函数或者类,以提高代码的可读性和维护性。
- 在绘制砖块时,可以使用一个三元运算符来判断砖块的状态,避免不必要的循环。
- 可以提取出一些常量和重复代码,如canvas的宽高、颜色等,以便后续修改和优化。
- 可以在更新小球和挡板位置的时候,使用requestAnimationFrame方法代替setInterval/setTimeout方法,以获得更流畅的动画效果。