「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」。
前情回顾
- 在前面三篇文章,我们实现了地图类、食物类以及蛇类相关的一些逻辑封装
- 今天我们来实现游戏控制的主流程类 Game
实现游戏主流程控制类
- 游戏类主要负责的是游戏相关的一些逻辑的封装,也就是按照游戏需求,将前面实现的三个类串联在一起
- 我们先来看一下代码
class Game {
/**
* el: 为地图 dom 元素
* rect: 为单元格的宽高
* onControl: 为控制逻辑函数,默认为 null, 可由外部用户传入
*/
constructor({ el, rect, onControl = null, toGrade, toWin, toOver }) {
this.map = new Map({ el, rect });
this.food = new Food({ rows: this.map.rows, columns: this.map.columns });
this.snake = new Snake();
// 蛇 移动的时间间隔,默认 200ms
this.timer = null;
this.interval = 200;
// 控制器
this.onControl = onControl;
this.keyDown = this.keyDown.bind(this);
this.controlFn();
// 分数
this.grade = 0;
this.toGrade = toGrade;
// 成功或者失败时的回调
this.toOver = toOver;
this.toWin = toWin;
// init
this.initGame();
}
// 初始化游戏
initGame() {
this.map.setData(this.snake.data);
this.createFood();
this.render();
}
// 产生食物
// 如果当前的地图数据中已经包含了当前产生的食物的坐标,就重新生成一个
// 防止食物出现在 蛇 的身体上
createFood() {
const food = this.food.create();
if (this.map.check(food)) {
this.createFood();
}
}
// 在地图中渲染数据
render() {
this.map.clear();
this.map.setData(this.snake.data);
this.map.setData(this.food.data);
this.map.render();
}
// 专门处理 游戏开始 的相关逻辑
start() {
this.move();
}
// 暂停游戏
stop() {
clearInterval(this.timer);
}
// 专门处理 蛇 移动的逻辑
move() {
this.stop();
this.timer = setInterval(() => {
this.snake.moveFn();
if (this.isEat()) {
console.log('是否吃到食物', true);
this.grade++;
this.snake.eatFn();
this.food.create();
this.changeGrade();
// 加快速度
this.interval *= 0.9;
this.stop();
this.start();
if (this.grade >= 5) {
this.over(1);
// 待会直接完成 over 和 isOver 函数即可
}
}
if (this.isOver()) {
this.over(0);
return;
}
this.render();
}, this.interval);
}
keyDown({ keyCode }) {
// console.log(keyCode, this);
let setDirectionFlag;
switch (keyCode) {
case 37:
setDirectionFlag = this.snake.turnFn('left');
break;
case 38:
setDirectionFlag = this.snake.turnFn('top');
break;
case 39:
setDirectionFlag = this.snake.turnFn('right');
break;
case 40:
setDirectionFlag = this.snake.turnFn('bottom');
break;
default:
break;
}
// console.log("是否改变方向", setDirectionFlag);
}
// 控制器
controlFn() {
if (this.onControl) {
this.onControl.call(this);
} else {
window.addEventListener('keydown', this.keyDown);
}
}
// 判断是否吃到东西
isEat() {
let snake = this.snake.data[0];
let food = this.food.data;
return snake.x === food.x && snake.y === food.y;
}
// 处理分数改变的相关逻辑
changeGrade() {
this.toGrade && this.toGrade(this.grade);
}
// 处理 游戏结束 的相关逻辑
/**
* overState
* 0 中间停止, 完挂了
* 1 胜利了, 游戏结束
*/
over(state) {
if (state === 1) {
if (this.toWin) {
this.toWin();
} else {
console.log('通关了');
}
} else if (state === 0) {
if (this.toOver) {
this.toOver();
} else {
console.log('game over!!!');
}
}
this.stop();
}
// 判断游戏是否结束
isOver() {
const { x, y } = this.snake.data[0];
const { columns, rows } = this.map;
let out = x < 0 || x >= columns || y < 0 || y >= rows;
if (out) {
return true;
}
for (let i = 1; i < this.snake.data.length; i++) {
const body = this.snake.data[i];
if (body.x === x && body.y === y) {
return true;
}
}
return false;
}
}
- 上面代码中,最开始我们需要初始化地图对象,食物对象和游戏的主角 蛇对象
- 然后初始化一个定时器
timer,以及蛇移动的时间间隔interval - 初始化控制器,分数改变的回调,游戏通关的回调,游戏结束的回调
- 最后初始化游戏数据
- 将蛇初始的默认位置数据添加到地图对象中
- 创建一个食物对象,若食物的坐标数据,已经存在于地图对象中,则再次产生一个食物对象,直至食物对象可用,然后将食物放入地图中
- 根据地图对象中蛇与食物的信息,渲染相应的页面
move是用来处理蛇移动过程中的一些逻辑,包括吃食物,越过地图边界结束游戏等,蛇的移动需要用到定时器,所以每次 move 都先清空上次的定时器,重新生成一个定时器- 如何判断蛇吃到食物?
- 只用判断蛇头的坐标是否与食物的坐标重叠即可
- 如果吃到食物,则分数加一,调用初始化时的分数改变回调函数。然后创建新的食物,并添加到地图中
- 如何判断游戏是否结束?
- 有两种情况,一种是蛇头部元素撞到地图边界,一种是蛇头部撞到自己的身体
- 越界情况很好判断,每次移动时,根据地图边界坐标,判断一下是否有与蛇头部坐标相等的,若有则可判断为越界
- 撞到蛇身体,则需要循环蛇身每个元素,一次判断,蛇头部元素的坐标是否与蛇身元素有相等的,若有则可判断为蛇头撞到身体了
最后
- 当游戏所有类的逻辑都封装完成后,即可开始编写,游戏开始的引导程序了
let map = document.querySelector('#map');
let onControl = function () {
window.addEventListener('keydown', ({ keyCode }) => {
switch (keyCode) {
case 65:
setDirectionFlag = this.snake.turnFn('left');
break;
case 87:
setDirectionFlag = this.snake.turnFn('top');
break;
case 68:
setDirectionFlag = this.snake.turnFn('right');
break;
case 83:
setDirectionFlag = this.snake.turnFn('bottom');
break;
default:
break;
}
});
};
let gradeEle = document.querySelector('.grade');
let toGrade = (grade) => {
gradeEle.textContent = grade;
};
let game = new Game({ el: map, rect: 12, toGrade, onControl });
game.toOver = () => {
alert('游戏结束');
};
game.toWin = () => {
alert('太棒了,你通关了');
};
game.start();
onControl为外部实现的控制器,监听按键按下事件,来改变蛇头移动的方向- 给 game 对象设置相应的游戏通关回调和游戏结束回调
game.start()即可开始游戏了
最后
- 今天的分享就到这里,欢迎大家在评论区留言讨论
- 如果觉得文章写的还不错的话,希望大家不要吝惜点赞,大家的鼓励是我分享的最大动力 🥰