项目二:《贪吃蛇》
思路:
- 蛇分为两个部分,蛇头和蛇身体的形成
- 蛇的运动,通过键盘控制蛇的走向
- 游戏结束的条件,撞墙和吃到自己
- 食物的产生,注意食物产生的位置和蛇的位置
- 蛇吃到食物的过程,吃到后身体变长并产生新的食物
一、效果图
二、贪吃蛇功能分析
1、游戏区域
游戏画布由 30 行 30 列且宽高皆为 20 px 的格子组成,蛇和食物功能的实现实际上都是在这些格子中进行的。
var cw = 20, // 一个格子的宽
ch = 20, // 格子的高
tr = 30,
td = 30;
// 实例化
var snake = null,
food = null,
game = null;
// 格子的构造函数
function Cell(x, y, classname) {
this.x = x * cw;
this.y = y * ch;
this.class = classname;
// 创建并添加标签
this.viewContent = document.createElement('div');
this.viewContent.className = this.class;
this.parent = document.querySelector('.snakeBox');
}
// prototype 属性:为一个特定类声明通用的变量或者函数
Cell.prototype.create = function() { // 创建并添加格子 dom
this.viewContent.style.position = 'absolute';
this.viewContent.style.width = cw + 'px';
this.viewContent.style.height = ch + 'px';
this.viewContent.style.left = this.x + 'px';
this.viewContent.style.top = this.y + 'px';
this.parent.appendChild(this.viewContent); // 添加到页面
}
Cell.prototype.remove = function() { // 删除格子
this.parent.removeChild(this.viewContent);
}
2、开始/暂停游戏功能
用户点击页面中的 start 按钮后即可进行新游戏,点击页面即可暂停游戏,再点击出现的暂停按钮即可继续游戏。代码如下:
// 调用中介类 开始游戏
game = new Game();
var startBtn = document.querySelector('.startBtn button');
startBtn.addEventListener('click', function() {
startBtn.parentNode.style.display = 'none';
game.init(); // 初始化游戏
});
// 暂停游戏
var snakeBox = document.querySelector('.snakeBox');
var pauseBtn = document.querySelector('.pauseBtn button');
snakeBox.addEventListener('click', function() {
game.pause();
pauseBtn.parentNode.style.display = 'block';
});
pauseBtn.addEventListener('click', function() {
game.start();
pauseBtn.parentNode.style.display = 'none';
});
// 游戏开始
Game.prototype.start = function() {
this.timer = setInterval(function() {
snake.getNextPos();
}, 200);
}
// 游戏暂停
Game.prototype.pause = function() {
clearInterval(this.timer);
}
3、贪吃蛇
1.蛇的组成
蛇分为蛇头和蛇身两个部分,通过数组来存储蛇的位置,点击 start 按钮后,蛇的方向默认往右移动。
// 蛇的构造函数
function Snake() {
this.head = null; //蛇头
this.tail = null; // 蛇尾
this.pos = []; // 存储蛇身上的每一个格子的位置
this.directionNum = { // 存储用对象来表示的蛇头朝向
left: {
x: -1,
y: 0,
rotate: 180 // 通过旋转角度调整蛇头方向
},
right: {
x: 1,
y: 0,
rotate: 0
},
up: {
x: 0,
y: -1,
rotate: -90
},
down: {
x: 0,
y: 1,
rotate: 90
}
}
}
// 初始化蛇
Snake.prototype.init = function() {
// 创建蛇头
var snakeHead = new Cell(2, 0, 'snakeHead');
snakeHead.create();
this.head = snakeHead; // 存储蛇头信息
this.pos.push([2, 0]) // 存储蛇头位置
// 创建蛇身体
var snakeBody1 = new Cell(1, 0, 'snakeBody');
snakeBody1.create();
this.pos.push([1, 0]) // 存储蛇身1位置
var snakeBody2 = new Cell(0, 0, 'snakeBody');
snakeBody2.create();
this.tail = snakeBody2; // 存储蛇尾信息
this.pos.push([0, 0]) // 存储蛇身1位置
// 形成链表关系
snakeHead.last = null;
snakeBody1.last = snakeHead;
snakeBody2.last = snakeBody1;
snakeHead.next = snakeBody1;
snakeBody1.next = snakeBody2;
snakeBody2.next = null;
// 给蛇添加一个 direction 属性,用来表示蛇走的方向
this.direction = this.directionNum.right; // 默认往右走
}
2.蛇的运动功能
通过键盘上的 W、A、S、D 和上下左右方位键来控制蛇的移动方向。
// 控制蛇的运动方向
Game.prototype.init = function() {
// 初始化节点
snake.init();
createFood();
document.addEventListener('keydown', function(ev) {
if (ev.keyCode == 37 && snake.direction != snake.directionNum.right) {
snake.direction = snake.directionNum.left;
} else if (ev.keyCode == 38 && snake.direction != snake.directionNum.down) {
snake.direction = snake.directionNum.up;
} else if (ev.keyCode == 39 && snake.direction != snake.directionNum.left) {
snake.direction = snake.directionNum.right;
} else if (ev.keyCode == 40 && snake.direction != snake.directionNum.up) {
snake.direction = snake.directionNum.down;
}
if (ev.keyCode == 65 && snake.direction != snake.directionNum.right) {
snake.direction = snake.directionNum.left;
} else if (ev.keyCode == 87 && snake.direction != snake.directionNum.down) {
snake.direction = snake.directionNum.up;
} else if (ev.keyCode == 68 && snake.direction != snake.directionNum.left) {
snake.direction = snake.directionNum.right;
} else if (ev.keyCode == 83 && snake.direction != snake.directionNum.up) {
snake.direction = snake.directionNum.down;
}
});
this.start();
}
4、食物
1.食物的产生
通过数组存储随机生成的食物的坐标并判断是否与蛇的身体重合。
// 创建食物
function createFood() {
// 食物的随机坐标
var x = null;
var y = null;
var include = true; // 循环跳出的条件,true 表示食物坐标在蛇身上,false:表示不在
while (include) {
x = Math.round(Math.random() * (td - 1));
y = Math.round(Math.random() * (tr - 1));
snake.pos.forEach(function(value) {
if (x != value[0] && y != value[1]) {
include = false; // 坐标不在蛇身上
}
})
}
// 生成食物
food = new Cell(x, y, 'food');
food.pos = [x, y]; // 存储食物的坐标,用于判断是否与蛇头相撞
var foodDom = document.querySelector('.food');
if (foodDom) {
foodDom.style.left = x * cw + 'px';
foodDom.style.top = y * ch + 'px';
} else {
food.create();
}
}
2.吃食物的过程
当页面任意位置出现食物,用户控制蛇移动到食物周围,蛇头碰到食物时则吃掉此食物,页面上会在任意位置出现另一个食物,蛇再去吃掉。
Snake.prototype.getNextPos = function() {
// 蛇头的下一个点的坐标
var nextPos = [
this.head.x / cw + this.direction.x,
this.head.y / ch + this.direction.y
]
// 吃掉食物
if (food && food.pos[0] == nextPos[0] && food.pos[1] == nextPos[1]) {
// 条件成立则蛇头的下一个点是食物
this.strategies.eat.call(this);
return;
}
};
Snake.prototype.strategies = {
eat: function() {
this.strategies.move.call(this, true);
createFood();
game.score++;
},
}
5、游戏结束
吃到自己的身体或者撞到墙则游戏结束。
// 获取蛇头的下一个位置对应的元素,根据元素做不同的事情
Snake.prototype.getNextPos = function() {
// 蛇头的下一个点的坐标
var nextPos = [
this.head.x / cw + this.direction.x,
this.head.y / ch + this.direction.y
]
// 吃到自己,游戏结束
var selfCollied = false; // 判断是否撞到
this.pos.forEach(function(value) {
if (value[0] == nextPos[0] && value[1] == nextPos[1]) {
selfCollied = true;
}
});
if (selfCollied) {
// 使用 call 方法,能调用父类的实例
this.strategies.die.call(this); // this 指向实例
return;
}
// 撞到墙,游戏结束
if (nextPos[0] < 0 || nextPos[1] < 0 || nextPos[0] > td - 1 || nextPos[1] > tr - 1) {
this.strategies.die.call(this);
return;
}
// 吃掉食物
if (food && food.pos[0] == nextPos[0] && food.pos[1] == nextPos[1]) {
// 条件成立则蛇头的下一个点是食物
this.strategies.eat.call(this);
return;
}
// 不是上面的三种情况,继续走
this.strategies.move.call(this);
};
// 处理碰撞后要做的事
Snake.prototype.strategies = {
move: function(format) { // 该参数用于决定是否删除蛇尾
// 在旧蛇头的位置 创建新身体
var newBody = new Cell(this.head.x / cw,
this.head.y / ch, 'snakeBody');
// 更新链表关系
newBody.next = this.head.next;
newBody.next.last = newBody;
newBody.last = null;
// 把旧蛇头从原来的位置删除
this.head.remove();
newBody.create();
// 创建新的蛇头即蛇头下一个移动的点
var newHead = new Cell(this.head.x / cw + this.direction.x,
this.head.y / ch + this.direction.y, 'snakeHead');
// 更新链表关系
newHead.next = newBody;
newHead.last = null;
newBody.last = newHead;
// 更新蛇头方向
newHead.viewContent.style.transform = 'rotate(' + this.direction.rotate + 'deg)';
newHead.create();
// 更新蛇身上每一个格子的坐标
this.pos.splice(0, 0, [this.head.x / cw + this.direction.x,
this.head.y / ch + this.direction.y
]);
// 更新 this.head 即蛇头的位置
this.head = newHead;
if (!format) { // false: 需要删除蛇尾(处理吃之外的操作)
this.tail.remove();
this.tail = this.tail.last;
this.pos.pop();
}
},
eat: function() {
this.strategies.move.call(this, true);
createFood();
game.score++;
},
die: function() {
game.over();
}
}
// 游戏结束
Game.prototype.over = function() {
clearInterval(this.timer);
alert('游戏结束,你本次的得分为:' + this.score);
// 游戏回到最初始的状态
var snakeBox = document.querySelector('.snakeBox');
snakeBox.innerHTML = '';
snake = new Snake();
game = new Game();
var startBtnWrap = document.querySelector('.startBtn');
startBtnWrap.style.display = 'block';
}
小结
我认为写贪吃蛇最重要的就是要在写之前必需先理清自己的思路并明确代码的逻辑结构。
视频学习链接:web前端实战项目系列,JavaScript面相对象开发贪吃蛇(全套教程)