简介
一个使用原生 HTML5 Canvas 实现的简洁贪吃蛇小游戏。无需依赖外部库,直接打开即可游玩。
演示
功能特性
- 方向键控制:上/下/左/右(禁止直接掉头)
- 食物随机生成,吃到后变长
- 碰撞判定:撞墙或撞到自身即 Game Over
- 基于 20px 网格移动,蛇头为红色、身体为灰色、食物为绿色
- 默认画布大小:
800 x 500,默认速度:100ms/步
快速开始
方式一:直接打开
- 双击或用浏览器打开
index.html即可游玩。
方式二:本地静态服务器(推荐)
为避免个别浏览器的本地文件策略限制,建议用本地静态服务器打开:
- 使用 Python(macOS 自带 Python3):
# 在项目根目录下(包含 index.html)
python3 -m http.server 8000
# 然后在浏览器访问:http://localhost:8000
- 使用 Node(npx serve):
# 如未安装,可直接使用 npx
npx serve -p 8000
# 然后在浏览器访问:http://localhost:8000
操作说明
- 方向键 ← ↑ → ↓ 控制移动方向
- 不能直接掉头(例如向右时不能立即向左)
- 如按键无效,请点击画面使页面获得焦点
项目结构
./
├─ index.html # 游戏主页面与脚本(内联)
主要实现思路
Rect:方块对象,负责在画布上绘制基础单元Snake:- 使用
snakeArray维护蛇的每一节(方块) head指向蛇头(红色)direction保存当前方向(键值:37/38/39/40)move():- 在蛇头位置插入新节,吃到食物则不移除尾节(变长),否则移除尾节(等长)
- 根据方向移动蛇头一个网格
- 撞墙或撞自身即结束(清理定时器)
- 使用
getRandomFood():生成不与蛇身重叠的随机食物位置isEat():判断蛇头与食物坐标是否重合
自定义与扩展
- 画布尺寸:修改
index.html中<canvas id="canvas" width="800" height="500"> - 移动速度:调整
setInterval(..., 100)的间隔(单位 ms) - 网格大小:
Rect的w/h(默认 20)以及移动步长一致 - 初始长度与颜色:在
Snake构造函数中修改初始化循环与颜色
浏览器支持
- 现代浏览器均支持(需要 HTML5 Canvas)
- 如果出现画面未居中,可根据需要调整内联 CSS 中
#canvas的定位样式
开发小贴士
- 建议在本地静态服务器环境下调试,便于控制缓存与资源加载策略
- 如需模块化或进一步重构,可将脚本从
index.html中抽离为独立的*.js文件
源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1"> -->
<!-- 上述3个meta标签必须放在最前面,任何其他内容都必须跟随其后! -->
<meta name="renderer" content="webkit">
<title>贪吃蛇</title>
<style>
#canvas{
box-shadow: 0 5px 40px black;
position: absolute;
top: 50%;
left: 50%;
margin-top: -250px;
margin-left: -400px;
}
</style>
</head>
<body>
<canvas id="canvas" width="800" height="500">
Your browser does not support the Canvas API.
Please upgrade your browser.
</canvas>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
//构造对象方块
function Rect(x, y, w, h, color) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
//画方块的方法
Rect.prototype.draw = function() {
context.beginPath();
context.fillStyle = this.color;
context.rect(this.x, this.y, this.w, this.h);
context.fill();
context.stroke();
}
//构造对象蛇
function Snake() {
//定义一个空数组存放组成整蛇的方块对象
var snakeArray = [];
//画出4个方块,设置成灰色
for (var i = 0; i < 4; i++) {
var rect = new Rect(i * 20, 0, 20, 20, "gray");
//之所以用splice(往前加)而不是用push(往后加),是为了让蛇头出现在数组第一个位置
snakeArray.splice(0, 0, rect);
}
//把数组第一个作为蛇头,蛇头设成红色
var head = snakeArray[0];
head.color = "red";
//此处将两个后面常用的东西定为属性,方便后面调用
this.head = snakeArray[0]; //蛇头
this.snakeArray = snakeArray; //整蛇数组
//给定初始位置向右(同keyCode右箭头)
this.direction = 39;
}
//画蛇的方法
Snake.prototype.draw = function() {
for (var i = 0; i < this.snakeArray.length; i++) {
this.snakeArray[i].draw();
}
}
//蛇移动的方法
Snake.prototype.move = function() {
//此处是核心部分,蛇的 移动方式
//1、画一个灰色的方块,位置与蛇头重叠
//2、将这个方块插到数组中蛇头后面一个的位置
//3、砍去末尾的方块
//4、将蛇头向设定方向移动一格
var rect = new Rect(this.head.x, this.head.y, this.head.w, this.head.h, "gray");
this.snakeArray.splice(1, 0, rect);
//判断是否吃到食物,isEat判定函数写在最后了
//吃到则食物重新给位置,不砍去最后一节,即蛇变长
//没吃到则末尾砍掉一节,即蛇长度不变
if (isEat()) {
food = new getRandomFood();
} else {
this.snakeArray.pop();
}
//设置蛇头的运动方向,37 左,38 上,39 右,40 下
switch (this.direction) {
case 37:
this.head.x -= this.head.w
break;
case 38:
this.head.y -= this.head.h
break;
case 39:
this.head.x += this.head.w
break;
case 40:
this.head.y += this.head.h
break;
default:
break;
}
// gameover判定
// 撞墙
if (this.head.x >= canvas.width || this.head.x < 0 || this.head.y >= canvas.height || this.head.y < 0) {
clearInterval(timer);
}
// 撞自己,循环从1开始,避开蛇头与蛇头比较的情况
for (var i = 1; i < this.snakeArray.length; i++) {
if (this.snakeArray[i].x == this.head.x && this.snakeArray[i].y == this.head.y) {
clearInterval(timer);
}
}
}
//画出初始的蛇
var snake = new Snake()
snake.draw();
//画出初始的食物
var food = new getRandomFood()
//定时器
var timer = setInterval(function() {
context.clearRect(0, 0, canvas.width, canvas.height);
food.draw();
snake.move();
snake.draw();
}, 100)
//键盘事件,其中的if判定是为了让蛇不能直接掉头
document.onkeydown = function(e) {
var ev = e || window.event;
switch (ev.keyCode) {
case 37:
{
if (snake.direction !== 39) {
snake.direction = 37;
}
break;
}
case 38:
{
if (snake.direction !== 40) {
snake.direction = 38;
}
break;
}
case 39:
{
if (snake.direction !== 37) {
snake.direction = 39;
}
break;
}
case 40:
{
if (snake.direction !== 38) {
snake.direction = 40;
}
break;
}
}
ev.preventDefault();
}
//随机函数,获得[min,max]范围的值
function getNumberInRange(min, max) {
var range = max - min;
var r = Math.random();
return Math.round(r * range + min)
}
//构建食物对象
function getRandomFood() {
//判定食物是否出现在蛇身上,如果是重合,则重新生成一遍
var isOnSnake = true;
//设置食物出现的随机位置
while (isOnSnake) {
//执行后先将判定条件设置为false,如果判定不重合,则不会再执行下列语句
isOnSnake = false;
var indexX = getNumberInRange(0, canvas.width / 20 - 1);
var indexY = getNumberInRange(0, canvas.height / 20 - 1);
var rect = new Rect(indexX * 20, indexY * 20, 20, 20, "green");
for (var i = 0; i < snake.snakeArray.length; i++) {
if (snake.snakeArray[i].x == rect.x && snake.snakeArray[i].y == rect.y) {
//如果判定重合,将其设置为true,使随机数重给
isOnSnake = true;
break;
}
}
}
//返回rect,使得实例化对象food有draw的方法
return rect;
}
//判定吃到食物,即蛇头坐标与食物坐标重合
function isEat() {
if (snake.head.x == food.x && snake.head.y == food.y) {
return true;
} else {
return false;
}
}
</script>
</body>
</html>
正文完

