文章目录
贪吃蛇项目
使用typescript的基本是定义类,在类中写代码
gitee-Snake-详解.md
搭建webpack环境
-
用npm全局安装 typescript(前提是安装了nodejs
node -v
查看版本号)- 进入命令行
- 输入:
npm i -g typescript
-
搭建webpack环境
-
初始化项目
进入项目根目录,执行命令
npm init -y
主要作用:创建package.json文件
-
下载构建工具
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin html-webpack-plugin
-
根目录下创建webpack的配置文件 webpack.config.js
// webpack的配置信息 // 1. 引入一个包 const path = require('path'); // 用来拼接路径 // 引入html 插件 const HTMLWebpackPlugin = require('html-webpack-plugin'); // 引入clean插件 const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // webpack中的所有的配置信息都应该写在module.exports中 module.exports = { // 指定入口文件 entry: "./src/index.ts", // 指定打包文件所在的目录 output: { // 指定打包后的目录 path: path.resolve(__dirname, 'dist'), // 打包后文件的名字 filename: "bundle.js" }, // 指定webpack打包时要使用的模块 module: { // 指定要加载的规则 rules: [ { // 指定规则生效的文件 test: /\.ts$/, // 匹配所有以ts结尾的文件 // 要使用的loader use: 'ts-loader', // 要排除的文件 exclude: /node_modules/, }, ] }, // 配置webpack的插件 plugins: [ new HTMLWebpackPlugin({ // title: "这是一个自定义的title", template: "./src/index.html", // 表示生成的网页根据这个模板生成的 }), // 自动生成html文件 new CleanWebpackPlugin(), ], // 用来设置引用模块 resolve: { extensions: ['.ts', '.js'] // 表示 以这两个结尾的都可以模块来使用 } };
-
根目录下创建tsconfig.json,配置可以根据自己需要
// tsconfig.json { // tsconfig.json 是ts编译器的配置文件,ts编译器可以根据她的信息来对代码进行编译 /* include 用来指定哪些ts文件需要被编译 路径:**表示任意目录 * 表示任意文件 */ "include": [ "./src/**/*" ], /* exclude 表示不需要被编译的文件目录 */ // "exclude": [ // "./src/hello/**/*" // hello 下的文件都不会被编译 // ], /* complierOptions 编译器的选项 */ "compilerOptions": { "target": "ES6", // 用来指定 ts被编译为js的版本 "module": "CommonJS", // 指定要使用的模块化的规范 "lib": ["DOM","ES2015"], // 指定项目中要使用的库 "outDir": "./dist", // 用来指定编译后文件所在的目录 // "outFile": "", // 将代码合并为一个文件(所有的全局作用域的代码合并到一个文件中) "allowJs": false, // 是否对js文件进行编译,默认是false "checkJs": false, // 是否检查js代码是否符合语法规范 "removeComments": false, // 是否移除注释,默认是false "noEmit": false, // 不生成编译后的文件 "noEmitOnError": true, // 当有错误时不生成编译后的文件 "sourceMap": false, "strict": true, // 所有严格检查的总开关 "alwaysStrict": true, // 用来设置编译后的文件是否使用严格模式,默认false "noImplicitAny": false, // 是否禁止隐式的any类型 "noImplicitThis": false, // 是否不允许类型不明确的this "strictNullChecks": false, // 严格的空值检查 }, }
-
修改package.json添加如下配置
{ ...略... "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --mode development", "start": "webpack serve --open --mode development" }, ...略... }
-
-
运行
npm run build
看是否打包成功
搭建其他配置环境
我们不仅需要typescript的配置环境,还需要css,less预处理器等配置
-
下载依赖
npm i -D less less-loader css-loader style-loader
-
设定规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2oLmtbWT-1664174694192)(详解.assets/image-20220924160230465.png)]
loader的执行顺序是 从下往上!!!
-
查看是否配置成功
写一点less样式,然后在index.ts中引入,重新打包查看dist里面的index.html里面是否有样式
但是一些CSS3样式兼容不了旧浏览器,我们需要配置postcss,postcss-loader(加载器),postcss-preset-env(解决兼容性问题)
-
下载依赖
npm i -D postcss postcss-loader postcss-preset-env
-
修改配置文件webpack.config.js
use: [ 'style-loader', 'css-loader', // 引入postcss { loader: "postcss-loader", options: { postcssOptions: { plugins: [ [ "postcss-preset-env", { // 兼容浏览器的信息 browsers: "last 2 versions",// 最新的两个版本 } ] ] } } }, 'less-loader' ]
-
打包
npm run build
配置环境的全部代码
tsconfig.json
// tsconfig.json
{
// tsconfig.json 是ts编译器的配置文件,ts编译器可以根据她的信息来对代码进行编译
/* include 用来指定哪些ts文件需要被编译
路径:**表示任意目录
* 表示任意文件
*/
"include": [
"./src/**/*"
],
/*
exclude 表示不需要被编译的文件目录
*/
// "exclude": [
// "./src/hello/**/*" // hello 下的文件都不会被编译
// ],
/*
complierOptions 编译器的选项
*/
"compilerOptions": {
"target": "ES6", // 用来指定 ts被编译为js的版本
"module": "CommonJS", // 指定要使用的模块化的规范
"lib": ["DOM","ES2015"], // 指定项目中要使用的库
"outDir": "./dist", // 用来指定编译后文件所在的目录
// "outFile": "", // 将代码合并为一个文件(所有的全局作用域的代码合并到一个文件中)
"allowJs": false, // 是否对js文件进行编译,默认是false
"checkJs": false, // 是否检查js代码是否符合语法规范
"removeComments": false, // 是否移除注释,默认是false
"noEmit": false, // 不生成编译后的文件
"noEmitOnError": true, // 当有错误时不生成编译后的文件
"sourceMap": false,
"strict": true, // 所有严格检查的总开关
"alwaysStrict": true, // 用来设置编译后的文件是否使用严格模式,默认false
"noImplicitAny": false, // 是否禁止隐式的any类型
"noImplicitThis": false, // 是否不允许类型不明确的this
"strictNullChecks": false, // 严格的空值检查
},
}
webpack.config.js
// webpack的配置信息
// 1. 引入一个包
const path = require('path'); // 用来拼接路径
// 引入html 插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
// 指定入口文件
entry: "./src/index.ts",
// 指定打包文件所在的目录
output: {
// 指定打包后的目录
path: path.resolve(__dirname, 'dist'),
// 打包后文件的名字
filename: "bundle.js"
},
// 指定webpack打包时要使用的模块
module: {
// 指定要加载的规则
rules: [
{
// 指定规则生效的文件
test: /\.ts$/, // 匹配所有以ts结尾的文件
// 要使用的loader
use: 'ts-loader',
// 要排除的文件
exclude: /node_modules/,
},
// 设置less文件的处理
{
test: /\.less$/, // 匹配所有以less结尾的文件
// 要使用的loader,执行顺序从下往上
use: [
'style-loader',
'css-loader',
// 引入postcss
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
// 兼容浏览器的信息
browsers: "last 2 versions",// 最新的两个版本
}
]
]
}
}
},
'less-loader'
]
},
]
},
// 配置webpack的插件
plugins: [
new HTMLWebpackPlugin({
// title: "这是一个自定义的title",
template: "./src/index.html", // 表示生成的网页根据这个模板生成的
}), // 自动生成html文件
new CleanWebpackPlugin(),
],
// 用来设置引用模块
resolve: {
extensions: ['.ts', '.js'] // 表示 以这两个结尾的都可以模块来使用
}
};
package.json
主要添加 build 和 start 这两行,这里面的都是你下载的依赖版本号
完成项目界面
npm start
打开开发环境实时界面
完成Food类
Q:食物有什么属性和方法?
A: 定义一个属性表示食物所对应的元素
如何能判断蛇是否迟到了食物?蛇的偏移量和食物的偏移量一样就证明吃到了。
所以需要一个方法来获取食物的偏移量
当食物被吃掉后,食物要给随机改变位置
所以需要一个 修改食物位置的方法
定义一个属性表示食物所对应的元素
-
如何能判断蛇是否迟到了食物?蛇的偏移量和食物的偏移量一样就证明吃到了。
-
所以定义一个属性表示食物所对应的元素。
element: HTMLElement;
constructor() {
// 获取页面中的food元素,并将其赋值给element
this.element = document.getElementById('food') as HTMLElement;
}
来获取食物的偏移量的方法
-
如何能判断蛇是否迟到了食物?蛇的偏移量和食物的偏移量一样就证明吃到了。
-
所以需要一个方法来获取食物的偏移量
这里用到了 属性存储器的 get方法
// 属性存储器 get方法
// 获取食物的x轴坐标
get X() {
return this.element.offsetLeft;
}
// 获取食物Y轴坐标
get Y() {
return this.element.offsetTop;
}
修改食物位置的方法
如何随机修改食物的位置?
-
首先 食物的活动范围是0~294px。
因为stage的高度和宽度是304px,而食物的高度和宽度是10px,所以食物的最大活动范围是 304-10 = 294px
-
其次 食物的位置只能是 10 的倍数。
因为蛇的高度和宽度是10px,所以蛇每次移动是10px的移动,我们要让 蛇的偏移量 = 食物的偏移量,食物才被吃掉,所以食物的位置只能是 10倍数。
Math.random()
是可以获得0~1之间的数(不包括0和1)Math.random()*29
是将获得的随机数 x 29Math.round(Math.random()*29)
然后再将获得的数进行四舍五入,这样就是整数Math.round(Math.random()*29)*10
这里就是我们的随机位置了
// 随即修改食物的位置
change() {
// 生成一个随机的位置,食物坐标范围(0~294px 记得要减去食物的宽度和高度)
// 要求 食物的坐标是10的倍数
let top = Math.round(Math.random() * 29) * 10;
let left = Math.round(Math.random() * 29) * 10;
this.element.style.left = top + 'px';
this.element.style.top = left + 'px';
}
Food类-总
class Food {
// 食物有什么属性?和方法?
// 定义一个属性表示食物所对应的元素
element: HTMLElement;
constructor() {
// 获取页面中的food元素,并将其赋值给element
this.element = document.getElementById('food') as HTMLElement;
}
// 属性存储器 get方法
// 获取食物的x轴坐标
get X() {
return this.element.offsetLeft;
}
// 获取食物Y轴坐标
get Y() {
return this.element.offsetTop;
}
// 随即修改食物的位置
change() {
// 生成一个随机的位置,食物坐标范围(0~294px 记得要减去食物的宽度和高度)
// 要求 食物的坐标是10的倍数
let top = Math.round(Math.random() * 29) * 10;
let left = Math.round(Math.random() * 29) * 10;
this.element.style.left = top + 'px';
this.element.style.top = left + 'px';
}
}
完成ScorePanel类
ScorePanel游戏的记分牌
score是分数
level是等级:从1开始,当你每获得了10分,等级就会上升,蛇的速度就会增加,有上限(10)
Q:记分牌有什么属性和方法?
A:记分牌有 积分器和 等级 属性,另外还可以设置一个变量来限制等级,和设置一个变量表示 每获得xx分就升级
当你吃到了食物,就会加分
所以有 设置加分的方法
当你每加xx分的时候的时候,就会升一个等级,蛇的速度会加快
所以有 提升等级的方法
属性
记分牌需要什么属性?
- 需要 分数(每吃到食物加1分),等级(分数每获得xx分就生一个等级),需要一个变量来限制最高等级,需要一个变量表示每获得xx分就升级
// score和 level用来记录分数和等级
score = 0;
level = 1;
// 设置一个变量来限制等级
maxLevel: number;
// 设置一个变量表示每获得xx分时就升级
upScore: number;
// 分数和等级所在的元素,在构造函数中进行初始化
scoreEle: HTMLElement;
levelEle: HTMLElement;
constructor(maxLevel: number = 10, upScore: number = 10) { // 你不传参就是10,传参的话就是你传的那个数
this.scoreEle = document.querySelector('#score');
this.levelEle = document.querySelector('#level');
this.maxLevel = maxLevel;
this.upScore = upScore;
}
提升等级的方法
当你每加xx分的时候的时候,就会升一个等级,蛇的速度会加快
需要提升等级的方法
// 提升等级的方法
levelUp() {
// 等级要有上限
if (this.level < this.maxLevel) {
this.level++;
this.levelEle.innerHTML = this.level + '';
}
}
设置加分的方法
当你每吃到一个食物,就会加分,同时你的等级就会上升一个
// 设置加分的方法
addScore() {
this.score++;
this.scoreEle.innerHTML = this.score + ''; // 转成字符串形式
// 判断分数是多少就开始升级
if (this.score % this.upScore == 0) {
this.levelUp();
}
}
ScorePanel类-总
// 定义记分牌的类
class ScorePanel {
// score和 level用来记录分数和等级
score = 0;
level = 1;
// 设置一个变量来限制等级
maxLevel: number;
// 设置一个变量表示每获得xx分时就升级
upScore: number;
// 分数和等级所在的元素,在构造函数中进行初始化
scoreEle: HTMLElement;
levelEle: HTMLElement;
constructor(maxLevel: number = 10, upScore: number = 10) { // 你不传参就是10,传参的话就是你传的那个数
this.scoreEle = document.querySelector('#score');
this.levelEle = document.querySelector('#level');
this.maxLevel = maxLevel;
this.upScore = upScore;
}
// 修改score 和 level
// 设置加分的方法
addScore() {
this.score++;
this.scoreEle.innerHTML = this.score + ''; // 转成字符串形式
// 判断分数是多少就开始升级
if (this.score % this.upScore == 0) {
this.levelUp();
}
}
// 提升等级的方法
levelUp() {
// 等级要有上限
if (this.level < this.maxLevel) {
this.level++;
this.levelEle.innerHTML = this.level + '';
}
}
}
export default ScorePanel;
完成Snake类
Q:蛇有什么属性和方法?
A: 我们需要控制蛇,所以有一个表示 蛇和 蛇头 的元素,还需要蛇所有的身体
我们需要控制蛇来移动
所以我们需要获取蛇头 坐标的方法
我们需要设置 蛇头坐标的方法
当我们蛇吃到食物后,会增加身体一节
所以我们需要增加身体的方法
属性
我们需要控制蛇,所以有一个表示 蛇和 蛇头 的元素,还需要蛇所有的身体
// 获取蛇的容器
element:HTMLElement;
// 表示蛇头的元素
head: HTMLElement;
// 表示蛇的身体(包括舌头)
// HTMLCollection是一个集合,它会实时刷新的
bodies: HTMLCollection;
constructor() {
this.element = document.querySelector('#snake');
// querySelector只取一个节点,所以取到了蛇头
this.head = document.querySelector('#snake > div')
// querySelector不能实时变化,所以我们用getElementById
this.bodies = this.element.getElementsByTagName('div');
}
获取和设置蛇头坐标的方法
我们需要控制蛇来移动
-
所以我们需要获取蛇头 坐标的方法
-
我们需要设置 蛇头坐标的方法
// 获取蛇头的坐标(蛇头的坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
// 设置蛇头的坐标
set X(value: number) {
this.head.style.left = value + 'px';
}
set Y(value: number) {
this.head.style.top = value + 'px';
}
增加蛇身体的方法
当我们蛇吃到食物后,会增加身体一节
- 所以我们需要增加身体的方法
// 蛇增加身体的方法
addBody(){
// 创建div节点
// 向element中添加一个div
this.element.insertAdjacentHTML("beforeend","<div></div>")
}
完成Snake类-总
// 定义蛇 的类
class Snake {
// 获取蛇的容器
element:HTMLElement;
// 表示蛇头的元素
head: HTMLElement;
// 表示蛇的身体(包括舌头)
// HTMLCollection是一个集合,它会实时刷新的
bodies: HTMLCollection;
constructor() {
this.element = document.querySelector('#snake');
// querySelector只取一个节点,所以取到了蛇头
this.head = document.querySelector('#snake > div')
// querySelector不能实时变化,所以我们用getElementById
this.bodies = this.element.getElementsByTagName('div');
}
// 获取蛇头的坐标(蛇头的坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
// 设置蛇头的坐标
set X(value: number) {
this.head.style.left = value + 'px';
}
set Y(value: number) {
this.head.style.top = value + 'px';
}
// 蛇增加身体的方法
addBody(){
// 创建div节点
const div = document.createElement('div');
// 向element中添加一个div,放在element孩子中的第一个
this.element.insertBefore(div,this.element.children[0]);
}
}
export default Snake;
完成GameContro类
游戏控制器,控制其他的所有类
Q:游戏控制器的属性和方法?
A:游戏控制器,需要控制他们元素,所以需要snake,food,scorePanel他们三个属性
另外我们需要存储蛇的移动方向,和记录游戏是否结束的
游戏控制器的初始化方法,当你调用这个方法后游戏就开始
所以要有游戏的初始化方法
蛇需要动,当我们按下键盘上的↑↓←→键的时候,蛇会移动
所以我们要有一个绑定键盘按下的事件
另外我们还要 让蛇移动的方法
属性
游戏控制器,需要控制他们元素,所以需要snake,food,scorePanel他们三个属性
另外我们需要存储蛇的移动方向,和记录游戏是否结束的
// 定义三个属性
snake: Snake;
food: Food;
scorePanel: ScorePanel;
// 存储蛇的移动方向(按键的方向)
direction: string = 'ArrowDown';
// 创建一个属性用来记录游戏是否结束
isLive:boolean = true;
constructor() {
// 创建他们的实例,就可以运行他们了
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel();
// 游戏开始
this.init();
}
键盘按下的响应函数
当我们按下按键的时候,要知道它按的是哪个键
所以要 - 存储按键的方向
// 键盘按下的响应函数
keydownHandler(event: KeyboardEvent) {
// console.log(event.key);// ArrowUp,ArrowDown, ArrowLeft, ArrowRight
// 需要检查event.key的值是否合法(是否正确按键)
// 当我们按键的时候,就要修改direction的属性
// - 这里的this值指向的是document,因为绑定的document,所以我们在调用这个函数的时候用bind改变this指向
this.direction = event.key;
}
让蛇移动的方法
让蛇移动就是,如果你按下了→,蛇 的left变大,↓ top值变大
-
获取到蛇 现在的坐标
-
根据按键的方向来修改X和Y的值
-
修改蛇的X和Y值
-
开启定时调用,等级1的时候每300ms调用,就可以让蛇移动
等级越高速度越快
300 - (this.scorePanel.level - 1) * 30
-
300ms是最低速度,每升一级速度就会加快,300就会往下减少。
-
this.scorePanel.level 一开始是 1 ,然后 再 -1,就是0,(this.scorePanel.level - 1) * 30 就是0,所以一开始的是300ms。
-
随着level 的增加,(this.scorePanel.level - 1) * 30 也会增加,定时器就调用间隔会减短,蛇移动的速度会变快。
// 让蛇移动的方法
run() {
// 根据现在的方向this.direction,来让蛇的位置改变
// 获取蛇现在的坐标
let X = this.snake.X;
let Y = this.snake.Y;
// 根据按键的方向修改X和Y值
switch (this.direction) {
// 向上移动,top值减少
case "ArrowUp":
Y -= 10;
break;
// 向下移动,top值增加
case "ArrowDown":
Y += 10;
break;
// 向左移动,left值减少
case "ArrowLeft":
X -= 10;
break;
// 向右移动,left值增加
case "ArrowRight":
X += 10;
break;
}
// 修改蛇的X和Y
this.snake.X = X;
this.snake.Y = Y;
console.log(this.snake);
// 开启定时调用,每300ms以后就调用run方法
// 等级越高速度越快
this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);
}
游戏的初始化方法
当调用这个方法后,游戏会立即开始,在这里面 - 绑定键盘按下的事件,以及 调用让蛇run的方法
// 游戏的初始化方法,调用后游戏即开始
init() {
// 绑定键盘按下的事件
document.addEventListener('keydown', this.keydownHandler.bind(this)); // 这里面的this指向GameControl
// 调用让蛇run的方法
this.run();
}
⚠ 这里绑定键盘按下的事件,会调用 keydownHandler 这个函数,因为这个函数是和document绑定的,所以 keydownHandler 这个函数里面的this指向的是document
而我们需要this指向的是GameControl 这个原本指向,所以使用了
.bind
来改变this指向
蛇穿墙问题
当蛇撞墙,蛇会die,给蛇加个位置范围,所以主要在蛇的类里面就行修改!!
如果蛇撞墙了,说明value不在0~294这个范围,我们就抛出异常,那么 GameControl里面的 修改蛇的X和Y 可以用
trycatch
来捕获异常将isLive设置为false,表示游戏结束了
进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
// Snake.ts
// 设置蛇头的坐标
set X(value: number) {
// 当我们旧值和要设置的坐标一样,就return,不做修改
if (this.X == value) {
return;
}
this.head.style.left = value + 'px';
// 是否撞墙(X值的合法范围0~294)
if (value < 0 || value > 294) {
// 说明蛇撞墙了(蛇die)把这个消息传给gameControl
// 抛出异常
throw new Error('蛇撞墙了');
}
}
set Y(value: number) {
if (this.Y == value) {
return;
}
this.head.style.top = value + 'px';
// 是否撞墙(Y值的合法范围0~294)
if (value < 0 || value > 294) {
// 说明蛇撞墙了(蛇die)把这个消息传给gameControl
throw new Error('蛇撞墙了');
}
}
// GameControl.ts
// 修改蛇的X和Y
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (error) {
// 将isLive设置为false,表示游戏结束了
this.isLive = false;
// 进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
alert(error.message + '✨ Game Over');
}
检查蛇是否吃到了食物
蛇和食物,需要两个类,我们在GameControl里面进行修改。
吃到食物以后
- 食物的位置要重置
- 分数score要增加
- 蛇要增加一节
// GameControl.ts
// 检查蛇是否迟到了食物
// 上面定义了X和Y是蛇现在的坐标
this.checkEat(X, Y);
// 定义一个方法,检查蛇是否吃到了食物
checkEat(X: number, Y: number) {
// 这里的X和Y是蛇的坐标
if (X === this.food.X && Y === this.food.Y) {
// 食物的位置要重置
this.food.change();
// 分数score要增加
this.scorePanel.addScore();
// 蛇要增加一节
this.snake.addBody();
}
}
身体的移动
身体属于蛇的一部分,所以在Snake类中去写身体的部分
- 让蛇身体跟着移动的方法
// 蛇身体移动的方法
moveBody() {
// 将后面的身体设置为前面的身体(从后往前改)
// 遍历获取所有的身体,从后往前遍历
for (let i = this.bodies.length - 1; i > 0; i--) {
// 获取前面身体的位置 - 类型断言
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';
}
}
-
禁止 蛇掉头
比如:蛇在往下走的时候,不能按上走
判断蛇头的坐标(蛇身体的第一节),和蛇身体的第二节坐标是否一样,一样的话就是掉头。
并且在做????判断前,要先判断蛇身体是否有第二节
value 为我们下一步要走的地方,
现在我们正在往左走,X正在减小,如果这时候我们要往右走,value会变大,但是我们要阻止往右走,就让他继续往左走,让X-10,继续往左走。
如果我们正在往右走,X正在增大,这时候我们要他往左走,value会变小
// 修改蛇头的坐标
set X(value: number) {
// 当我们旧值和要设置的坐标一样,就return,不做修改
if (this.X == value) {
return;
}
// 是否撞墙(X值的合法范围0~294)
if (value < 0 || value > 294) {
// 说明蛇撞墙了(蛇die)把这个消息传给gameControl
// 抛出异常
throw new Error('蛇撞墙了');
}
// 修改X时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头
if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft == value) {
// 说明发生水平方向掉头
// 发生了掉头,让蛇向反方向继续移动
if (value > this.X) {
// 如果新值大于旧值,说明蛇在向右走,此事发生掉头我们应该使蛇向左走
value = this.X - 10;
} else {
value = this.X + 10;
}
}
// 移动我们的身体
this.moveBody();
// 蛇的最新坐标
this.head.style.left = value + 'px';
// 检查是否撞到自己
this.checkHeadBody();
}
-
检查蛇 是否撞到自己,撞到自己后就die
- 其实就是蛇头的坐标和身体的坐标是否发生重复
什么时候检查?
当改变蛇头坐标后,进行检查。
// 检查头和身体是否相撞
checkHeadBody() {
// 获取所有的身体,检查其是否和蛇头的坐标发生重叠
for (let i = 1; i < this.bodies.length; i++) {
// 获取到所有的body
let bd = this.bodies[i] as HTMLElement;
// 看蛇头的X坐标和身体的坐标发生重叠
if (this.X == bd.offsetLeft && this.Y == bd.offsetTop) {
// 游戏结束
throw new Error('撞到自己啦');
}
}
}