TypeScript实战------实现贪吃蛇小游戏

在开始实现贪吃蛇小游戏之前,可以先看看https://blog.csdn.net/fageaaa/article/details/145802780,这篇文章可以帮助你初步了解Typescript。另外,在开发之前我会先讲解一下Typescript的开发准备。

1.TypeScript 开发环境搭建

1.1 下载Node.js

1.2 安装Node.js

这个不用多说,网上都有相应的教程。

1.3 使用npm全局安装typescript

 npm i -g typescript

1.4 创建一个ts文件

在文件夹里新建一个01Hello.ts文件夹:
在这里插入图片描述
01Hello.ts中内容为:

console.log("hello ts!!");

1.5 使用tsc对ts文件进行编译

进入命令行,进入ts文件所在目录,执行命令:tsc 01Hello.ts
在这里插入图片描述
当控制台没反应就说明成功了。这时候我们会发现ts文件所在文件夹里面多了一个01Hello.js文件(执行了tscts文件编译成立js文件):
在这里插入图片描述
这时候查看01Hello.js文件中内容:

console.log("hello ts!!");

发现和01Hello.ts的内容一模一样,这是因为内容简单,如果内容稍微复杂点内容就不一样了。加入ts中内容如下:

interface Person {
  name: string;
  age: number;
}

let person: Person = {
  name: "bob",
  age: 12,
};
person.age = 19;
console.log(person);

使用tsc命令编译成js文件后内容如下
在这里插入图片描述
这时候编译后的js文件内容和原ts就不一样了。如果想要执行js文件,可以执行“node 02Test.js”命令就可以了。
在这里插入图片描述

2.编译选项

我们知道tsjs不一样,js文件可以直接被引入到html中放在浏览器中就可以执行了,而ts则需要通过tsc命令把ts文件编译为js文件然后才能引入浏览器中。如果文件很多,这样子就会显得很麻烦。

而编译选项就可以解决这个问题。编译选项让编写ts变得优雅,比如把手动需要做的事情让其自动化完成,比如语法有问题就停止让其编译,比如把ts编译成es6格式的js等。

2.1 自动编译文件

编译文件时,使用-w 指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。格式如下:

tsc xxx.ts -w

执行后控制台如下,表明TS编译器会自动监视文件的变化:
在这里插入图片描述

2.2 自动编译整个项目

我们想要这种效果:直接使用tsc指令,就可以自动将当前项目下的所有ts文件编译为js文件。

但是能直接使用tsc命令的前提是:**要先在项目根目录下创建一个ts的配置文件 tsconfig.json。**如下(暂时什么配置都没有,tsconfig.json是一个JSON文件,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译):
在这里插入图片描述
执行tsc:
在这里插入图片描述
会发现该项目中的三个文件都被编译成了js文件。
在这里插入图片描述

2.3 tsconfig.json中配置选项

2.3.1 include

include表示希望被编译文件所在的目录。默认值是["**/*"],下面是它的示例:

   //所有src目录和tests目录下的文件都会被编译
   "include":["src/**/*", "tests/**/*"]

2.3.2 exclude

exclude表示需要排除在外的目录。默认值是["node_modules", "bower_components", "jspm_packages"],下面是它的示例:

   //src下hello目录下的文件都不会被编译
   "exclude": ["./src/hello/**/*"]

2.3.3 extends

extends表示被继承的配置文件。下面是它的示例:

   //当前配置文件中会自动包含config目录下base.json中的所有配置信息
   "extends": "./configs/base"

2.3.4 files

files指定被编译文件的列表,只有需要编译的文件少时才会用。示例如下:

"files": [
    "core.ts",
    "sys.ts",
    "types.ts",
    "scanner.ts",
    "parser.ts",
    "utilities.ts",
    "binder.ts",
    "checker.ts",
    "tsc.ts"
  ]

2.3.5 compilerOptions

compilerOptions编译选项是配置文件中非常重要也比较复杂的配置选项,在compilerOptions中包含多个子选项,用来完成对编译的配置。

2.3.5.1 项目选项

(1)target

target用于设置ts代码编译的目标版本。可选值有ES3(默认)ES5ES6/ES2015ES7/ES2016ES2017ES2018ES2019ES2020ESNext

如下在src/app.ts文件夹:

let a = 12;

执行tsc命令后会生成app.js,如下:
在这里插入图片描述
这是因为当compilerOptionstarget属性默认为es3,所以项目默认会把ts代码编译为es3版本。所以let就变成了var

加入在tsconfig.jsontarget属性默认为es6:
在这里插入图片描述
然后命令行执行tsc,再去看生成的app.js内容如下(这时候是let):
在这里插入图片描述

(2)module
用于设置编译后代码使用的模块化系统。可选值有CommonJSUMDAMDSystemES6/ES2015ES2020ESNextNone。如果不知道有哪些可选值,可以查官网,也可以直接给module属性赋值为一个不可选的随机值,如下:
在这里插入图片描述
然后执行tsc命令,控制台就会有相应的提示:在这里插入图片描述
我们把module属性值改为es2015。同时写一些代码:

//src/m.ts
export const hi = "hello";

//src/app.ts
import { hi } from "./m";
console.log(hi);

此时执行tsc生成app.js文件,内容如下:
在这里插入图片描述
module属性值改为CommonJS,按照刚才的操作生成app.js文件,内容如下:
在这里插入图片描述

(3)lib
用于指定代码运行时所包含的库(宿主环境)。可选值如下:
在这里插入图片描述
使用示例如下:

"compilerOptions": {
    "target": "ES6",
    "lib": ["ES6", "DOM"],
}

注意:如果是在浏览器环境下,一般是不需要配置lib的,在nodejs环境下是需要配置一些库的,比如DOM

(4)outDir
outDir编译后文件的所在目录。默认情况下,编译后的js文件会和ts文件位于相同的目录,如下:。
在这里插入图片描述
设置outDir后可以改变编译后文件的位置:

{
  "include": ["./src/**/*"],
  "compilerOptions": {
    "target": "ES6",
    "module": "ES2015",
    "outDir": "./dist"
  }
}

tsc编译项目,生成的文件都放在dist文件夹(会新建一个dist文件夹)中了:
在这里插入图片描述

(5)outFile
outFile将所有的文件编译为一个js文件,默认会将所有的编写在全局作用域中的代码合并为一个js文件。如下:

//src/m.ts
let b = 12;
let c = 13
console.log(b,c);

//src/app.ts
let a = 78
console.log(a);

配置outFile属性:

//tsconfig.json
{
  "include": ["./src/**/*"],
  "compilerOptions": {
    "target": "ES6",
    "module": "System",//当使用outfile属性时候,module的值只能是system或者amd,-outFile仅支持 "amd" 和 "system" 模块
    "outDir": "./dist",
    "outFile": "./dist/index.js"
  }
}

执行tsc编译整个项目,生成dist文件夹,在dist文件夹中只生成一个index.js文件:
在这里插入图片描述
index.js文件内容如下:
在这里插入图片描述

如果module制定了NoneSystemAMD则会将模块一起合并到文件之中。下面来验证一下。

let b = 12;
let c = 13
console.log(b,c);
export const hi = "hello";

//src/app.ts
import { hi } from "./m";
console.log(hi);
let a = 78
console.log(a);

再执行tsc编译整个项目后在项目dist文件夹下生成一个index.js文件,内容如下:
在这里插入图片描述
实际上,在真实开发中很少采用这个属性来确定打包的出口,因为把所有文件打包在一个文件里面并不是一个好的方法。在真实开发中,都是结合打包工具,配置相关文件来做的。(后续我会讲解)

(6)allowJs
allowJs表示是否对js文件编译。默认为false

在src文件夹新建一个hello.js,随便写点内容:

let x=10;
console.log(x);

默认情况下执行tsc后在dist文件夹中是不会生成hello.js文件的:
在这里插入图片描述
修改allowJs属性值为true后,执行tsc后在dist文件夹中会生成hello.js文件:
在这里插入图片描述

(7)checkJs

checkJs表示是否对js文件进行检查。默认为false,如下:
在这里插入图片描述
js中,如上图代码不会报错,因为js中没有类型一说。但在ts中上述代码是错误的。由于checkJs属性值默认为false,所以这里不会对hello.js进行检查。

如果把checkJs属性值改为true:
在这里插入图片描述
这时候就会对hello.js进行检查了。
在这里插入图片描述

(8)removeComments
表示是否删除注释,默认值为false
如果把removeComments属性值改为true,编译后的文件中的注释就会没有。

(9)noEmit
表示不对代码进行编译,默认值为false
如果把noEmit属性值改为true,表示不对代码进行编译。那就不会有dist文件夹,也不会有编译的文件:
在这里插入图片描述

(10)rootDir
rootDir指定代码的根目录。默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过rootDir可以手动指定根目录。示例如下:

"compilerOptions": {
    "rootDir": "./src"
}

(11)sourceMap
表示是否生成sourceMap,默认为falsesourceMap是什么?后续我将在webpack中讲解。

2.3.5.2 严格检查

(1)strict
启用所有的严格检查,默认值为true,设置后相当于开启了所有的严格检查。

(2)alwaysStrict
总是以严格模式对代码进行编译。

(3)noImplicitAny
禁止隐式的any类型。

(4)noImplicitThis
禁止类型不明确的this。

(5)strictBindCallApply
严格检查bindcallapply的参数列表。

(6)strictFunctionTypes
严格检查函数的类型。

(7)strictNullChecks
严格的空值检查。

(8)strictPropertyInitialization
严格检查属性是否初始化。

2.3.5.3 额外检查

(1)noFallthroughCasesInSwitch
noFallthroughCasesInSwitch用于检查switch语句包含正确的break

(2)noImplicitReturns
noImplicitReturns用于检查函数没有隐式的返回值。

(3)noUnusedLocals
noUnusedLocals用于检查未使用的局部变量。

(4)noUnusedParameters
noUnusedParameters用于检查未使用的参数。

2.3.5.4 高级

(1)allowUnreachableCode
allowUnreachableCode表示检查不可达代码,true表示忽略不可达代码,false表示不可达代码将引起错误。

(2)noEmitOnError
noEmitOnError表示有错误的情况下不进行编译,默认值为false

上述都是常见的ts编译选项配置,还有很多属性没列出来。详情可看官网或者其他文档。

3 使用webpack打包ts代码

通常情况下,实际开发中我们都需要使用构建工具对代码进行打包,TS同样也可以结合构建工具一起使用,下边以webpack为例介绍一下如何结合构建工具使用TS

3.1 初始化项目

进入项目根目录,执行命令 npm init -y,目的是创建package.json文件:
在这里插入图片描述

3.2 下载构建工具

命令如下:

npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin html-webpack-plugin
  • webpack
    构建工具webpack
  • webpack-cli
    webpack的命令行工具
  • webpack-dev-server
    webpack的开发服务器。安装了它就相当于在项目中安装了一个内置服务器。
  • typescript
    ts编译器
  • ts-loader
    ts加载器,用于在webpack中编译ts文件
  • html-webpack-plugin
    webpackhtml插件,用来自动创建html文件
  • clean-webpack-plugin
    webpack中的清除插件,每次构建都会先清除打包目录

安装好这七个第三方库后,会多出一个node_modules的文件夹,里面放的就是下载的第三方库。
在这里插入图片描述

3.3 配置webpack.config.js

根目录下创建webpack的配置文件webpack.config.js

//引入node中内置包path
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
    optimization:{
        minimize: false // 关闭代码压缩,可选
    },
    //指定项目的入口文件
    entry: "./src/index.ts",
    
    devtool: "inline-source-map",
    
    mode: 'development',
    //指定打包文件所在目录
    output: {
        path: path.resolve(__dirname, "dist"),
        //打包后文件的名字
        filename: "bundle.js",
        environment: {
            arrowFunction: false // 关闭webpack的箭头函数,可选
        }
    },
    //指定webpack打包时候用到的loader
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: {
                   loader: "ts-loader"     
                },
                exclude: /node_modules/
            }
        ]
    },
    //配置webpack插件
    plugins: [
        //每次构建都会先清除打包目录
        new CleanWebpackPlugin(),
        //生成自定义html模板
        new HtmlWebpackPlugin({
            title:'TS测试'
        }),
    ]
}

3.4 配置tsconfig.json

根目录下创建tsconfig.json

{
    "compilerOptions": {
        "target": "ES2015",
        "module": "ES2015",
        "strict": true
    }
}

3.5 配置package.json

{
  ......
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    //启动webpack服务器,并且用chrome.exe打开网页
    "start": "webpack serve --open"
  },
  ......
}

3.6 运行打包项目:

在根目录新建src,在src下创建index.ts文件和m1.ts文件。

// src/m1.ts
export const hi="你好";

// src/index.ts
import { hi } from "./m1";
function sum(a: number, b: number): number {
  return a + b;
}
console.log(sum(121, 256));
console.log("hhhhh");
console.log(hi);

在命令行执行npm run dev启动服务器,发现报错:

在这里插入图片描述

这是因为在src下的ts文件中使用了模块化。我们需要在webpack.config.js中配置模块化,如下:

在这里插入图片描述

在命令行再次执行npm run dev:

在这里插入图片描述

运行成功。执行npm run build对代码进行编译打包,如下(说明打包成功):

在这里插入图片描述

4 使用Babel兼容更多浏览器

经过一系列的配置,使得TSwebpack已经结合到了一起,除了webpack,开发中还经常需要结合babel来对代码进行转换以使其可以兼容到更多的浏览器,在上述步骤的基础上,通过以下步骤再将babel引入到项目中。

4.1 安装依赖包

npm i -D @babel/core @babel/preset-env babel-loader core-js

共安装了4个包,分别是:

  • @babel/core
    babel的核心工具
  • @babel/preset-env
    babel的预定义环境
  • @babel-loader
    babelwebpack中的加载器
  • core-js
    core-js用来使老版本的浏览器支持新版ES语法

4.2 修改webpack.config.js配置文件

......
module: {
    rules: [
        {
            test: /\.ts$/,
            use: [
                {
                    loader: "babel-loader",
                    options:{
                        presets: [
                            [
                                "@babel/preset-env",
                                {
                                    "targets":{
                                        "chrome": "58",
                                        "ie": "11"
                                    },
                                    "corejs":"3",
                                    "useBuiltIns": "usage"
                                }
                            ]
                        ]
                    }
                },
                {
                    loader: "ts-loader",
                }
            ],
            exclude: /node_modules/
        }
    ]
}
......

如此一来,使用ts编译后的文件将会再次被babel处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的targets中指定要兼容的浏览器版本。

5 初始化项目架构

5.1 初始化配置文件

新建一个greedy-snake文件夹,将上述所讲的配置好的package.jsonwebpack.config.jstsconfig.json三个配置文件复制到greedy-snake文件夹下。

tsconfig.json新加一条配置 "noEmitOnError": true表示等项目出错时候不进行编译:
在这里插入图片描述
package.jsonname属性值修改成本项目的名字:
在这里插入图片描述
新建src文件夹,在src文件夹下建立index.ts文件;新建public文件夹,在src文件夹下建立index.html文件(项目打包模板文件)。
在这里插入图片描述
在这里插入图片描述
修改一下webpack.config.js中的配置,指定public文件夹下的index.html文件为项目打包模板主页面。
在这里插入图片描述
使用npm intsall安装所有的第三方库。安装成功之后会多一个node_modules文件夹和package-lock.json文件。
npm run build打包项目。打包成功之后会多生成一个dist打包文件夹,文件夹下有bundle.jsindex.html两个文件。可以看看index,html中内容:
在这里插入图片描述

5.2 集成less

安装:

npm install -D less less-loader css-loader style-loader

webpack.config.js中配置less:

 {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          "less-loader",
        ],
},

src下新建style文件夹,在文件夹中新建index.less文件:

//src/style/index.less
body {
  background-color: antiquewhite;
}

src/index.ts中引入index.less文件:
在这里插入图片描述
运行项目会发现body背景色确实改变,说明集成less已经成功。

5.3 集成postcss

postcss可以实现css代码的转换,可以将css中的新语法转为旧语法。css中有些新语法在低版本浏览器中是不被支持的,为了让项目的兼容性更好,我们希望项目css的新语法能被大多数浏览器所识别。

5.3.1 浏览器私有前缀

如下在src/style/index.less添加一个弹性盒子(css3新属性)。
在这里插入图片描述
对其需要考虑兼容性,常规的做法是添加浏览器私有前缀。浏览器私有前缀是为了兼容老版本的写法,比较新版本的浏览器无须添加

如下写几个常见浏览器的私有前缀:

  • -moz-:代表 firefox 浏览器私有属性
  • -ms-:代表 ie 浏览器私有属性
  • -webkit-:代表 safarichrome 私有属性
  • -o-:代表 Opera 私有属性

使用示例如下:

-moz-border-radius: 10px; 
-webkit-border-radius: 10px; 
-o-border-radius: 10px; 
border-radius: 10px;

5.3.2 postcss解决样式兼容性问题

如上,当很多地方使用css3语法时候,手动添加浏览器私有前缀会很麻烦。如果能自动添加浏览器私有前缀就好了。postcss就可以解决这个问题。

我们先不使用postcss,直接打包项目,打包后的flex语法被编译成如下:
在这里插入图片描述

接下来安装一下postcss

npm install -D postcss postcss-loader postcss-preset-env

webpack.config.js中配置postcss:

      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          //引入postcss,这个放在less-loader之前,css-loader之后
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      //兼容每种浏览器的最新两个版本
                      browsers: "last 2 versions",
                    },
                  ],
                ],
              },
            },
          },
          "less-loader",
        ],
      },

再次打包之后,打包后的flex语法被编译成如下,说明postcss可以自动给css新语法转为旧语法。
在这里插入图片描述

6 实现项目界面

6.1 大致原型界面

在这里插入图片描述
如上,有一个游戏面板,底部是积分区和等级区。等到了一定分数会自动升级,等级越高蛇移动的速度越快。上面部分是游戏主区,里面有贪吃蛇和食物,蛇吃到食物之后,会自动加长一个格子单位,食物会自动并且随机刷新生成。如果蛇碰到墙就会游戏结束,另外蛇过长时候,自己撞到自己也会游戏结束。

6.2 大致界面的实现

public/index.html代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>贪吃蛇</title>
</head>
<body>
 <div id="main">
<!--   设置游戏的舞台-->
  <div id="stage">
<!---->
     <div id="snake">
         <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>

src/style/index.less代码如下:

//设置变量
@bg-color:#b7d4a8;

//清除默认的样式
*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body{
  font:bold 20px "Courier";
}
//设置主窗口的样式
#main{
  width: 360px;
  height: 420px;
  background-color: @bg-color;
  margin: 100px auto;
  border: 10px solid black;
  border-radius: 40px;
  display: flex;
  //设置主轴方向
  flex-flow: column;
  //设置侧轴对齐方式
  align-items:center ;
  //设置主轴对齐方式
  justify-content: space-around;
}
//游戏舞台
#stage{
  width: 304px;
  height: 304px;
  border: 2px solid black;
  //开启相对定位
  position: relative;
  //设置蛇的样式
  #snake{
    &>div{
      width: 10px;
      height: 10px;
      background-color: black;
      border: 1px solid @bg-color;

      //开启绝对定位
      position: absolute;
    }
  }

  #food{
    width: 10px;
    height: 10px;
    display: flex;
    flex-flow: row wrap;
    justify-content: space-between;
    align-content: space-between;
    //开启绝对定位
    position: absolute;
    left: 40px;
    top: 100px;
    &>div{
      width: 4px;
      height: 4px;
      background-color: black;
      transform: rotate(45deg);
    }
  }
}

//积分牌
#score-panel{
  width: 300px;
  display: flex;
  justify-content: space-between;
}

然后在src/index.ts中引入这个index.less文件:
在这里插入图片描述

7 实现项目逻辑

src中新建modules文件夹,里面放置实现项目逻辑的各种ts文件。
在这里插入图片描述

7.1 完成Food类

modules中新建一个Foods.ts文件:

class Food {
  //定义一个属性表示食物对应的元素
  element: HTMLElement;

  constructor() {
    //获取页面中食物元素,非空断言
    this.element = document.getElementById("food")!;
  }

  // 获取食物的x坐标
  get X() {
    return this.element.offsetLeft;
  }

  // 获取食物的y坐标
  get Y() {
    return this.element.offsetTop;
  }

  //修改食物的位置
  change() {
    //生成随机位置
    //食物位置最小为0,最大为290
    let left = Math.round(Math.random() * 29) * 10;
    let top = Math.round(Math.random() * 29) * 10;

    this.element.style.left = left + "px";
    this.element.style.top = top + "px";
  }
}
// 测试代码
// const food = new Food();
// food.change()

export default Food;

7.2 完成ScorePanel类

modules中新建一个ScorePanel.ts文件:

class ScorePanel {
    score = 0;
    level = 1;

    //设置变量限制等级
    maxLevel:number;

    //设置变量表示多少分升级
    upScore:number;

    scoreElement: HTMLElement;

    levelElement: HTMLElement;

    constructor(maxLevel:number=10,upScore:Number=10) {
        //获取页面中食物元素,非空断言
        this.scoreElement = document.getElementById("score")!;
        this.levelElement = document.getElementById("level")!;
        this.maxLevel=maxLevel;
        this.upScore=maxLevel;
    }

    //加分方法
    addScore() {
        this.scoreElement.innerHTML = ++this.score + "";
        //判断分数
        if(this.score%this.upScore==0){
            this.levelUp();
        }
    }

    // 提升等级方法
    levelUp() {
        if (this.level < this.maxLevel) {
            this.levelElement.innerHTML = ++this.level + "";
        }
    }
}

// 测试代码
// const scorePanel = new ScorePanel();
export default ScorePanel;

7.3 完成Snake类

modules中新建一个Snake.ts文件:

class Snake {
    //蛇头元素
    head:HTMLElement;

    //蛇的身体(包括蛇头)
    bodies:HTMLCollection;

    //获取蛇的容器
    element:HTMLElement;

    constructor() {
        this.element=document.getElementById("snake")!;
        this.head=document.querySelector("#snake > div") as HTMLElement;
        this.bodies=document.getElementById("snake")!.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 (value<0||value>290){
          throw new Error("蛇撞墙了!");
        }
        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();
    }
    //设置蛇的坐标y(蛇头)
    set Y(value:number) {
        if(this.Y===value){
            return;
        }
        if (value<0||value>290){
            throw new Error("蛇撞墙了!");
        }
        if(this.bodies[1]&& (this.bodies[1] as HTMLElement).offsetTop===value){
            if(value>this.Y){
                value=this.Y-10;
            }else{
                value=this.Y+10;
            }
        }
        this.moveBody();
        this.head.style.top = value + "px";
        this.checkHeadBody();
    }

    //蛇增加身体
    addBody(){
        this.element.insertAdjacentHTML("beforeend","<div></div>")
    }

    //蛇移动身体
    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';
        }

    }

    //检查头部与身体是否相撞
    checkHeadBody(){
        for (let i=1;i<this.bodies.length;i++){
            let bd=this.bodies[i] as HTMLElement
            if(this.X===bd.offsetLeft&&this.Y==bd.offsetTop){
                throw new Error("撞到自己了")
            }
        }
    }
}

export default Snake;

7.4 完成GameControl类

modules中新建一个GameControl.ts文件:

import Food from "./Food";
import ScorePanel from "./ScorePanel";
import Snake from "./Snake";

//游戏控制器,控制其他的类

class GameControl {
  snake: Snake;

  food: Food;

  scorePanel: ScorePanel;

  //蛇的移动的方向
  direction: string = "";

  //判断蛇是否存活
  isLive = true;

  constructor() {
    this.snake = new Snake();

    this.food = new Food();

    this.scorePanel = new ScorePanel();

    this.init();
  }

  //游戏初始化方法,调用后游戏即开始
  init() {
    //绑定键盘按下的事件
    document.addEventListener("keydown", this.keyDownHandler.bind(this)); //注意this指向
    this.run();
  }

  //创建一个键盘按下的响应函数
  keyDownHandler(event: KeyboardEvent) {
    this.direction = event.key;
  }

  //蛇移动的方法
  run() {
    //获取蛇现在的坐标
    let X = this.snake.X;
    let Y = this.snake.Y;
    switch (this.direction) {
      case "ArrowUp":
      case "Up":
        Y -= 10;
        break;
      case "ArrowDown":
      case "Down":
        Y += 10;
        break;
      case "ArrowLeft":
      case "Left":
        X -= 10;
        break;
      case "ArrowRight":
      case "Right":
        X += 10;
        break;
    }
    this.checkEat(X, Y);

    try {
      this.snake.X = X;
      this.snake.Y = Y;
    } catch (e: any) {
      alert(e.message + " GAME OVER!");
      this.isLive = false;
    }
    this.isLive &&
      setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);
  }

  // 检查蛇是否吃到了食物
  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;

7.5 在入口文件引入逻辑模块

src/index.ts中引入GameControl.ts
在这里插入图片描述
运行后如下:
在这里插入图片描述

一个模拟贪吃蛇游戏的项目就此完毕!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值