目录
1. 贪吃蛇项目
我们决定使用面向对象的方式来书写该游戏
因为面向对象是最适合书写游戏的
我们将这个游戏看成是一个类(Game类)
既然把游戏看成是一个类,它可以拥有属性和方法
有一个地图属性
有一个蛇属性
有一个食物属性
有一个障碍物属性
2. 我们可以把地图看成一个类
有一个行属性 有一个列属性
有宽度属性(总宽度) 有高度属性(总高度)
有一个数组属性是一个二维数组,里面存储的都是一个小方格元素
我们可以把蛇也看成是一个类
有一个数组属性,存储蛇的每一节身体
有一个方向属性,用于控制蛇的方向
蛇增长的方法
蛇转向的方法
3. 我们可以把食物看成一个类
有一个x属性
有一个y属性
有一个图片属性
我们可以把障碍物看成一个类
数组属性
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hungry Snake</title>
<style>
.box {
margin: 50px auto;
border: 1px solid #ccc;
}
.row {
display: flex;
}
.col {
border: 1px solid #ccc;
flex: 1;
background-size: cover;
}
</style>
</head>
<body>
<script src="./js/Block.js"></script>
<script src="./js/Food.js"></script>
<script src="./js/Map.js"></script>
<script src="./js/Snake.js"></script>
<script src="./js/Game.js"></script>
<script>
//实例化各个类
var block = new Block('./img/block.jpg');
//行的数量、列的数量、总宽、总高
// var map = new Map(30, 300);
var map = new Map(10, 10, 300, 300);
// var map = new Map(20, 20, 600, 600);
var food = new Food(6, 2, './img/food.jpg');
var Snake = new Snake({
head: ['./img/1.jpg', './img/2.jpg', './img/3.jpg', './img/4.jpg'],
body: ['./img/5.jpg'],
tail: ['./img/8.jpg', './img/9.jpg', './img/6.jpg', './img/7.jpg']
});
//将上面四个属性传递给Game类
var game = new Game(Snake, map, food, block, 500);
</script>
</body>
</html>
Game.js
//游戏类
//1.负责渲染 2. 游戏交互(判断游戏结束)
function Game(snake, map, food, block, time) {
//存储属性
this.snake = snake;
this.map = map;
this.food = food;
this.block = block;
//获取句柄
this.timebar = null;
//循环的时间
this.time = time || 1000;
//游戏是否可以正常进行
this.start = true;
//初始化
this.init();
}
//实现初始化方法
Game.prototype.init = function () {
//将地图初始化
// this.map.init();
this.renderMap();
//渲染食物
this.renderFood();
//绘制障碍物
this.renderBlock();
//绘制蛇
this.renderSnake();
//启动游戏
this.state();
//绑定事件
this.bindEvent();
}
//渲染地图
Game.prototype.renderMap = function () {
this.map.init();
}
//渲染食物
Game.prototype.renderFood = function () {
//根据食物的横纵坐标,在地图中找到对应的元素,设置其背景图片
this.map.arr[this.food.y][this.food.x].style.backgroundImage = 'url(' + this.food.img + ')';
// console.log(this.map, this.food, this.food.y)
// console.log(this.map.arr[this.food.y][this.food.x]);
}
//渲染障碍物
Game.prototype.renderBlock = function () {
// console.log(Game.prototype);
//遍历障碍物数组成员,将其一一绘制出来
for (var i = 0, len = this.block.arr.length; i < len; i++) {
//将其渲染//y对应的是行,x:列
//缓存障碍物
var item = this.block.arr[i];
this.map.arr[item.y][item.x].style.backgroundImage = 'url(' + this.block.img + ')'
}
}
//渲染蛇
Game.prototype.renderSnake = function () {
//特殊绘制,头 尾
var head = this.snake.arr[0];
var tail = this.snake.arr[this.snake.arr.length - 1];
//绘制头
this.map.arr[head.y][head.x].style.backgroundImage = 'url(' + this.snake.headImge + ')';
//绘制身体,从第二张绘制到倒数第二张
for (var i = 1, len = this.snake.arr.length - 1; i < len; i++) {
//缓存身体元素
var body = this.snake.arr[i];
//绘制身体
this.map.arr[body.y][body.x].style.backgroundImage = 'url(' + this.snake.bodyImge + ')';
}
//绘制尾部
this.map.arr[tail.y][tail.x].style.backgroundImage = 'url(' + this.snake.tailImge + ')';
}
//清空地图
Game.prototype.clear = function () {
//通过地图对象清空
this.map.clear()
}
//启动游戏
Game.prototype.state = function () {
//缓存thi;s
var me = this;
//启动定时器
this.timebar = setInterval(function () {
//开始移动蛇
me.snake.move();
//蛇移动之后,开始判断边界
me.checkMap();
//检测碰撞障碍物
me.checkBlock();
//检测是否碰到身体
me.checkSnake();
//检测是否吃到食物
me.checkFood();
//如果游戏结束,停止绘制游戏
//如果游戏正常运行,正常绘制游戏
if (me.state) {
//清空后再渲染
me.clear();
//渲染墙和食物
me.renderBlock();
me.renderFood();
//重新渲染
me.renderSnake();
}
}, this.time)
}
//绑定事件,控制蛇的移动
Game.prototype.bindEvent = function () {
//缓存this
var me = this;
//监听键盘事件
document.onkeydown = function (e) {
// console.log(e.keyCode);
//改变运行方向
me.snake.change(e.keyCode);
}
}
//游戏结束
Game.prototype.gameOver = function () {
//终止游戏,停止移动循环
clearInterval(this.timebar);
//游戏结束
this.state = false;
//提示用户GG
alert('Victory QAQ ' + '您的分数是:' + this.snake.arr.length);
// console.log('Victory', '您的分数:' + this.snake.arr.length);
}
//检测边界
Game.prototype.checkMap = function () {
//判断蛇的头部,及位置
var head = this.snake.arr[0];
//般的是否超出边界
if (head.x < 0 || head.y < 0 || head.x >= this.map.col || head.y >= this.map.row) {
this.gameOver();
}
// console.log(head.x < 0 || head.y < 0 || head.x > this.map.col || head.y > this.map.row);
}
//检测是否碰撞障碍物
Game.prototype.checkBlock = function () {
//判断头部位置
var head = this.snake.arr[0];
//遍历障碍物
for (var i = 0; i < this.block.arr.length; i++) {
//比较头部与障碍物是否在同一个位置
//获取障碍物位置
var block = this.block.arr[i];
//开始比较(即:x,y相同)
if (block.x === head.x && block.y === head.y) {
this.gameOver();
//打断执行//经测试好像可以省略
return;
}
}
}
//这是两个对象,对两个对象的判断,需要放到Game里面判断
//蛇是否吃的食物
Game.prototype.checkFood = function () {
//蛇头的位置与食物的位置 是否相等
if (this.snake.arr[0].x === this.food.x && this.snake.arr[0].y === this.food.y) {
//恰到饭了,蛇变长,增加身体长度(个数)
//这是一个对象(蛇)的变化,放到蛇的对象内描写
this.snake.growUp();
//重置食物
this.resetFood();
}
}
//重置食物
Game.prototype.resetFood = function () {
//随机一个食物的位置
let x = parseInt(Math.random() * this.map.col);
let y = parseInt(Math.random() * this.map.row);
//判断坐标是否在:1.食物不能出现在墙体中;2.食物不能出现在蛇的身体上
for (var i = 0; i < this.block.arr.length; i++) {
//1.食物不能出现在墙体中 //缓存一个障碍物
let block = this.block.arr[i];
//判断坐标是否重合
if (block.x === x && block.y === y) {
//在墙体中,所以要重新随机食物
this.resetFood();
//中断执行
return;
}
}
//2.食物不能出现在蛇的身体上
for (var i = 0; i < this.snake.arr.length; i++) {
//缓存蛇的部位
let snake = this.snake.arr[i];
//判断坐标是否重合
if (snake.x === x && snake.y === y) {
//在墙体中,所以要重新随机食物
this.resetFood();
//中断执行
return;
}
}
//更改食物位置
//要对食物内部的数据进行修改,所以放到Food.js文件中
this.food.reset(x, y);
console.log(x, y);
}
//碰撞身体
Game.prototype.checkSnake = function () {
//获取头部
var head = this.snake.arr[0];
//从第二个开始遍历身体
for (var i = 1; i < this.snake.arr.length; i++) {
// //缓存身体
// var body = this.snake.arr[i];
//获取身体部件位置
var body = this.snake.arr[i];
if (head.x === body.x && head.y === body.y) {
this.gameOver();
//打断执行//经测试好像可以省略
return;
}
}
}
// //获取键盘四个箭头的键码
// window.onkeydown = function (e) {
// console.log(e.keyCode);
// }
Snake.js
//蛇的类
function Snake(img) {
//存储坐标
this.arr = [
// { x: 7, y: 4 },
{ x: 6, y: 4 },
{ x: 5, y: 4 },
{ x: 4, y: 4 },
// { x: 3, y: 4 },
// { x: 2, y: 4 },
];
//存储图片
this.img = img;
// this.headImage = img.head;
// this.bodyImage = img.body;
// this.tailImage = img.tail;
//蛇的方向//左:37, 上:38, 右:39, 下:40
//根据方向确定头部图片(direction-37).jpg就是数组中,图片的序号
this.direction = 39;//为什么要等于39啊啊啊啊啊
this.headImge = this.img.head[this.direction - 37];
this.bodyImge = this.img.body;
this.tailImge = this.img.tail[this.direction - 37];
// console.log(this.direction);
}
//移动蛇
Snake.prototype.move = function () {
//获取新的头部位置
// var head = this.arr[0];//不能直接引用
var head = { x: this.arr[0].x, y: this.arr[0].y };
//👆:赋值了一个新的头部//switch是引用类型的,新的头部和原来的头部不能引用同一个对象
//判断方向
switch (this.direction) {
//向左移动,x-
case 37:
head.x -= 1;
break;
case 38:
//向上移动:y-1
head.y -= 1;
break;
case 39:
//向右移动:x+1
head.x += 1;
break;
case 40:
//向下移动:y+1
head.y += 1;
break;
default:
break;
}
//删除尾部,更新头部
this.arr.pop();
this.arr.unshift(head);
//尾部的图片,始终是受到倒数二个元素影响
var tail = this.arr[this.arr.length - 1];
var second = this.arr[this.arr.length - 2];
//判断,tail与second的位置关系(x与y比较),并改变尾部图片方向
if (tail.x === second.x) {
//比较垂直方向的
if (tail.y > second.y) {
//向上移动
this.tailImge = this.img.tail[1];
} else {
//说明向下移动
this.tailImge = this.img.tail[3];
}
} else {
//比较水平方向的
if (tail.x > second.x) {
//向左移动
this.tailImge = this.img.tail[0];
} else {
//向右移动
this.tailImge = this.img.tail[2];
}
}
this.lock = false;
}
//改变方向
Snake.prototype.change = function (code) {
//如果锁住了,不能改变方向
//设置节流锁:的目的,确保每一步都按时按量执行,不会插队(覆盖)别的指令
//阻止了一个bug:先按一个方向的键,然后迅速按下垂直方向的键,
//会导致直接指向后按下的键及其指令,且会覆盖前面的指令与限制条件(不能同方向回头)
if (this.lock) {
return;
}
//锁住
this.lock = true;
//改变方向
//向左右移动只能上下改变移动方向,//向上下移动只能左右改变移动方向
//获取方向键值之差
var num = Math.abs(code - this.direction);
//同向或相反方向不能改变方向
if (num === 0 || num === 2) {
return;
}
//可以改变方向
this.direction = code;
//更改头部图片方向
this.headImge = this.img.head[this.direction - 37];
this.bodyImge = this.img.body;
this.tailImge = this.img.tail[this.direction - 37];
}
//让蛇成长
Snake.prototype.growUp = function () {
//获取尾部。蛇变长了
var tail = this.arr[this.arr.length - 1];
//尾部添加
this.arr.push(tail)
}
Block.js
//障碍物类
function Block(img) {
//障碍物绘制图片
this.img = img;
//障碍物数组
this.arr = [
//每一个成员代表一个障碍物,保存它的位置
{ x: 3, y: 6 },
{ x: 4, y: 6 },
{ x: 5, y: 6 },
{ x: 6, y: 6 },
{ x: 7, y: 6 },
{ x: 8, y: 6 }
]
}
Food.js
//食物类
function Food(x, y, img) {
this.x = x;
this.y = y;
this.img = img;
}
//重置位置
Food.prototype.reset = function (x, y) {
//更新食物坐标
this.x = x;
this.y = y;
}
Map.js
/**
* 地图类
* @row:行的数量 @col:列的数量
* @width:总宽度 @height:总高度
*
* **/
function Map(row, col, width, height) {
this.row = row;
this.col = col;
this.height = height;
this.width = width;
//二维数组
this.arr = [];
//定义容器元素
this.dom = document.createElement('div');
}
//初始化//绘制地图
Map.prototype.init = function () {
//遍历行列
for (var i = 0; i < this.row; i++) {
//创建行元素
var rowDom = document.createElement('div');
//设置类
rowDom.className = 'row';
rowDom.style.height = this.height / this.row + 'px';
//定义行数组
var rowArr = [];
//遍历改行的每列
for (var j = 0; j < this.col; j++) {
//创建每一个列元素
var colDom = document.createElement('div');
//设置类
colDom.className = 'col';
//将单元格放入行元素中
rowDom.appendChild(colDom);
//在数组中,存储单元格的映射
rowArr.push(colDom);
}
//将行元素渲染,并存储
this.dom.appendChild(rowDom);
//存储行数组
this.arr.push(rowArr);
}
//设置容器元素类
this.dom.className = 'box';
this.dom.style.width = this.width + 'px';
this.dom.style.height = this.height + 'px';
//上树
document.body.appendChild(this.dom)
}
//清空地图
Map.prototype.clear = function () {
//清空的本质就是删除每一个元素的背景
for (var i = this.arr.length - 1; i >= 0; i--) {
//遍历列
for (var j = this.arr.length - 1; j >= 0; j--) {
//清空背景
this.arr[i][j].style.backgroundImage = ''
}
}
}