代码是一个用 HTML5 Canvas 和 JavaScript 实现的简单贪吃蛇游戏。
HTML 部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Snake Game</title>
<style>
canvas {
display: block;
margin: 0 auto;
background-color: #000;
}
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<canvas>
:游戏在一个 400x400 像素的画布上运行,ID 为gameCanvas
。- CSS:
display: block
和margin: 0 auto
让画布居中显示。background-color: #000
设置画布背景为黑色。
JavaScript 部分
JavaScript 代码控制游戏的逻辑,包括蛇的移动、食物生成、碰撞检测等。
1. 初始化
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const gridSize = 20;
const tileCount = canvas.width / gridSize;
canvas
:获取画布元素。ctx
:获取 2D 绘图上下文,用于绘制图形。gridSize = 20
:游戏网格的大小,每个蛇身或食物占 20x20 像素。tileCount = 20
:画布宽 400 像素 ÷ 网格大小 20 = 20 个格子,游戏在一个 20x20 的网格中运行。
2. 游戏状态
let snake = [{ x: 10, y: 10 }];
let food = { x: Math.floor(Math.random() * tileCount), y: Math.floor(Math.random() * tileCount) };
let direction = { x: 0, y: 0 };
let score = 0;
snake
:蛇是一个数组,初始时只有一节,位于网格坐标 (10, 10)。food
:食物的初始位置在网格中随机生成(0 到 19 的整数)。direction
:蛇的移动方向,初始为静止{ x: 0, y: 0 }
。score
:玩家的得分,初始为 0。
3. 游戏循环
function gameLoop() {
update();
draw();
setTimeout(gameLoop, 100);
}
gameLoop();
gameLoop
:主循环,调用update
(更新游戏状态)和draw
(绘制画面)。setTimeout(gameLoop, 100)
:每 100 毫秒运行一次循环,控制游戏速度(每秒约 10 次更新)。- 最后调用
gameLoop()
启动游戏。
4. 更新逻辑
function update() {
const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount || snake.some(segment => segment.x === head.x && segment.y === head.y)) {
resetGame();
return;
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score++;
food = { x: Math.floor(Math.random() * tileCount), y: Math.floor(Math.random() * tileCount) };
} else {
snake.pop();
}
}
- 计算新蛇头:
head
:根据当前方向计算蛇头的新位置(当前蛇头坐标 + 方向)。
- 碰撞检测:
- 检查蛇头是否撞墙(超出网格边界:
x < 0
或x >= tileCount
等)。 - 检查蛇头是否撞到自己(
snake.some(...)
检查蛇头是否与蛇身任一段重合)。 - 如果碰撞,调用
resetGame()
重置游戏。
- 检查蛇头是否撞墙(超出网格边界:
- 移动蛇:
snake.unshift(head)
:将新蛇头加入蛇数组头部。
- 吃食物:
- 如果蛇头坐标等于食物坐标,说明吃到食物:
score++
:得分加 1。- 随机生成新食物位置。
- 如果没吃到食物,
snake.pop()
移除蛇尾,保持蛇长度不变(模拟移动)。
- 如果蛇头坐标等于食物坐标,说明吃到食物:
- 如果吃到食物,不移除蛇尾,蛇会变长。
5. 绘制画面
function draw() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#0f0';
snake.forEach(segment => ctx.fillRect(segment.x * gridSize, segment.y * gridSize, gridSize, gridSize));
ctx.fillStyle = '#f00';
ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
ctx.fillStyle = '#fff';
ctx.font = '20px Arial';
ctx.fillText('Score: ' + score, 10, 30);
}
- 清空画布:
- 用黑色 (
#000
) 填充整个画布,清除上一帧。
- 用黑色 (
- 绘制蛇:
- 用绿色 (
#0f0
) 绘制蛇的每一段,segment.x * gridSize
将网格坐标转为像素坐标。
- 用绿色 (
- 绘制食物:
- 用红色 (
#f00
) 绘制食物方块。
- 用红色 (
- 绘制得分:
- 用白色 (
#fff
) 在画布左上角显示得分(Score: X
)。
- 用白色 (
6. 重置游戏
function resetGame() {
snake = [{ x: 10, y: 10 }];
direction = { x: 0, y: 0 };
score = 0;
}
- 当游戏结束(撞墙或撞自己)时:
- 重置蛇为初始状态(一节,位于 (10, 10))。
- 方向设为静止。
- 得分清零。
7. 键盘控制
window.addEventListener('keydown', e => {
switch (e.key) {
case 'ArrowUp':
if (direction.y === 0) direction = { x: 0, y: -1 };
break;
case 'ArrowDown':
if (direction.y === 0) direction = { x: 0, y: 1 };
break;
case 'ArrowLeft':
if (direction.x === 0) direction = { x: -1, y: 0 };
break;
case 'ArrowRight':
if (direction.x === 0) direction = { x: 1, y: 0 };
break;
}
});
- 监听键盘:
- 使用
keydown
事件捕获方向键输入。
- 使用
- 方向控制:
- 上键:
y: -1
(向上移动)。 - 下键:
y: 1
(向下移动)。 - 左键:
x: -1
(向左移动)。 - 右键:
x: 1
(向右移动)。
- 上键:
- 防止反向移动:
- 例如,如果蛇正在向右(
direction.x !== 0
),不能直接向左,if (direction.x === 0)
确保只在水平静止时允许左右移动,防止蛇直接掉头咬自己。
- 例如,如果蛇正在向右(
游戏流程总结
- 游戏开始,蛇位于网格中央,食物随机出现。
- 玩家用方向键控制蛇移动(每 100 毫秒更新一次)。
- 蛇吃到食物时,长度增加,得分加 1,食物重新生成。
- 如果蛇撞墙或撞自己,游戏重置(蛇回到初始位置,得分清零)。
- 画面实时绘制蛇、食物和得分。
代码特点
- 简单高效:代码逻辑清晰,适合初学者学习。
- 网格系统:用网格坐标简化了位置计算。
- 防作弊:防止蛇直接反向移动,增加了游戏合理性。