从零开始用HTML、CSS和JavaScript制作贪吃蛇网页小游戏
〇、前言
贪吃蛇是一款经典的休闲游戏,在诺基亚手机时代风靡全球。
作为编程入门者,实现一个贪吃蛇游戏是学习Web前端技术的绝佳练习。
名人说:博观而约取,厚积而薄发。——苏轼《稼说送张琥》
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)目录
- 〇、前言
- 效果预览
- 一、HTML结构
- 二、CSS样式设计
- 三、JavaScript游戏逻辑
- 四、代码详解
- 1. 游戏初始化
- 2. 生成食物
- 3. 移动蛇
- 4. 碰撞检测
- 5. 游戏控制
- 五、完整代码
很高兴你打开了这篇博客,更多好用的软件工具,请关注我、订阅专栏《项目探索实验室》,内容持续更新中…
思维速览:
本文将详细讲解如何使用HTML、CSS和JavaScript从零开始创建一个功能完整的贪吃蛇网页游戏(更多功能可以根据个人开发需求拓展)。
我们的贪吃蛇游戏将包含以下功能:
- 基础的游戏逻辑(移动、吃食物、碰撞检测)
- 分数记录和最高分保存
- 游戏控制(开始、暂停、继续)
- 自适应界面设计(支持PC和移动设备)
- 逐步提高的游戏难度
效果预览
▍1️⃣静态展示:
▍2️⃣动态展示:
完成后的游戏效果如下:
- 一个20x20格子的游戏场地
- 绿色的蛇,红色的食物
- 顶部显示当前分数和历史最高分
- 底部有游戏控制按钮和移动设备专用的方向控制键
一、HTML结构
首先,创建基本的HTML结构:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇小游戏</title>
<!-- CSS将在这里添加 -->
</head>
<body>
<h1>贪吃蛇小游戏</h1>
<div class="game-container">
<div class="game-header">
<div>分数: <span id="score">0</span></div>
<div>最高分: <span id="high-score">0</span></div>
</div>
<div id="game-board"></div>
<div class="controls">
<button id="start-button">开始游戏</button>
<div class="mobile-controls">
<button class="up">↑</button>
<button class="left">←</button>
<button class="right">→</button>
<button class="down">↓</button>
</div>
</div>
</div>
<!-- JavaScript将在这里添加 -->
</body>
</html>
这个HTML结构包含了:
- 游戏标题
- 分数和最高分显示区域
- 游戏主画布(game-board)
- 游戏控制按钮
- 移动设备的方向控制按钮
二、CSS样式设计
接下来,我们需要添加CSS样式,使游戏看起来更加美观:
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
.game-container {
display: flex;
flex-direction: column;
align-items: center;
}
.game-header {
display: flex;
justify-content: space-between;
width: 400px;
margin-bottom: 10px;
}
#game-board {
width: 400px;
height: 400px;
border: 2px solid #333;
position: relative;
background-color: #eee;
}
.snake-part {
width: 20px;
height: 20px;
background-color: #4CAF50;
position: absolute;
border-radius: 2px;
}
.snake-head {
background-color: #388E3C;
}
.food {
width: 20px;
height: 20px;
background-color: #F44336;
position: absolute;
border-radius: 50%;
}
.controls {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
button {
padding: 10px 20px;
margin: 5px;
font-size: 16px;
cursor: pointer;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
}
button:hover {
background-color: #0b7dda;
}
.mobile-controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
gap: 5px;
margin-top: 15px;
max-width: 200px;
}
.mobile-controls button {
padding: 15px;
margin: 0;
}
.up {
grid-column: 2;
grid-row: 1;
}
.left {
grid-column: 1;
grid-row: 2;
}
.right {
grid-column: 3;
grid-row: 2;
}
.down {
grid-column: 2;
grid-row: 3;
}
@media (max-width: 500px) {
#game-board {
width: 300px;
height: 300px;
}
.game-header {
width: 300px;
}
}
</style>
这些CSS样式:
- 使用Flexbox和Grid布局
- 设计蛇和食物的外观
- 美化按钮和控件
- 添加响应式设计,适应不同屏幕尺寸
三、JavaScript游戏逻辑
最后,也是最重要的部分,我们需要实现游戏的核心逻辑:
<script>
document.addEventListener('DOMContentLoaded', () => {
// 游戏变量
const boardSize = 20; // 20x20 格子
const gridSize = 20; // 每个格子的大小(像素)
const board = document.getElementById('game-board');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('high-score');
const startButton = document.getElementById('start-button');
let snake = []; // 蛇的身体部分坐标
let food = null; // 食物坐标
let direction = 'right'; // 初始方向
let nextDirection = 'right';
let gameInterval = null;
let score = 0;
let highScore = localStorage.getItem('snakeHighScore') || 0;
let gameSpeed = 150; // 游戏速度,毫秒
let gameStarted = false;
let gamePaused = false;
highScoreElement.textContent = highScore;
// 初始化游戏
function initGame() {
clearBoard();
// 初始化蛇
snake = [
{x: 5, y: 10}, // 头部
{x: 4, y: 10},
{x: 3, y: 10}
];
// 重置游戏状态
direction = 'right';
nextDirection = 'right';
score = 0;
scoreElement.textContent = score;
// 生成第一个食物
generateFood();
// 渲染初始状态
renderSnake();
renderFood();
}
// 清空游戏板
function clearBoard() {
board.innerHTML = '';
}
// 生成食物
function generateFood() {
let foodPosition;
let onSnake;
do {
// 随机生成食物位置
foodPosition = {
x: Math.floor(Math.random() * boardSize),
y: Math.floor(Math.random() * boardSize)
};
// 检查食物是否与蛇重叠
onSnake = snake.some(part => part.x === foodPosition.x && part.y === foodPosition.y);
} while (onSnake);
food = foodPosition;
}
// 渲染蛇
function renderSnake() {
snake.forEach((part, index) => {
const snakePart = document.createElement('div');
snakePart.className = 'snake-part';
if (index === 0) {
snakePart.classList.add('snake-head');
}
snakePart.style.left = `${part.x * gridSize}px`;
snakePart.style.top = `${part.y * gridSize}px`;
board.appendChild(snakePart);
});
}
// 渲染食物
function renderFood() {
const foodElement = document.createElement('div');
foodElement.className = 'food';
foodElement.style.left = `${food.x * gridSize}px`;
foodElement.style.top = `${food.y * gridSize}px`;
board.appendChild(foodElement);
}
// 移动蛇
function moveSnake() {
// 更新方向
direction = nextDirection;
// 获取蛇头的当前位置
const head = {...snake[0]};
// 根据方向移动蛇头
switch (direction) {
case 'up':
head.y -= 1;
break;
case 'down':
head.y += 1;
break;
case 'left':
head.x -= 1;
break;
case 'right':
head.x += 1;
break;
}
// 检查碰撞
if (checkCollision(head)) {
gameOver();
return;
}
// 将新头部添加到蛇的开始
snake.unshift(head);
// 检查是否吃到食物
if (head.x === food.x && head.y === food.y) {
// 吃到食物,增加分数
score += 10;
scoreElement.textContent = score;
// 更新最高分
if (score > highScore) {
highScore = score;
highScoreElement.textContent = highScore;
localStorage.setItem('snakeHighScore', highScore);
}
// 生成新食物
generateFood();
// 增加游戏速度
if (gameSpeed > 50) {
gameSpeed -= 2;
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
}
} else {
// 没吃到食物,移除蛇尾
snake.pop();
}
// 重新渲染游戏
clearBoard();
renderSnake();
renderFood();
}
// 检查碰撞
function checkCollision(head) {
// 检查墙壁碰撞
if (head.x < 0 || head.x >= boardSize || head.y < 0 || head.y >= boardSize) {
return true;
}
// 检查自身碰撞
return snake.some((part, index) => {
// 跳过第一个元素,因为它就是头部
if (index === 0) return false;
return part.x === head.x && part.y === head.y;
});
}
// 游戏结束
function gameOver() {
clearInterval(gameInterval);
alert(`游戏结束! 你的得分: ${score}`);
gameStarted = false;
startButton.textContent = '开始游戏';
}
// 游戏循环
function gameLoop() {
moveSnake();
}
// 开始游戏
function startGame() {
if (gameStarted) {
// 如果游戏已经开始,暂停或继续
if (gamePaused) {
// 继续游戏
gameInterval = setInterval(gameLoop, gameSpeed);
startButton.textContent = '暂停游戏';
gamePaused = false;
} else {
// 暂停游戏
clearInterval(gameInterval);
startButton.textContent = '继续游戏';
gamePaused = true;
}
} else {
// 开始新游戏
initGame();
gameInterval = setInterval(gameLoop, gameSpeed);
gameStarted = true;
gamePaused = false;
startButton.textContent = '暂停游戏';
}
}
// 处理键盘输入
document.addEventListener('keydown', e => {
if (!gameStarted || gamePaused) return;
// 防止方向键引起页面滚动
if(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
e.preventDefault();
}
// 更新方向
switch (e.key) {
case 'ArrowUp':
if (direction !== 'down') nextDirection = 'up';
break;
case 'ArrowDown':
if (direction !== 'up') nextDirection = 'down';
break;
case 'ArrowLeft':
if (direction !== 'right') nextDirection = 'left';
break;
case 'ArrowRight':
if (direction !== 'left') nextDirection = 'right';
break;
}
});
// 移动设备控制按钮
document.querySelector('.up').addEventListener('click', () => {
if (direction !== 'down' && gameStarted && !gamePaused) nextDirection = 'up';
});
document.querySelector('.down').addEventListener('click', () => {
if (direction !== 'up' && gameStarted && !gamePaused) nextDirection = 'down';
});
document.querySelector('.left').addEventListener('click', () => {
if (direction !== 'right' && gameStarted && !gamePaused) nextDirection = 'left';
});
document.querySelector('.right').addEventListener('click', () => {
if (direction !== 'left' && gameStarted && !gamePaused) nextDirection = 'right';
});
// 开始游戏按钮
startButton.addEventListener('click', startGame);
// 初始化游戏
initGame();
});
</script>
四、代码详解
1. 游戏初始化
function initGame() {
clearBoard();
// 初始化蛇
snake = [
{x: 5, y: 10}, // 头部
{x: 4, y: 10},
{x: 3, y: 10}
];
// 重置游戏状态
direction = 'right';
nextDirection = 'right';
score = 0;
scoreElement.textContent = score;
// 生成第一个食物
generateFood();
// 渲染初始状态
renderSnake();
renderFood();
}
这个函数负责:
- 清空游戏板
- 创建初始长度为3的蛇
- 重置方向和分数
- 生成食物
- 渲染初始游戏状态
2. 生成食物
function generateFood() {
let foodPosition;
let onSnake;
do {
// 随机生成食物位置
foodPosition = {
x: Math.floor(Math.random() * boardSize),
y: Math.floor(Math.random() * boardSize)
};
// 检查食物是否与蛇重叠
onSnake = snake.some(part => part.x === foodPosition.x && part.y === foodPosition.y);
} while (onSnake);
food = foodPosition;
}
这个函数:
- 随机生成食物的位置
- 确保食物不会出现在蛇身上
- 使用do-while循环直到找到合适的位置
3. 移动蛇
function moveSnake() {
// 更新方向
direction = nextDirection;
// 获取蛇头的当前位置
const head = {...snake[0]};
// 根据方向移动蛇头
switch (direction) {
case 'up':
head.y -= 1;
break;
case 'down':
head.y += 1;
break;
case 'left':
head.x -= 1;
break;
case 'right':
head.x += 1;
break;
}
// 检查碰撞
if (checkCollision(head)) {
gameOver();
return;
}
// 将新头部添加到蛇的开始
snake.unshift(head);
// 检查是否吃到食物
if (head.x === food.x && head.y === food.y) {
// 吃到食物,增加分数
score += 10;
scoreElement.textContent = score;
// 更新最高分
if (score > highScore) {
highScore = score;
highScoreElement.textContent = highScore;
localStorage.setItem('snakeHighScore', highScore);
}
// 生成新食物
generateFood();
// 增加游戏速度
if (gameSpeed > 50) {
gameSpeed -= 2;
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
}
} else {
// 没吃到食物,移除蛇尾
snake.pop();
}
// 重新渲染游戏
clearBoard();
renderSnake();
renderFood();
}
这个函数是游戏的核心,它:
- 根据当前方向移动蛇头
- 检查是否发生碰撞
- 如果吃到食物,增加分数,生成新食物,加快游戏速度
- 如果没吃到食物,移除蛇尾(保持长度不变)
- 更新游戏界面
4. 碰撞检测
function checkCollision(head) {
// 检查墙壁碰撞
if (head.x < 0 || head.x >= boardSize || head.y < 0 || head.y >= boardSize) {
return true;
}
// 检查自身碰撞
return snake.some((part, index) => {
// 跳过第一个元素,因为它就是头部
if (index === 0) return false;
return part.x === head.x && part.y === head.y;
});
}
这个函数检查两种碰撞情况:
- 蛇头撞到墙壁(超出游戏边界)
- 蛇头撞到自己的身体
5. 游戏控制
function startGame() {
if (gameStarted) {
// 如果游戏已经开始,暂停或继续
if (gamePaused) {
// 继续游戏
gameInterval = setInterval(gameLoop, gameSpeed);
startButton.textContent = '暂停游戏';
gamePaused = false;
} else {
// 暂停游戏
clearInterval(gameInterval);
startButton.textContent = '继续游戏';
gamePaused = true;
}
} else {
// 开始新游戏
initGame();
gameInterval = setInterval(gameLoop, gameSpeed);
gameStarted = true;
gamePaused = false;
startButton.textContent = '暂停游戏';
}
}
这个函数处理游戏的控制逻辑:
- 开始新游戏
- 暂停正在进行的游戏
- 继续已暂停的游戏
五、完整代码
最后,我们将上面的HTML、CSS和JavaScript代码合并,得到完整的贪吃蛇游戏:
<!--创作者:Code_流苏(CSDN)-->
<!DOCTYPE html>
<html lang="zh">
<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;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
.game-container {
display: flex;
flex-direction: column;
align-items: center;
}
.game-header {
display: flex;
justify-content: space-between;
width: 400px;
margin-bottom: 10px;
}
#game-board {
width: 400px;
height: 400px;
border: 2px solid #333;
position: relative;
background-color: #eee;
}
.snake-part {
width: 20px;
height: 20px;
background-color: #4CAF50;
position: absolute;
border-radius: 2px;
}
.snake-head {
background-color: #388E3C;
}
.food {
width: 20px;
height: 20px;
background-color: #F44336;
position: absolute;
border-radius: 50%;
}
.controls {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
button {
padding: 10px 20px;
margin: 5px;
font-size: 16px;
cursor: pointer;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
}
button:hover {
background-color: #0b7dda;
}
.mobile-controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
gap: 5px;
margin-top: 15px;
max-width: 200px;
}
.mobile-controls button {
padding: 15px;
margin: 0;
}
.up {
grid-column: 2;
grid-row: 1;
}
.left {
grid-column: 1;
grid-row: 2;
}
.right {
grid-column: 3;
grid-row: 2;
}
.down {
grid-column: 2;
grid-row: 3;
}
@media (max-width: 500px) {
#game-board {
width: 300px;
height: 300px;
}
.game-header {
width: 300px;
}
}
</style>
</head>
<body>
<h1>贪吃蛇小游戏</h1>
<div class="game-container">
<div class="game-header">
<div>分数: <span id="score">0</span></div>
<div>最高分: <span id="high-score">0</span></div>
</div>
<div id="game-board"></div>
<div class="controls">
<button id="start-button">开始游戏</button>
<div class="mobile-controls">
<button class="up">↑</button>
<button class="left">←</button>
<button class="right">→</button>
<button class="down">↓</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 游戏变量
const boardSize = 20; // 20x20 格子
const gridSize = 20; // 每个格子的大小(像素)
const board = document.getElementById('game-board');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('high-score');
const startButton = document.getElementById('start-button');
let snake = []; // 蛇的身体部分坐标
let food = null; // 食物坐标
let direction = 'right'; // 初始方向
let nextDirection = 'right';
let gameInterval = null;
let score = 0;
let highScore = localStorage.getItem('snakeHighScore') || 0;
let gameSpeed = 150; // 游戏速度,毫秒
let gameStarted = false;
let gamePaused = false;
highScoreElement.textContent = highScore;
// 初始化游戏
function initGame() {
clearBoard();
// 初始化蛇
snake = [
{x: 5, y: 10}, // 头部
{x: 4, y: 10},
{x: 3, y: 10}
];
// 重置游戏状态
direction = 'right';
nextDirection = 'right';
score = 0;
scoreElement.textContent = score;
// 生成第一个食物
generateFood();
// 渲染初始状态
renderSnake();
renderFood();
}
// 清空游戏板
function clearBoard() {
board.innerHTML = '';
}
// 生成食物
function generateFood() {
let foodPosition;
let onSnake;
do {
// 随机生成食物位置
foodPosition = {
x: Math.floor(Math.random() * boardSize),
y: Math.floor(Math.random() * boardSize)
};
// 检查食物是否与蛇重叠
onSnake = snake.some(part => part.x === foodPosition.x && part.y === foodPosition.y);
} while (onSnake);
food = foodPosition;
}
// 渲染蛇
function renderSnake() {
snake.forEach((part, index) => {
const snakePart = document.createElement('div');
snakePart.className = 'snake-part';
if (index === 0) {
snakePart.classList.add('snake-head');
}
snakePart.style.left = `${part.x * gridSize}px`;
snakePart.style.top = `${part.y * gridSize}px`;
board.appendChild(snakePart);
});
}
// 渲染食物
function renderFood() {
const foodElement = document.createElement('div');
foodElement.className = 'food';
foodElement.style.left = `${food.x * gridSize}px`;
foodElement.style.top = `${food.y * gridSize}px`;
board.appendChild(foodElement);
}
// 移动蛇
function moveSnake() {
// 更新方向
direction = nextDirection;
// 获取蛇头的当前位置
const head = {...snake[0]};
// 根据方向移动蛇头
switch (direction) {
case 'up':
head.y -= 1;
break;
case 'down':
head.y += 1;
break;
case 'left':
head.x -= 1;
break;
case 'right':
head.x += 1;
break;
}
// 检查碰撞
if (checkCollision(head)) {
gameOver();
return;
}
// 将新头部添加到蛇的开始
snake.unshift(head);
// 检查是否吃到食物
if (head.x === food.x && head.y === food.y) {
// 吃到食物,增加分数
score += 10;
scoreElement.textContent = score;
// 更新最高分
if (score > highScore) {
highScore = score;
highScoreElement.textContent = highScore;
localStorage.setItem('snakeHighScore', highScore);
}
// 生成新食物
generateFood();
// 增加游戏速度
if (gameSpeed > 50) {
gameSpeed -= 2;
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
}
} else {
// 没吃到食物,移除蛇尾
snake.pop();
}
// 重新渲染游戏
clearBoard();
renderSnake();
renderFood();
}
// 检查碰撞
function checkCollision(head) {
// 检查墙壁碰撞
if (head.x < 0 || head.x >= boardSize || head.y < 0 || head.y >= boardSize) {
return true;
}
// 检查自身碰撞
return snake.some((part, index) => {
// 跳过第一个元素,因为它就是头部
if (index === 0) return false;
return part.x === head.x && part.y === head.y;
});
}
// 游戏结束
function gameOver() {
clearInterval(gameInterval);
alert(`游戏结束! 你的得分: ${score}`);
gameStarted = false;
startButton.textContent = '开始游戏';
}
// 游戏循环
function gameLoop() {
moveSnake();
}
// 开始游戏
function startGame() {
if (gameStarted) {
// 如果游戏已经开始,暂停或继续
if (gamePaused) {
// 继续游戏
gameInterval = setInterval(gameLoop, gameSpeed);
startButton.textContent = '暂停游戏';
gamePaused = false;
} else {
// 暂停游戏
clearInterval(gameInterval);
startButton.textContent = '继续游戏';
gamePaused = true;
}
} else {
// 开始新游戏
initGame();
gameInterval = setInterval(gameLoop, gameSpeed);
gameStarted = true;
gamePaused = false;
startButton.textContent = '暂停游戏';
}
}
// 处理键盘输入
document.addEventListener('keydown', e => {
if (!gameStarted || gamePaused) return;
// 防止方向键引起页面滚动
if(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
e.preventDefault();
}
// 更新方向
switch (e.key) {
case 'ArrowUp':
if (direction !== 'down') nextDirection = 'up';
break;
case 'ArrowDown':
if (direction !== 'up') nextDirection = 'down';
break;
case 'ArrowLeft':
if (direction !== 'right') nextDirection = 'left';
break;
case 'ArrowRight':
if (direction !== 'left') nextDirection = 'right';
break;
}
});
// 移动设备控制按钮
document.querySelector('.up').addEventListener('click', () => {
if (direction !== 'down' && gameStarted && !gamePaused) nextDirection = 'up';
});
document.querySelector('.down').addEventListener('click', () => {
if (direction !== 'up' && gameStarted && !gamePaused) nextDirection = 'down';
});
document.querySelector('.left').addEventListener('click', () => {
if (direction !== 'right' && gameStarted && !gamePaused) nextDirection = 'left';
});
document.querySelector('.right').addEventListener('click', () => {
if (direction !== 'left' && gameStarted && !gamePaused) nextDirection = 'right';
});
// 开始游戏按钮
startButton.addEventListener('click', startGame);
// 初始化游戏
initGame();
});
</script>
</body>
</html>
很感谢你能看到这里,如果你有哪些想学习的项目,欢迎在评论区分享!
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)