赞
踩
蛇身体的移动属于蛇的事情,所以写在Snake类中。
后一节移动后的位置刚好为前一节位置,依次移动即可。因此除了蛇头为键盘控制外,蛇身继承前一节的位置即可。
移动身体的moveBody
方法代码如下:
// 添加一个蛇身体移动的方法
moveBody() {
/**
* 将后边的身体设置为前边身体的位置
*/
for (let i = this.bodies.length - 1; i > 0; i--) {
// 获取前边身体的位置(类型断言,就是HTMLElement)
let X = (this.bodies[i - 1] as HTMLElement).offsetLeft;
let Y = (this.bodies[i - 1] as HTMLElement).offsetTop;
// 将值设置到当前身体上
(this.bodies[i] as HTMLElement).style.left = X + "px";
(this.bodies[i] as HTMLElement).style.top = Y + "px";
}
}
所以理所应当的,在蛇头移动的时候需要调用moveBody方法。蛇头移动方法是X和Y的setters方法。因此在蛇头的坐标发生改变之后,需要moveBody。setters的代码如下:
// 设置蛇头的X坐标 set X(value: number) { // 如果新值和旧值相同,则直接返回,不再修改 if (this.X === value) { return; } // X的值的合法范围在0-290px之间 if (value < 0 || value > 290) { // 进入判断,说明蛇撞墙了 throw new Error("蛇撞墙了"); } // 移动身体 this.moveBody(); this.head.style.left = value + "px"; } // 设置蛇头的Y坐标 set Y(value: number) { // 如果新值和旧值相同,则直接返回,不再修改 if (this.Y === value) { return; } // X的值的合法范围在0-290px之间 if (value < 0 || value > 290) { // 进入判断,说明蛇撞墙了 throw new Error("蛇撞墙了"); } // 移动身体 this.moveBody(); this.head.style.top = value + "px"; }
避免蛇在两节及以上的时候发生掉头的问题,在只有单独一个蛇头的时候可以随意变换方向。
直接判断蛇头和下一节蛇身的坐标是否重叠,重叠了则表明发生了掉头情况,此时将蛇头的位置重新更改即可。
// 设置蛇头的X坐标 set X(value: number) { // 如果新值和旧值相同,则直接返回,不再修改 if (this.X === value) { return; } // 避免蛇在两节及以上的时候发生掉头的问题 if ( this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value ) { // 进入判断说明发生了掉头 // 如果发生掉头,让蛇向反方向继续移动 if (value > this.X) { // 如果新值value大于旧值X,则说明蛇在向右走,如果此时发生掉头,应该使蛇继续向左走 value = this.X - 10; } else { value = this.X + 10; } } // X的值的合法范围在0-290px之间 if (value < 0 || value > 290) { // 进入判断,说明蛇撞墙了 throw new Error("蛇撞墙了"); } // 移动身体 this.moveBody(); this.head.style.left = value + "px"; } // 设置蛇头的Y坐标 set Y(value: number) { // 如果新值和旧值相同,则直接返回,不再修改 if (this.Y === value) { return; } // 避免蛇在两节及以上的时候发生掉头的问题 if ( this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value ) { if (value > this.Y) { value = this.Y - 10; } else { value = this.Y + 10; } } // X的值的合法范围在0-290px之间 if (value < 0 || value > 290) { // 进入判断,说明蛇撞墙了 throw new Error("蛇撞墙了"); } // 移动身体 this.moveBody(); this.head.style.top = value + "px"; }
这里的逻辑其实挺简单,只要蛇的头和身体的某一节坐标重叠,说明蛇撞到了自己的身体。当然,这部分的代码逻辑需要放在蛇头移动后进行判断。
判断蛇头是否撞击到蛇身的方法checkHeadBody
代码如下:
// 判断蛇头是否撞击到蛇身
checkHeadBody() {
// 获取所有的身体,检查是否和蛇头的坐标发生重叠
for (let i = 1; i < this.bodies.length; i++) {
let node = this.bodies[i] as HTMLElement;
if (this.X === node.offsetLeft && this.Y === node.offsetTop) {
// 进入判断说明蛇头撞到了身体,游戏结束
throw new Error("撞到自己了~");
}
}
}
蛇每次开始游戏时,应当在随机位置出生,食物也是如此。
食物被吃到后刷新位置不能刷新到蛇的身体上。
这些问题的结局办法较简单,另外,因为没有全方位测试,所以可能还有一些隐藏bug,但是总体的功能是没有问题的。
enum Direction {
Up = "ArrowUp",
Down = "ArrowDown",
Left = "ArrowLeft",
Right = "ArrowRight"
}
export default Direction;
// 定义食物类Food class Food { // 定义一个属性表示食物所对应的元素 private element: HTMLElement; constructor() { // 获取页面中的food元素并将其赋值给element成员变量 // 感叹号! : 肯定获取的元素不可能为空,就不用进行空值判断了 this.element = document.getElementById("food")!; } // 定义一个获取食物X轴坐标的方法 get X() { return this.element.offsetLeft; } // 定义一个获取食物Y轴坐标的方法 get Y() { return this.element.offsetTop; } // 修改食物的位置 public change() { // 生成一个随机的位置 // 食物的位置最小是0,最大是290px let left = Math.floor(Math.random() * 30) * 10; let top = Math.floor(Math.random() * 30) * 10; this.element.style.left = top + "px"; this.element.style.top = left + "px"; } } export default Food;
// 引入其他的类 import Snake from "./Snake"; import Food from "./Food"; import ScorePanel from "./ScorePanel"; import Direction from "./Direction"; // 游戏控制器,控制其他的所有类 class GameControl { // 基础运动间隔事件 private baseTimeInterval: number = 300; private timeInterval: number = this.baseTimeInterval; // 定义三个属性 // 蛇 private snake: Snake; // 食物 private food: Food; // 记分牌 private scorePanel: ScorePanel; // 创建一个属性来存储蛇的移动方向 private direction: string = ""; // 创建一个属性用来记录游戏是否结束 private isLive: boolean = true; constructor() { this.snake = new Snake(); this.food = new Food(); this.scorePanel = new ScorePanel(); this.init(); } // 游戏的初始化方法,调用后游戏立即开始 private init() { // 绑定键盘按键按下的事件 document.addEventListener("keydown", this.keyDownHandle.bind(this)); // 调用run方法,使蛇不停移动 this.run(); } // 创建一个键盘按下的响应函数 private keyDownHandle(event: KeyboardEvent): void { // 修改direction this.direction = event.key; } // 创建一个控制蛇移动的方法 run() { /** * 根据方向 this.direction 来使蛇的位置改变 * 向上:top减少 * 向下:top增加 * 向左:left减少 * 向右:left增加 */ // 获取蛇现在坐标 let X: number = this.snake.X; let Y: number = this.snake.Y; // 判断按键方向后改变蛇头的坐标 switch (this.direction) { case Direction.Up: Y -= 10; break; case Direction.Down: Y += 10; break; case Direction.Left: X -= 10; break; case Direction.Right: X += 10; break; default: break; } // 检查蛇是否吃到了食物 this.checkEat(X, Y); // 修改改变后的蛇的坐标 try { this.snake.X = X; this.snake.Y = Y; } catch (error) { // 进入到catch说明出现了异常,游戏结束。 alert(error.message + "GAME OVER"); // 将isLive设置为false this.isLive = false; } // 蛇还没死的时候,开启一个定时调用,时间间隔和level相关 if (this.isLive) { this.timeInterval = this.baseTimeInterval - (this.scorePanel.getLevel() - 1) * 30; setTimeout(this.run.bind(this), this.timeInterval); } } // 定义一个方法,用来检查蛇是否吃到食物 checkEat(X: number, Y: number) { if (X === this.food.X && Y === this.food.Y) { // 食物的位置要进行重置 this.food.change(); // 分数增加 this.scorePanel.addScore(); // 蛇要增加一节 this.snake.addBody(); } } } export default GameControl;
// 定义表示记分牌的类 class ScorePanel { // score和level用来记录分数和等级 private score: number = 0; private level: number = 1; // 分数和等级所在的元素,在构造函数中进行初始化 private scoreEle: HTMLElement; private levelEle: HTMLElement; // 设置一个变量限制等级 private maxLevel: number; // 设置一个变量表示每多少分难度升级一次 private upScore: number; // 默认的最高等级为10 constructor(maxLevel: number = 10, upScore: number = 10) { this.scoreEle = document.getElementById("score")!; this.levelEle = document.getElementById("level")!; this.maxLevel = maxLevel; this.upScore = upScore; } public getLevel() { return this.level; } // 设置一个分数自增1的方法 public addScore(): void { this.scoreEle.innerHTML = ++this.score + ''; // 判断分数的阈值,达到阈值提升等级 if (this.score % 10 === 0) { this.levelUp(); } } // 提升等级方法 private levelUp(): void { // level有最高等级 if (this.level < this.maxLevel) { this.levelEle.innerHTML = ++this.level + ''; } } } export default ScorePanel;
class Snake { // 表示蛇的元素 head: HTMLElement; // 蛇的身体(包括蛇头) bodies: HTMLCollection; // 获取蛇的容器 element: HTMLElement; constructor() { this.element = document.getElementById("snake")!; // 获取放置蛇的容器,取第一个子元素便为头 this.head = this.element.querySelector("div")!; this.bodies = this.element.getElementsByTagName("div"); } // 获取蛇头的X坐标 get X() { return this.head.offsetLeft; } // 获取蛇头的Y坐标 get Y() { return this.head.offsetTop; } // 设置蛇头的X坐标 set X(value: number) { // 如果新值和旧值相同,则直接返回,不再修改 if (this.X === value) { return; } // 避免蛇在两节及以上的时候发生掉头的问题 if ( this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value ) { // 进入判断说明发生了掉头 // 如果发生掉头,让蛇向反方向继续移动 if (value > this.X) { // 如果新值value大于旧值X,则说明蛇在向右走,如果此时发生掉头,应该使蛇继续向左走 value = this.X - 10; } else { value = this.X + 10; } } // X的值的合法范围在0-290px之间 if (value < 0 || value > 290) { // 进入判断,说明蛇撞墙了 throw new Error("蛇撞墙了"); } // 移动身体 this.moveBody(); // 移动头 this.head.style.left = value + "px"; // 检查有没有撞到自己 this.checkHeadBody(); } // 设置蛇头的Y坐标 set Y(value: number) { // 如果新值和旧值相同,则直接返回,不再修改 if (this.Y === value) { return; } // 避免蛇在两节及以上的时候发生掉头的问题 if ( this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value ) { if (value > this.Y) { value = this.Y - 10; } else { value = this.Y + 10; } } // X的值的合法范围在0-290px之间 if (value < 0 || value > 290) { // 进入判断,说明蛇撞墙了 throw new Error("蛇撞墙了"); } // 移动身体 this.moveBody(); // 移动头 this.head.style.top = value + "px"; // 判断有没有撞到自己 this.checkHeadBody(); } // 吃入食物后身体变长(增加div元素) addBody() { // 向element中的末尾添加一个div this.element.insertAdjacentHTML("beforeend", "<div></div>"); } // 添加一个蛇身体移动的方法 moveBody() { /** * 将后边的身体设置为前边身体的位置 */ for (let i = this.bodies.length - 1; i > 0; i--) { // 获取前边身体的位置(类型断言,就是HTMLElement) let X = (this.bodies[i - 1] as HTMLElement).offsetLeft; let Y = (this.bodies[i - 1] as HTMLElement).offsetTop; // 将值设置到当前身体上 (this.bodies[i] as HTMLElement).style.left = X + "px"; (this.bodies[i] as HTMLElement).style.top = Y + "px"; } } // 判断蛇头是否撞击到蛇身 checkHeadBody() { // 获取所有的身体,检查是否和蛇头的坐标发生重叠 for (let i = 1; i < this.bodies.length; i++) { let node = this.bodies[i] as HTMLElement; if (this.X === node.offsetLeft && this.Y === node.offsetTop) { // 进入判断说明蛇头撞到了身体,游戏结束 throw new Error("撞到自己了~"); } } } } export default Snake;
// 引入样式
import "./style/index.less";
// 引入类
import GameControl from "./modules/GameControl";
new GameControl();
// 设置变量 @bg-color: #b7d4a8; // 清除默认样式 * { margin: 0; padding: 0; // 改变盒子模型的计算方式 box-sizing: border-box; } body { font: 700 20px "Courier"; } // 设置主窗口样式 #main { width: 360px; height: 420px; // 设置背景颜色 background-color: @bg-color; // 设置居中 margin: 100px auto; border: 10px solid black; // 设置圆角 border-radius: 20px; // 开启弹性盒模型 display: flex; // 设置主轴的方向(纵向) flex-flow: column; // 设置侧轴的对齐方式 align-items: center; // 设置主轴的对齐方式(每个项目两侧的间隔相等) justify-content: space-around; // 设计游戏界面样式 #stage { width: 304px; height: 304px; border: 2px solid #000; // 开启相对定位(子绝父相) position: relative; // 设置蛇的样式 #snake { // & 代表当前选择器的父类 & > div { width: 10px; height: 10px; background-color: #000; border: 1px solid @bg-color; // 开启绝对定位 position: absolute; } } // 设置食物样式 #food { width: 10px; height: 10px; position: absolute; left: 40px; top: 100px; // 开启弹性盒模型 display: flex; // 设置主轴内项目为横向排列,但是会换行 flex-flow: row wrap; // 设置主轴内项目的对齐方式(两端对齐,项目之间的间隔都相等) justify-content: space-between; & > div { width: 4px; height: 4px; background-color: #000; // 使div旋转45度 transform: rotate(45deg); } } } // 设置记分牌样式 #score-panel { width: 300px; display: flex; // 设置主轴的对齐方式(两端对齐,项目之间的间隔都相等) justify-content: space-between; } }
<!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>贪吃蛇</title> </head> <body> <!-- 创建游戏的主容器 --> <div id="main"> <!-- 设置游戏界面 --> <div id="stage"> <!-- 设置蛇 --> <div id="snake"> <!-- snake内部的div,表示蛇的各部分 --> <div></div> </div> <!-- 设置食物 --> <div id="food"> <!-- 添加四个小div,来设置食物的样式(花瓣形) --> <div></div> <div></div> <div></div> <div></div> </div> </div> <!-- 设置游戏的积分牌 --> <div id="score-panel"> <div> SCORE: <span id="score">0</span> </div> <div> LEVEL: <span id="level">1</span> </div> </div> </div> </body> </html>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。