在网页开发的领域中,利用 HTML 和 JavaScript 能够创造出各种引人入胜的互动游戏。其中,贪吃蛇作为一款经典之作,以其简单易玩的特性和紧张刺激的挑战,一直深受玩家的喜爱。本文将详细阐述如何运用 HTML 和 JavaScript 来打造一个具有美观度的贪吃蛇游戏,并深入剖析其代码。
一、游戏介绍
贪吃蛇是一款广为人知的游戏。玩家的任务是通过方向键控制蛇的移动来吞食食物。随着蛇不断吃到食物,其身体会逐渐变长。然而,如果蛇头撞击到边界或者蛇身自身,游戏就会宣告结束。
二、效果图
三、代码分析
-
HTML 结构:
- 页面主要包含一个
<canvas>
元素,用于绘制游戏画面。此外,还有一个<div>
元素,用于放置开始按钮和显示当前分数。
- 页面主要包含一个
-
JavaScript 部分:
SnakeGame
类是游戏的核心所在。在构造函数中,初始化了游戏所需的各种属性,包括蛇的初始位置、移动方向、食物位置以及得分等。同时,还添加了事件监听器,分别用于响应开始按钮的点击和键盘按下事件。generateFood
方法负责生成随机的食物位置,确保食物在画布范围内随机出现。draw
方法承担着绘制游戏画面的重任,它会遍历蛇的身体部分进行绘制,包括蛇头、眼睛、嘴以及蛇身,同时还会绘制红色的食物方块。update
方法用于更新游戏状态,包括移动蛇的位置、检查是否吃到食物以及判断是否发生碰撞。如果蛇头与食物位置重合,蛇会吃到食物,身体变长且得分增加;如果发生碰撞,游戏结束并进行重置。checkCollision
方法主要检查蛇是否撞到边界或者自身身体。如果蛇头超出画布边界或者与蛇身其他部分重合,就会返回碰撞状态。handleKeyDown
方法处理键盘按下事件,根据按下的方向键改变蛇的移动方向,但不能直接反向移动。startGame
方法启动游戏,设置游戏运行标志为true
,重置上次更新时间,并开始游戏循环。gameLoop
方法是游戏的循环核心,它根据时间间隔更新游戏状态并绘制游戏画面,通过不断请求动画帧来保持游戏的持续运行。
四、代码实现
以下是用 HTML 和 JavaScript 实现的贪吃蛇游戏代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇游戏</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
background-color: #e8e8e8;
font-family: 'Open Sans', sans-serif;
}
#gameCanvas {
border: 4px solid #2e86c1;
border-radius: 12px;
box-shadow: 0 0 20px rgba(46, 134, 193, 0.4);
margin: 40px auto;
}
.controls {
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
width: 420px;
display: flex;
flex-direction: column;
align-items: center;
}
button {
padding: 15px 30px;
background-color: #2e86c1;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 18px;
margin-top: 20px;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #226699;
}
p {
font-size: 20px;
margin-top: 15px;
}
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div class="controls">
<button id="startButton">开始游戏</button>
<p>当前分数:<span id="scoreDisplay">0</span></p>
</div>
<script>
// 定义贪吃蛇游戏类
class SnakeGame {
constructor() {
// 获取canvas元素,用于绘制游戏画面
this.canvas = document.getElementById('gameCanvas');
// 获取canvas的绘图上下文,用于在canvas上进行绘图操作
this.ctx = this.canvas.getContext('2d');
// 每个方块的大小
this.gridSize = 20;
// canvas的宽度包含的方块数量
this.widthInGrids = this.canvas.width / this.gridSize;
// canvas的高度包含的方块数量
this.heightInGrids = this.canvas.height / this.gridSize;
// 初始化蛇的位置为一个包含一个对象的数组,对象表示蛇头的初始位置
this.snake = [{ x: 10, y: 10 }];
// 初始化蛇的移动方向为向右
this.direction = 'right';
// 生成食物的初始位置
this.food = this.generateFood();
// 游戏是否正在运行的标志,初始为false
this.isRunning = false;
// 得分,初始为0
this.score = 0;
// 给开始按钮添加点击事件监听器,点击时调用startGame方法
document.getElementById('startButton').addEventListener('click', this.startGame.bind(this));
// 给文档添加键盘按下事件监听器,按下键盘时调用handleKeyDown方法
document.addEventListener('keydown', this.handleKeyDown.bind(this));
}
// 生成食物位置的方法
generateFood() {
// 返回一个随机的食物位置对象,x和y坐标在canvas范围内随机生成
return {
x: Math.floor(Math.random() * this.widthInGrids),
y: Math.floor(Math.random() * this.heightInGrids),
};
}
// 绘制游戏画面的方法
draw() {
// 清空canvas,为绘制新的一帧画面做准备
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 遍历蛇的身体部分进行绘制
this.snake.forEach((segment, index) => {
if (index === 0) {
// 绘制蛇头
this.ctx.beginPath();
this.ctx.arc(segment.x * this.gridSize + this.gridSize / 2, segment.y * this.gridSize + this.gridSize / 2, this.gridSize / 2, 0, 2 * Math.PI);
this.ctx.fillStyle = '#006400';
this.ctx.fill();
// 绘制眼睛
this.ctx.beginPath();
this.ctx.arc(segment.x * this.gridSize + this.gridSize / 4, segment.y * this.gridSize + this.gridSize / 4, 2, 0, 2 * Math.PI);
this.ctx.fillStyle = 'white';
this.ctx.fill();
this.ctx.beginPath();
this.ctx.arc(segment.x * this.gridSize + this.gridSize / 4, segment.y * this.gridSize + this.gridSize / 4, 1, 0, 2 * Math.PI);
this.ctx.fillStyle = 'black';
this.ctx.fill();
this.ctx.beginPath();
this.ctx.arc(segment.x * this.gridSize + this.gridSize * 3 / 4, segment.y * this.gridSize + this.gridSize / 4, 2, 0, 2 * Math.PI);
this.ctx.fillStyle = 'white';
this.ctx.fill();
this.ctx.beginPath();
this.ctx.arc(segment.x * this.gridSize + this.gridSize * 3 / 4, segment.y * this.gridSize + this.gridSize / 4, 1, 0, 2 * Math.PI);
this.ctx.fillStyle = 'black';
this.ctx.fill();
// 绘制嘴
this.ctx.beginPath();
this.ctx.moveTo(segment.x * this.gridSize + this.gridSize / 2, segment.y * this.gridSize + this.gridSize * 3 / 4);
this.ctx.lineTo(segment.x * this.gridSize + this.gridSize / 4, segment.y * this.gridSize + this.gridSize * 2 / 3);
this.ctx.lineTo(segment.x * this.gridSize + this.gridSize * 3 / 4, segment.y * this.gridSize + this.gridSize * 2 / 3);
this.ctx.fillStyle = '#003300';
this.ctx.fill();
} else {
// 绘制蛇身,使用较浅的绿色
this.ctx.fillStyle = '#008000';
this.ctx.fillRect(segment.x * this.gridSize, segment.y * this.gridSize, this.gridSize, this.gridSize);
}
});
// 绘制红色方块表示食物
this.ctx.fillStyle = '#ff0000';
this.ctx.fillRect(this.food.x * this.gridSize, this.food.y * this.gridSize, this.gridSize, this.gridSize);
}
// 更新游戏状态的方法
update() {
// 如果游戏没有在运行,直接返回,不进行更新
if (!this.isRunning) return;
// 获取蛇头的位置
const head = { x: this.snake[0].x, y: this.snake[0].y };
// 根据当前方向更新蛇头位置
switch (this.direction) {
case 'right':
head.x++;
break;
case 'left':
head.x--;
break;
case 'up':
head.y--;
break;
case 'down':
head.y++;
break;
}
// 将新的蛇头位置添加到蛇身数组的开头
this.snake.unshift(head);
// 如果蛇头位置与食物位置相同,表示吃到食物
if (head.x === this.food.x && head.y === this.food.y) {
// 重新生成食物位置
this.food = this.generateFood();
// 得分加一
this.score++;
// 更新页面上显示的得分
document.getElementById('scoreDisplay').textContent = this.score;
} else {
// 如果没有吃到食物,将蛇尾部分移除
this.snake.pop();
}
// 检查是否发生碰撞
if (this.checkCollision()) {
// 如果发生碰撞,弹出提示框显示得分并重置游戏状态
alert(`游戏结束!你的得分为:${this.score}`);
this.snake = [{ x: 10, y: 10 }];
this.direction = 'right';
this.food = this.generateFood();
this.score = 0;
document.getElementById('scoreDisplay').textContent = this.score;
this.isRunning = false;
}
}
// 检查碰撞的方法
checkCollision() {
const head = this.snake[0];
// 检查蛇头是否超出canvas边界
if (head.x < 0 || head.x >= this.widthInGrids || head.y < 0 || head.y >= this.heightInGrids) {
return true;
}
// 检查蛇头是否与蛇身其他部分碰撞
for (let i = 1; i < this.snake.length; i++) {
if (head.x === this.snake[i].x && head.y === this.snake[i].y) {
return true;
}
}
// 如果没有发生碰撞,返回false
return false;
}
// 处理键盘按下事件的方法
handleKeyDown(event) {
// 如果游戏没有在运行,直接返回,不处理按键事件
if (!this.isRunning) return;
// 根据按下的方向键改变蛇的移动方向,但不能直接反向移动
switch (event.key) {
case 'ArrowRight':
if (this.direction!== 'left') this.direction = 'right';
break;
case 'ArrowLeft':
if (this.direction!== 'right') this.direction = 'left';
break;
case 'ArrowUp':
if (this.direction!== 'down') this.direction = 'up';
break;
case 'ArrowDown':
if (this.direction!== 'up') this.direction = 'down';
break;
}
}
// 开始游戏的方法
startGame() {
// 设置游戏正在运行标志为true
this.isRunning = true;
// 将上次更新时间重置为0
this.lastTime = 0;
// 开始游戏循环
requestAnimationFrame(this.gameLoop.bind(this));
}
// 游戏循环方法
gameLoop(timestamp) {
// 如果游戏没有在运行,直接返回
if (!this.isRunning) return;
// 如果自上次更新时间超过100毫秒
if (timestamp - this.lastTime > 100) {
// 更新游戏状态
this.update();
// 绘制游戏画面
this.draw();
// 更新上次更新时间为当前时间戳
this.lastTime = timestamp;
}
// 请求下一次动画帧,继续游戏循环
requestAnimationFrame(this.gameLoop.bind(this));
}
}
// 游戏规则说明:
// - 玩家通过方向键控制蛇的移动。
// - 蛇会不断地朝着当前方向移动,吃到食物后身体会变长,得分增加。
// - 如果蛇头撞到边界或者自己的身体,游戏结束。
// 创建一个新的游戏实例,启动游戏
new SnakeGame();
</script>
</body>
</html>
五、总结
通过本文,我们成功地实现了一个简单的贪吃蛇游戏,并对其代码进行了详细的分析。这个游戏展示了 HTML5 的canvas
元素和 JavaScript 的强大组合能力,为网页游戏开发提供了一个良好的示例。你可以根据自己的需求进一步扩展和优化这个游戏,比如添加不同的游戏模式、音效效果等,使其更加丰富多彩,为玩家带来更好的游戏体验。