• TypeScript 贪吃蛇游戏详细教程


    前几篇博文学习了TypeScript的语法以及TypeScript的工程化实现方案,但是很多人学完了TypeScript的知识点后却仍然还在项目里面写大量js,并没有领悟TypeScript的思维(面向对象的思维)。所以今天我准备用TypeScript来开发一个贪吃蛇的游戏,我尽量把实现步骤写得详细一点。大家如果感兴趣的话,可以跟着这篇博文一起敲,这样也有利于熟练TypeScript的语法和领悟它的思维。这篇博文我真的写了很久,很认真,所以求关注、求点赞,求评论,求收藏,这对我真的很重要!

    一.项目搭建

    新建一个叫 greedy-snake 的项目文件夹,然后初始化一下项目:

    npm init -y
    
    • 1

    我们这个项目使用webpack来打包构建,所以需要安装webpack的相关的一些依赖,把webpack和TypeScript等一些东西整合一下,以方便我们后面整个项目的开发。

    要安装的包如下,运行如下的命令即可:

    npm i -D webpack webpack-cli typescript ts-loader webpack-dev-server style-loader postcss-preset-env postcss-loader postcss less-loader less html-webpack-plugin css-loader core-js clean-webpack-plugin babel-loader @babel/preset-env @babel/core
    
    • 1

    然后运行如下命令生成 tsconfig.json 配置文件:

    tsc --init
    
    • 1

    我们把生成的 tsconfig.json 里面的内容都删除掉,编写如下配置(至于这些配置的作用,我上一篇博文都有讲解):

    {
      "compilerOptions": {
        "module": "ES2015",
        "target": "ES2015",
        "strict": true,
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们在项目根目录下创建src目录,然后在里面创建一个index.html和index.ts文件 ,src目录下创建一个style目录用于存放样式文件,style目录里也创建一个index.less文件,最后src目录里还要创建一个modules文件夹,里面用于存放一些ts模块。

    然后我们还要在根目录下创建一个webpack.config.js文件,来配置一下webpack:

    // 引入一个包
    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不使用箭头
            environment:{
                arrowFunction: false,
                const: false
            }
        },
    
        // 指定webpack打包时要使用模块
        module: {
            // 指定要加载的规则
            rules: [
                {
                    // test指定的是规则生效的文件
                    test: /\.ts$/,
                    // 要使用的loader
                    use: [
                        // 配置babel
                        {
                            // 指定加载器
                            loader:"babel-loader",
                            // 设置babel
                            options: {
                                // 设置预定义的环境
                                presets:[
                                    [
                                        // 指定环境的插件
                                        "@babel/preset-env",
                                        // 配置信息
                                        {
                                            // 要兼容的目标浏览器
                                            targets:{
                                                "chrome":"58",
                                                "ie":"11"
                                            },
                                            // 指定corejs的版本
                                            "corejs":"3",
                                            // 使用corejs的方式 "usage" 表示按需加载
                                            "useBuiltIns":"usage"
                                        }
                                    ]
                                ]
                            }
                        },
                        'ts-loader'
                    ],
                    // 要排除的文件
                    exclude: /node-modules/
                },
                // 设置less文件的处理
                {
                    test: /\.less$/,
                    use:[
                        "style-loader",
                        "css-loader",
                        // 引入postcss
                        {
                            loader: "postcss-loader",
                            options: {
                                postcssOptions:{
                                    plugins:[
                                        [
                                            "postcss-preset-env",
                                            {
                                                browsers: 'last 2 versions'
                                            }
                                        ]
                                    ]
                                }
                            }
                        },
                        "less-loader"
                    ]
                }
            ]
        },
        // 配置Webpack插件
        plugins: [
            new CleanWebpackPlugin(),
            new HTMLWebpackPlugin({
                // title: "这是一个自定义的title"
                template: "./src/index.html"
            }),
        ],
        // 用来设置引用模块
        resolve: {
            extensions: ['.ts', '.js']
        },
        //打包模式,是生产模式production还是开发模式development
        mode: "development"
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    接下来,我们还需要在package.json里面添加两行配置来用于命令行打包和启动项目:

      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack",
        "start": "webpack serve"
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    最终的项目结构如下:

    	 ├── node_modules
         ├── src
         │   ├── modules
         │	 ├── style
         │   │		└── index.ts
         │   ├── index.html
         │   └── index.ts
         ├── package.json
         ├── webpack.config.js
         └── tsconfig.json
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    二.编写游戏界面

    游戏界面如下图所示:
    在这里插入图片描述
    我们先要来编写index.html:

    DOCTYPE html>
    <html lang="zh">
    <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 id="score-panel">
            <div>
                SCORE:<span id="score">0span>
            div>
            <div>
                level:<span id="level">1span>
            div>
        div>
    div>
    
    body>
    
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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: #000;
            border: 1px solid @bg-color;
            // 开启绝对定位
            position: absolute;
          }
        }
    
        // 设置食物
        #food{
          width: 10px;
          height: 10px;
          position: absolute;
          left: 40px;
          top: 100px;
    
          // 开启弹性盒
          display: flex;
          // 设置横轴为主轴,wrap表示会自动换行
          flex-flow: row wrap;
    
          // 设置主轴和侧轴的空白空间分配到元素之间
          justify-content: space-between;
          align-content: space-between;
    
          &>div{
            width: 4px;
            height: 4px;
            background-color: black;
    
            // 使四个div旋转45度
            transform: rotate(45deg);
          }
        }
      }
    
      // 记分牌
      #score-panel{
        width: 300px;
        display: flex;
        // 设置主轴的对齐方式
        justify-content: space-between;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    上面虽然编写了index.html页面和index.less样式,但是我们并没有在任何地方引用index.less。这时候如果运行命令编译打包项目的话,其实并不能把游戏界面显示出来。

    我们先要在index.ts里面引入index.less样式文件,只有这样,打包时才会对index.less打包进去。

    index.ts编写如下的代码来引入样式:

    // 引入样式
    import './style/index.less';
    
    • 1
    • 2

    最后,我们运行项目:

    npm run start
    
    • 1

    浏览器上访问http://localhost:8080/就可以查看到效果了。

    三.编写食物模块

    这一节来编写食物模块,我们既然要学习TypeScript的面向对象的思想,就要用类来写这些模块。并且,我们不应该把食物类写在index.ts里面,因为这样不好维护。

    所以我们应该在src下的modules目录下面创建一个Food.ts来单独编写食物模块。

    Food.ts的代码如下(具体实现思路,注释里面都有写):

    // 定义食物类Food
    class Food{
        // 定义一个属性表示食物所对应的元素
        element: HTMLElement;
    
        constructor() {
            // 获取页面中的food元素并将其赋值给element
            this.element = document.getElementById('food')!;
        }
    
        // 定义一个获取食物X轴坐标的方法
        get X(){
            return this.element.offsetLeft;
        }
    
        // 定义一个获取食物Y轴坐标的方法
        get Y(){
            return this.element.offsetTop;
        }
    
        // 修改食物的位置
        change(){
            // 生成一个随机的位置
            // 食物的位置最小是0 最大是290
            // 蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是整10
    
            let top = Math.round(Math.random() * 29) * 10;
            let left = Math.round(Math.random() * 29) * 10;
    
            this.element.style.left = left + 'px';
            this.element.style.top = top + 'px';
        }
    }
    
    export default Food;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    我们在index.ts里面引入Food.ts,然后测试一下:

    // 引入样式
    import './style/index.less';
    import Food from "./modules/Food";
    
    //测试代码食物,最后记得注释掉
    const food =  new Food();
    food.change();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们每次刷新浏览器页面都会看到食物的位置发生了改变。

    四.初步编写蛇模块

    因为蛇地模块,涉及的东西比较多,比如蛇的位置、蛇的身体、蛇的移动、蛇吃东西、蛇撞墙等等。这一节,先初步地编写一下蛇模块,到后面小节再来完善蛇的其他功能。

    我们先创建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 = this.element.getElementsByTagName('div');
        }
    
        // 获取蛇的坐标(蛇头坐标)
        get X(){
            return this.head.offsetLeft;
        }
    
        // 获取蛇的Y轴坐标
        get Y(){
            return this.head.offsetTop;
        }
    
        // 设置蛇头的X坐标
        set X(value){
            this.head.style.left = value + 'px'
        }
    
        // 设置蛇头的Y坐标
        set Y(value){
            this.head.style.top = value + 'px'
        }
    
        // 蛇增加身体的方法
        addBody(){
            // 向element中添加一个div
            this.element.insertAdjacentHTML("beforeend", "
    "
    ); } } export default Snake;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    关于上面的代码,有一个方法大家可能用得比较少,就是insertAdjacentHTML这个方法。下面来对这个方法简单解释一下:

    insertAdjacentHTML() 是Element的API中的一个方法,可以将字符串文本转化为你想要的节点(Node),并且插入到你想要插入的位置中。而且它并不会向innerHTML一样会替换掉已有的节点,而是会插入到指定位置。

    使用:element.insertAdjacentHTML(position,text)

    position参数
    position就是想要插入的位置,一共有如下的4个固定的值

    beforebegin:元素element自己的前面。
    afterbegin:插入到元素element里面的第一个子节点之前(也就是总是会插入到最前面,例如我插入5个节点,顺序是1、2、3、4、5,那么我就需要以5、4、3、2、1的顺序插入,有一种栈结构先进后出的感觉)。
    beforeend:插入元素element里面的最后一个子节点之后(这个比较容易理解,就是插入到最后一个节点后,例如我插入5个节点,顺序是1、2、3、4、5,那就正常的1、2、3、4、5就好啦,但是注意是在已有节点的后面哦)。
    afterend:元素element自己的后面。

    text参数
    参数便是你想要插入的HTML元素,可以是字符串形式,也可以用ES6新增的模板字符串的形式。

    五.编写计分盘模块

    我们本节,创建ScorePanel.ts,它的代码很简单,如下:

    // 定义表示记分牌的类
    class ScorePanel{
        // score和level用来记录分数和等级
        score = 0;
        level = 1;
    
        // 分数和等级所在的元素,在构造函数中进行初始化
        scoreEle: HTMLElement;
        levelEle: HTMLElement;
    
        // 设置一个变量限制最高等级
        maxLevel: number;
        // 设置一个变量表示多少分时升级
        upScore: number;
    
        constructor(maxLevel: number = 10, upScore: number = 10) {
            this.scoreEle = document.getElementById('score')!;
            this.levelEle = document.getElementById('level')!;
            this.maxLevel = maxLevel;
            this.upScore = upScore;
        }
    
        //设置一个加分的方法
        addScore(){
            // 使分数自增
            this.score = this.score+1;
            this.scoreEle.innerHTML = this.score + '';
            // 判断分数是多少
            if(this.score % this.upScore === 0){
                this.levelUp();
            }
        }
    
        // 提升等级的方法
        levelUp(){
            if(this.level < this.maxLevel){
                this.levelEle.innerHTML = ++this.level + '';
            }
        }
    }
    
    export default ScorePanel;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    其实逻辑非常简单,记分盘有分数和等级两个数据。我们要思考的是分数增加到多少的时候升级,多少级是最高级。这里我们定义了maxLevel变量来限制最高等级,设置一个变量upScore表示吃到多少分时升级。

    六.游戏控制模块的开发

    我们创建GameControl.ts ,我们这节开始编写和游戏控制有关的代码,里面都是控制游戏的核心代码。

    GameControl.ts代码如下:

    // 引入其他的类
    import Snake from "./Snake";
    import Food from "./Food";
    import ScorePanel from "./ScorePanel";
    
    // 游戏控制器,控制其他的所有类
    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(10,5);
    
            this.init();
        }
    
        // 游戏的初始化方法,调用后游戏即开始
        init() {
            // 绑定键盘按键按下的事件
            //要用bind()方法绑定到GameControl对象上,否则直接this.keydownHandler()则不会触发函数,因为绑定的是document对象
            document.addEventListener('keydown', this.keydownHandler.bind(this));
            // 调用run方法,使蛇移动
            this.run();
        }
    
        /*
        *   ArrowUp  Up
            ArrowDown Down
            ArrowLeft Left
            ArrowRight Right
        * */
    
        // 创建一个键盘按下的响应函数
        keydownHandler(event: KeyboardEvent) {
            // 需要检查event.key的值是否合法(用户是否按了正确的按键)
            // 修改direction属性
            this.direction = event.key;
        }
    
        // 创建一个控制蛇移动的方法
        run() {
            /*
            *   根据方向(this.direction)来使蛇的位置改变
            *       向上 top 减少
            *       向下 top 增加
            *       向左  left 减少
            *       向右  left 增加
            * */
            // 获取蛇现在坐标
            let X = this.snake.X;
            let Y = this.snake.Y;
    
    
            // 根据按键方向来修改X值和Y值
            switch (this.direction) {
                case "ArrowUp":
                case "Up":
                case "w":
                    // 向上移动 top 减少
                    Y -= 10;
                    break;
                case "ArrowDown":
                case "Down":
                case "s":
                    // 向下移动 top 增加
                    Y += 10;
                    break;
                case "ArrowLeft":
                case "Left":
                case "a":
                    // 向左移动 left 减少
                    X -= 10;
                    break;
                case "ArrowRight":
                case "Right":
                case "d":
                    // 向右移动 left 增加
                    X += 10;
                    break;
            }
    
            // 检查蛇是否吃到了食物
            this.checkEat(X, Y);
    
            //修改蛇的X和Y值
            try{
                this.snake.X = X;
                this.snake.Y = Y;
            }catch (e){
                // 进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
                alert(e+' GAME OVER!');
                // 将isLive设置为false
                this.isLive = false;
            }
    
    
            // 开启一个定时调用
            this.isLive && setTimeout(this.run.bind(this), 300 -(this.scorePanel.level-1)*25);
    
        }
    
        // 定义一个方法,用来检查蛇是否吃到食物
        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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    蛇的其他一些逻辑和Snake.ts完整代码如下(不懂评论区再来问我):

    蛇身体的移动逻辑也非常简单,就是蛇后一个身体部分的位置要移动到前一个身体部分的位置。

    我们移动时要先改最后一节身体的位置,从后面部分往前改。因为前面部分身体的位置如果先改了,那它原来的位置就没了,后面的部分就找不到正确的位置了。具体看上面Snake.ts的moveBody方法。

    还有就是检查蛇掉头的逻辑。关于蛇掉头的问题,有一点要注意了。当蛇拥有多节身体的时候,蛇往右边走的时候,是不能按左使他往左走的。可以先判断蛇是否有多节身体,游戏刚开始的情况下,蛇只有一节身体,这时蛇就可以往任意方向掉头移动。如果判断出蛇有多节身体的话,就要判断蛇头移动的位置是否为第二节身体的位置,如果是,则不允许掉头,不是,则允许掉头,就这么简单。

    检查蛇头是否撞到身体的逻辑也非常简单,获取所有的身体,检查其是否和蛇头的坐标发生重叠就好了。

    class Snake{
        // 表示蛇头的元素
        head: HTMLElement;
        // 蛇的身体(包括蛇头)
        bodies: HTMLCollection;
        // 获取蛇的容器
        element: HTMLElement;
    
        constructor() {
            this.element = document.getElementById('snake')!
            this.head = document.querySelector('#snake > div') as HTMLElement;
            this.bodies = this.element.getElementsByTagName('div');
        }
    
        // 获取蛇的坐标(蛇头坐标)
        get X(){
            return this.head.offsetLeft;
        }
    
        // 获取蛇的Y轴坐标
        get Y(){
            return this.head.offsetTop;
        }
    
        // 设置蛇头的坐标
        set X(value){
    
            // 如果新值和旧值相同,则直接返回不再修改
            if(this.X === value){
                return;
            }
    
            // X的值的合法范围0-290之间
            if(value < 0 || value > 290){
                // 进入判断说明蛇撞墙了
                throw new Error('蛇撞墙了!');
            }
    
            // 修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
            if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value){
                // console.log('水平方向发生了掉头');
                // 如果发生了掉头,让蛇向反方向继续移动
                if(value > this.X){
                    // 如果新值value大于旧值X,则说明蛇在向右走,此时发生掉头,应该使蛇继续向左走
                    value = this.X - 10;
                }else{
                    // 向左走
                    value = this.X + 10;
                }
            }
    
            // 移动身体
            this.moveBody();
    
            this.head.style.left = value + 'px';
            // 检查有没有撞到自己
            this.checkHeadBody();
        }
    
        set Y(value){
            // 如果新值和旧值相同,则直接返回不再修改
            if(this.Y === value){
                return;
            }
    
            // Y的值的合法范围0-290之间
            if(value < 0 || value > 290){
                // 进入判断说明蛇撞墙了,抛出一个异常
                throw new Error('蛇撞墙了!');
            }
    
            // 修改y时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
            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(){
            // 向element中添加一个div
            this.element.insertAdjacentHTML("beforeend", "
    "
    ); } // 添加一个蛇身体移动的方法 moveBody(){ /* * 将后边的身体设置为前边身体的位置 * 举例子: * 第4节 = 第3节的位置 * 第3节 = 第2节的位置 * 第2节 = 蛇头的位置 * */ // 遍历获取所有的身体 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;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131

    最后,我们还要在index.ts里面引入GameControl.ts,然后创建GameControl对象,就好了:

    // 引入样式
    import './style/index.less';
    
    import GameControl from "./modules/GameControl";
    const gameControl = new GameControl();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    运行npm run start来启动项目,在浏览器访问http://localhost:8080/就能玩了。大家学会了吗?如果有什么不懂,都可以来评论区问我,我绝对秒回。这篇博客我真的写了很久,所以求关注、求点赞,求评论,求收藏,这对我真的很重要!


    下期预告:下一篇开始我会总结Node.js的知识点。虽然我之前有学习过Node.js,但是我却没有系统且完整地总结过它的知识点,每次想用它的时侯就又忘了一些语法了,故我打算系统总结一下。大家感兴趣的话,可以关注一下我。还有就是,求点赞,求评论,求收藏,这对我真的很重要!


  • 相关阅读:
    ssm基于Html+css的音乐网站的设计与实现毕业设计源码181627
    Scratch、Python、C++,谁才是少儿编程的第一选择?
    美国指控著名安全厂商Avast 向广告商出售用户浏览数据
    四级最常用短语
    深度学习实战59-NLP最核心的模型:transformer的搭建与训练过程详解,手把手搭建与跑通
    《Linux从练气到飞升》No.30 深入理解 POSIX 信号量与生产消费模型
    专访 SPACE ID:通往 Web3 无许可域名服务协议之路
    如何正确遵守 Python 代码规范
    基于Java咖啡商品管理系统设计实现(源码+lw+部署文档+讲解等)
    扩容LVM卷导致lvm元数据丢失的恢复过程
  • 原文地址:https://blog.csdn.net/weixin_50216991/article/details/127133577