前言:写在前面的话
这个小案例是基于canvas
实现的,当然也可以用一些其他的html
标签来实现,但我觉得,用这个这个实现起来还是比较酷炫的。因为,这个最后做出来整个界面就是一张图片,所有的操作都是在操作这张图片中的像素,听起来,感觉是不是很牛叉。主要用到了这个canvas
的两个方法:一个是clearRect(x, y, w, h);
清除指定位置的矩形区域部分的像素,另一个是fillRect(x, y, w, h);
和第一方法相反这个是填充指定位置的矩形部分的像素。
下面放一张效果图:这是做了核心的一小部分,其它像什么关卡啊什么的都没做。
第一部分:贪吃蛇的核心类
话不多说上代码
该类的主要属性:
this.box = box;
// 存放canvas
的容器this.w = w;
// 宽度this.h = h;
// 高度this.headColor = headColor;
// 小蛇头的颜色this.bodyColor = bodyColor;
// 小蛇身体的颜色this.preyColor = preyColor;
// 猎物的颜色this.cellNum = cellNum;
// 小室 数量this.cell = this.w / this.cellNum;
// 小室this.ctx = canvas.getContext("2d");
// canvas ctxthis.snakeInitLen = 7;
// 初始小蛇长度this.snake = [];
// 初试小蛇数组this.timeId = null;
// 定时器idthis.speed = speed;
// 速度this.moveDirection = null;
// 初始移动方向this.preyPoint = null;
// 猎物坐标this.isEnd = false;
// 游戏是否结束
该类的主要方法:
17. createBgPic
: 创建一个背景图
18. createOneRandomPoint
: 获得一个随机的坐标,这个坐标不会和贪吃蛇身上的坐标重复
19. createDirectionPoint
: 根据上一个点和给定的方向,生成一个点的坐标
20. createOneContinuousPoint
: 获取一个随机的连续的方向点
21. paintSnake
: 根据坐标画出小蛇
22. createOneRandomSnake
: 创建一条随机的小蛇,主要分为两步:第一步:以获得给定长度this.snakeInitLen
的随机连续坐标,第二步:调用createOneRandomSnake
方法画出小蛇
23. createOneRandomPrey
: 创建随机的猎物,也是一个随机的点坐标,调用的是createOneRandomPoint
24. move
: 实现小蛇的移动,分为两步:第一步:初始化随机的移动方向initMoveDirection
。第二步:设置定时器,根据给定的速度this.speed
设置定时器setInterval(this.moveOneStep, speed)
25. initMoveDirection
: 初始化移动方向
26. moveOneStep
: 实现移动一步的方法,这里面主要实现了:
1. 根据上一个点和方向产生点
2. 碰撞检测
3. 将产生的点插入头部
4. 删除尾部的点,很像一个队列
5. 捕获猎物,就是一个入队操作,但也有其他一些细节需要注意
> 所谓移动,应该就是删除尾部元素,然后在头部插入带有方向的新元素
controlDirection
: 实现方向控制,也就是监听鼠标事件,上下左右键的按下,这里采用js
的addEventListener
方法,绑定事件给window
对象collisionDection
: 碰撞检测,主要分为两个部分:① 边界检测;②触碰身体检测clearSnakeAndPrey
: 清除小蛇和猎物的像素块,并且清空对象里面sanke
小蛇数组,和置空preyPoint
猎物位置init
: 初始化:①绘制地图createBgPic
②绘制小蛇createOneRandomSnake
③绘制猎物createOneRandomPrey
④注册键盘事件,实现控制controlDirection
⑤移动move
run
: 运行,这个方法可以重复的调用,即:游戏结束重新开始游戏(清空小蛇和猎物的像素块),然后开始移动move
.
第二部分:原码
utils.js
/** 贪吃蛇 **/
function Snake(box, w, h, cellNum, headColor, bodyColor, preyColor, speed) {
if (!box) {
box = document.createElement("div");
document.body.appendChild(box);
}
if (!w) w = 500;
if (!h) h = 500;
if (!cellNum) cellNum = 20;
if (!headColor) headColor = "red";
if (!bodyColor) bodyColor = "green";
if (!preyColor) preyColor = "gold";
if (!speed) speed = 500;
var canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
box.appendChild(canvas);
// 容器
this.box = box;
// 宽度
this.w = w;
// 高度
this.h = h;
// 小蛇头的颜色
this.headColor = headColor;
// 小蛇身体的颜色
this.bodyColor = bodyColor;
// 猎物的颜色
this.preyColor = preyColor;
// 小室 数量
this.cellNum = cellNum;
// 小室
this.cell = this.w / this.cellNum;
// canvas ctx
this.ctx = canvas.getContext("2d");
// 初始小蛇长度
this.snakeInitLen = 7;
// 初试小蛇数组
this.snake = [];
// 定时器id
this.timeId = null;
// 速度
this.speed = speed;
// 初始移动方向
this.moveDirection = null;
// 猎物坐标
this.preyPoint = null;
// 游戏是否结束
this.isEnd = false;
// 创建 背景图
this.createBgPic = function () {
for (var i = 0; i <= this.cellNum; i++) {
// 画横线
this.ctx.beginPath();
this.ctx.moveTo(0, i * this.cell);
this.ctx.lineTo(this.w, i * this.cell);
this.ctx.stroke();
// 画竖线
this.ctx.beginPath();
this.ctx.moveTo(i * this.cell, 0);
this.ctx.lineTo(i * this.cell, this.h);
this.ctx.stroke();
}
};
// 获取一个随机的坐标
this.createOneRandomPoint = function () {
/**
* 这个随机的坐标,不会和小蛇的坐标重复
*/
var point, len, flag = true;
while (1) {
point = [parseInt(Math.random() * this.cellNum), parseInt(Math.random() * this.cellNum)];
len = this.snake.length;
for (var i = 0; i < len; i++) {
if (point[0] == this.snake[i][0] && point[1] == this.snake[i][1]) {
flag = false; // 标志随机坐标产生失败,需要重新获取
break;
}
}
if (flag) break;
}
return point;
};
// 根据上一个点和给定的方向,生成一个点的坐标
this.createDirectionPoint = function (lastPoint, direction) {
var point;
switch (direction) {
case 0:
point = [lastPoint[0] + 1, lastPoint[1]];
break;
case 1:
point = [lastPoint[0], lastPoint[1] - 1];
break;
case 2:
point = [lastPoint[0] - 1, lastPoint[1]];
break;
case 3:
point = [lastPoint[0], lastPoint[1] + 1];
break;
}
return point;
}
// 获取一个随机的连续的方向点
this.createOneContinuousPoint = function (lastDirection) {
/**
* 目前,初始化小蛇长度如果高于4个会有问题。有待解决。。。
*/
// 0, 1, 2, 3 // 0 - 2, 1 - 3 返回不会回头的方向
var point = null, direction;
var lastPoint = this.snake[this.snake.length - 1];
while (1) {
// 产生不后退的方向
while (1) {
direction = parseInt(Math.random() * 4);
if (Math.abs(lastDirection - direction) != 2) {
// console.log(lastDirection, direction);
break;
}
}
point = this.createDirectionPoint(lastPoint, direction);
// 判断方向是否越界
if (point[0] > 0 && point[0] < this.cellNum && point[1] > 0 && point[1] < this.cellNum)
return [direction, point];
}
};
// 根据坐标画出小蛇
this.paintSnake = function () {
// 画头
this.ctx.fillStyle = this.headColor;
this.ctx.clearRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
this.ctx.fillRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
// 画身体
this.ctx.fillStyle = this.bodyColor;
for (var i = 1; i < this.snakeInitLen; i++) {
this.ctx.clearRect(this.snake[i][0] * this.cell + 1, this.snake[i][1] * this.cell + 1, this.cell - 2, this.cell - 2);
this.ctx.fillRect(this.snake[i][0] * this.cell + 1, this.snake[i][1] * this.cell + 1, this.cell - 2, this.cell - 2);
}
};
// 创建随机的小蛇
this.createOneRandomSnake = function () {
// ------------------ 创建随机相连坐标 ------------------
// 1. 获得初试坐标点
var initPoint = this.createOneRandomPoint();
this.snake.push(initPoint);
// 2. 根据方向产生后继的点
var directionAndPoint = this.createOneContinuousPoint(-1);
this.snake.push(directionAndPoint[1]);
for (var i = 0; i < this.snakeInitLen - 2; i++) {
// 将上一次的点传入 产生不后退的方向
directionAndPoint = this.createOneContinuousPoint(directionAndPoint[0]);
this.snake.push(directionAndPoint[1]);
}
// ------------------ 根据坐标画出小蛇 ------------------
this.paintSnake();
};
// 产生随机的猎物
this.createOneRandomPrey = function () {
var point = this.createOneRandomPoint();
this.ctx.fillStyle = this.preyColor;
this.ctx.clearRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
this.ctx.fillRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
this.preyPoint = point;
};
// 实现小蛇的移动
this.move = function () {
if (this.timeId) { console.log("请等待当前定时完成清除,在点击"); return; }
// 初始化移动方向
this.moveDirection = this.initMoveDirection();
// 设置定时器
this.timeId = setInterval(this.moveOneStep, speed);
this.isEnd = false;
};
// 初始化移动方向
this.initMoveDirection = function(){
// 如果按照这个方向移动,下一步会不会发生碰撞
var direction, point;
while(1){
direction = parseInt(Math.random() * 4);
point = this.createDirectionPoint(this.snake[0], direction);
if(!this.collisionDection(point))
return direction;
}
}
// 实现小蛇的一步移动
this.moveOneStep = () => {
/** 所谓移动,应该就是删除尾部元素,然后在头部插入带有方向的新元素 */
// 1. 根据点和方向产生点
var point = this.createDirectionPoint(this.snake[0], this.moveDirection);
// 碰撞检测
if (this.collisionDection(point)) {
clearInterval(this.timeId);
this.timeId = null;
this.isEnd = true;
console.log("游戏结束");
alert("游戏结束:得分("+ (this.snake.length-this.snakeInitLen) +")");
}
// 2. 将新的点,插入的头部
this.ctx.fillStyle = bodyColor; // 清除旧的头部色块
this.ctx.fillRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
this.snake.unshift(point);
this.ctx.fillStyle = headColor;
this.ctx.fillRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
// 3. 删除尾部元素
var delPoint = this.snake.pop();
this.ctx.clearRect(delPoint[0] * this.cell + 1, delPoint[1] * this.cell + 1, this.cell - 2, this.cell - 2);
// 4. 捕获猎物
if (point[0] == this.preyPoint[0] && point[1] == this.preyPoint[1]) {
this.ctx.fillStyle = bodyColor; // 清除旧的头部色块
this.ctx.fillRect(this.snake[0][0] * this.cell + 1, this.snake[0][1] * this.cell + 1, this.cell - 2, this.cell - 2);
this.snake.unshift(point);
this.ctx.fillStyle = headColor;
this.ctx.fillRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
console.log("吃了");
// 创建新的随机猎物
this.createOneRandomPrey();
}
console.log(this.snake)
};
// 控制方向
this.controlDirection = function () {
window.addEventListener("keydown", (ev) => {
switch (ev.key) {
case "ArrowRight":
this.moveDirection = 0;
break;
case "ArrowUp":
this.moveDirection = 1;
break;
case "ArrowLeft":
this.moveDirection = 2;
break;
case "ArrowDown":
this.moveDirection = 3;
break;
}
});
};
// 碰撞检测
this.collisionDection = function (point) {
// 边界检测
if (!(point[0] > 0 && point[0] < this.cellNum && point[1] > 0 && point[1] < this.cellNum)) return true;
// 身体检测
var len = this.snake.length;
for (var i = 0; i < len; i++) {
if (point[0] == this.snake[i][0] && point[1] == this.snake[i][1]) {
return true;
}
}
return false;
};
// 清除小蛇和猎物
this.clearSnakeAndPrey = function () {
var point;
while (this.snake.length) {
point = this.snake.pop();
this.ctx.clearRect(point[0] * this.cell + 1, point[1] * this.cell + 1, this.cell - 2, this.cell - 2);
}
this.ctx.clearRect(this.preyPoint[0] * this.cell + 1, this.preyPoint[1] * this.cell + 1, this.cell - 2, this.cell - 2);
this.preyPoint = null;
};
// 初始化
this.init = function () {
// 创建地图
this.createBgPic();
// 创建随机小蛇
this.createOneRandomSnake();
// 产生随机猎物
this.createOneRandomPrey();
// 移动
this.move();
// 控制方向
this.controlDirection();
}
// 运行
this.run = function () {
if (this.isEnd) {
console.log("重新开始");
if (this.timeId) clearInterval(this.timeId);
this.timeId = null;
// 清除上次的小蛇和猎物
this.clearSnakeAndPrey();
// 创建随机小蛇
this.createOneRandomSnake();
// 产生随机猎物
this.createOneRandomPrey();
}
// 移动
snake.move();
}
}
贪吃蛇.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>03-贪吃蛇</title>
<style>
* {
margin: 0;
padding: 0;
}
#btn {
width: 100px;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 24px;
margin: 0 auto;
display: block;
}
.container {
width: 100%;
margin: 0 auto;
}
.box {
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
<div class="box"></div>
<input id="btn" type="button" value="开始" onclick="btnClick()">
</div>
<!-- 引入外部文件 -->
<script src="../utils.js"></script>
<!-- -->
<script>
var box = document.getElementsByClassName("box")[0];
var snake = new Snake(box);
snake.init();
function btnClick() {
snake.run();
}
</script>
</body>
</html>