TypeScript实现贪吃蛇效果

项目参考教程:贪吃蛇案例

1.项目搭建

1.1 项目结构搭建

创建一个名为xxx的项目

项目初始化

npm init -y

安装后面需要用到的依赖,在package.json中查看

 

 

 

 

 

 

 

 

 

 

项目根目录创建名为tsconfig.json的文件并更改内容如下:

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

项目根目录创建名为twebpack.config.js的文件并更改内容如下:

//引入一个包
const path = require("path");
//引入html插件
const HTMLWebpackPlugin = require("html-webpack-plugin");
//引入clean插件
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const {options} = require("less");
//webpack中的所有配置信息都应该写在module.exports中
module.exports = {
   mode: "development",
   //指定入口文件
   entry: "./src/index.ts",
   //指定打包文件所在目录
   output: {
      //打包后文件的目录
      path: path.resolve(__dirname, "dist"),
      filename: "bundle.js",
      //告诉我们webpack不使用箭头函数
      environment: {
         arrowFunction: false,
      },
   },
   //指定webpack打包时要使用的模块
   module: {
      //指定要再加载的规则
      rules: [
         {
            //test规则生效的文件
            test: /\.ts$/,
            use: [
               //配置babel
               {
                  //指定加载器
                  loader: "babel-loader",
                  //设置babel
                  options: {
                     //设置预定义的环境
                     presets: [
                        [
                           //指定环境的插件
                           "@babel/preset-env",
                           //配置信息
                           {
                              //要兼容的目标浏览器
                              targets: {
                                 chrome: "88",
                                 ie: "11",
                              },
                              //指定corejs的版本
                              corejs: "3",
                              //按需使用
                              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"],
   },
};

创建如下目录

 

 

 

 

编写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>
      <!--食物-->
      <div id="food">
         <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>

编写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: 15px;
  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;
      flex-flow: row wrap;
      justify-content: space-between;
      align-content: space-between;
      transform: rotate(45deg);

      & > div {
        width: 4px;
        height: 4px;
        background-color: black;
      }
    }

  }

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

编写index.ts文件内容

import './style/index.less';

最终搭建项目如图所示(项目名称不做严格要求)

 

 

 

 

 

 

 

 

1.2 项目启动

在终端运行npm run build编译项目

运行npm start启动项目,如下为项目启动成功、

效果如图

 

 

 

 

 

 

 

 

 

 

2.编写Food类

index.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() * 30) * 10;
        let left = Math.round(Math.random() * 30) * 10;
        this.element.style.left = top + 'px';
        this.element.style.top = left + 'px'
    }
}

3.编写ScorePanel

在index.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.scoreEle.innerHTML = ++this.score + '';
        //判断分数是多少
        if (this.score % this.upScore === 0) {
            this.levelUp();
        }
    }

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

const scorePanel = new ScorePanel(100, 2);
for (let i = 0; i < 200; i++) {
    scorePanel.addScore();
}

运行代码效果如图

 

 

 

 

 

 

 

 

 

4.模块化

在src下创建名为modules的文件夹

 

 

 

 

 

 

 

 

 

创建Food.ts文件

将index.ts的Food类代码复制到Food.ts文件中

 

在Food.ts添加代码将模块暴露出去

export default Food;

ScorePanel类进行同样操作

在index.ts中引入模块并调用

 

5.编写snake类

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;
    }

    get Y() {
        return this.head.offsetTop;
    }

    //设置蛇头的坐标
    set X(value) {
        this.head.style.left = value + 'px';
    }

    set Y(value) {
        this.head.style.top = value + 'px';
    }

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

6.GameControoller实现

src下创建GamaController类,用来管理Food类、scorePanel类和Snake类,并将其暴露,并在index.ts中引入该模块

 

 

 

 

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

//游戏控制器 控制其他所有类
class GameController {
    //定义三个属性
    snake: Snake;
    food: Food;
    scorePanel: ScorePanel;
    //创建一个属性来存储移动方向(按键的方向)
    dirdction: string = '';

    constructor() {
        this.snake = new Snake();
        this.food = new Food();
        this.scorePanel = new ScorePanel();
        this.init();
    }

    //游戏初始化方法,调用后游戏开始
    init() {
        document.addEventListener('keydown', this.keydownhandler.bind(this));
    }

    //创建一个键盘按下的响应函数
    keydownhandler(event: KeyboardEvent) {
        //需要检查event.key的值是否合法(是否按了四个方向键)
        //修改direction属性
        this.dirdction = event.key;
    }
}

export default GameController;

6.1 GameController实现蛇的移动

在GameController中添加代码实现跑

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

//游戏控制器 控制其他所有类
class GameController {
    //定义三个属性
    snake: Snake;
    food: Food;
    scorePanel: ScorePanel;
    //创建一个属性来存储移动方向(按键的方向)
    dirdction: 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.run();
    }

    //创建一个键盘按下的响应函数
    keydownhandler(event: KeyboardEvent) {
        //需要检查event.key的值是否合法(是否按了四个方向键)
        //修改direction属性
        this.dirdction = event.key;
    }

    //控制蛇移动
    run() {
        /*
        * 根据方向(this.diretion)来使蛇的位置改变
        * 向上  top  减少
        * 向上  top  增加
        * 向左  left  减少
        * 向右  left  增加
        * */
        //获取蛇现在坐标
        let X = this.snake.X;
        let Y = this.snake.Y;
        //根据按键方向修改X值和Y值
        switch (this.dirdction) {
            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;
        }
        //修改蛇的x和Y
        this.snake.X = X;
        this.snake.Y = Y;
        //开启定时调用
        this.isLive && setTimeout(this.run.bind(this), 300 * (this.scorePanel.level - 1) * 30);//等级越高速度越快
    }
}

export default GameController;

6.2 蛇撞墙和吃食的检测

snake.ts做如下修改

 

 

 

 

 

 

 

 

 

 

 

 

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;
    }

    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('蛇撞墙了!')
        }
        this.head.style.left = value + 'px';
    }

    set Y(value) {
        //如果新值和旧值相等,则直接返回不修改
        if (this.Y === value) {
            return;
        }
        //Y的合法值为0~290之间
        if (value < 0 || value > 290) {
            //进入判断说明蛇撞墙了
            throw new Error('蛇撞墙了!')
        }
        this.head.style.top = value + 'px';
    }

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

export default Snake;

GameController.ts做如下修改

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

//游戏控制器 控制其他所有类
class GameController {
    //定义三个属性
    snake: Snake;
    food: Food;
    scorePanel: ScorePanel;
    //创建一个属性来存储移动方向(按键的方向)
    dirdction: 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.run();
    }

    //创建一个键盘按下的响应函数
    keydownhandler(event: KeyboardEvent) {
        //需要检查event.key的值是否合法(是否按了四个方向键)
        //修改direction属性
        this.dirdction = event.key;
    }

    //控制蛇移动
    run() {
        /*
        * 根据方向(this.diretion)来使蛇的位置改变
        * 向上  top  减少
        * 向上  top  增加
        * 向左  left  减少
        * 向右  left  增加
        * */
        //获取蛇现在坐标
        let X = this.snake.X;
        let Y = this.snake.Y;
        //根据按键方向修改X值和Y值
        switch (this.dirdction) {
            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);

        //修改蛇的x和Y
        try {
            this.snake.X = X;
            this.snake.Y = Y;
        } catch (e) {
            //进入catch,说明出现异常,游戏结束,弹出提示信息
            alert((e as Error).message + 'GAME OVER!');
            //将isLive设置为false
            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 GameController;

6.2 设置蛇吃食后增加身体长度

 

 

 

 

 

在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;
    }

    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("蛇撞墙了!");
        }
        //移动身体

        this.moveBody();
        this.head.style.left = value + "px";
    }


    set Y(value) {
        //如果新值和旧值相等,则直接返回不修改
        if (this.Y === value) {
            return;
        }
        //Y的合法值为0~290之间
        if (value < 0 || value > 290) {
            //进入判断说明蛇撞墙了
            throw new Error("蛇撞墙了!");
        }   //移动身体

        this.moveBody();

        this.head.style.top = value + "px";
    }

    //设置蛇增加身体的方法
    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';
        }
    }
}

export default Snake;

6.3 优化

蛇在向右走走时,不能按←键,反之亦然

6.3.1 移动方向的优化

 

 

 

 

 

 

 

 

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;
    }

    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时,在修改水平坐标,蛇在左右移动,Y轴同理,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            // console.log('水平方向掉头')
            //如果发生掉头,继续向反方向掉头
            if (value > this.X) {
                //如果新值大于旧值。则说明蛇在向右走。此时发生掉头,继续向左走
                value = this.X - 10;
            } else {
                value = this.X + 10;
            }
        }
        //移动身体

        this.moveBody();
        this.head.style.left = value + "px";
    }


    set Y(value) {
        //如果新值和旧值相等,则直接返回不修改
        if (this.Y === value) {
            return;
        }
        //Y的合法值为0~290之间
        if (value < 0 || value > 290) {
            //进入判断说明蛇撞墙了
            throw new Error("蛇撞墙了!");
        }

        //修改Y时,在修改水平坐标,蛇在上下移动,X轴同理,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            // console.log('水平方向掉头')
            //如果发生掉头,继续向反方向掉头
            if (value > this.Y) {
                //如果新值大于旧值。则说明蛇在向右走。此时发生掉头,继续向左走
                value = this.Y - 10;
            } else {
                value = this.Y + 10;
            }
        }
        //移动身体
        this.moveBody();

        this.head.style.top = value + "px";
    }

    //设置蛇增加身体的方法
    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';
        }
    }
}

export default Snake;

6.3.2 穿越身体的优化

穿越身体撞到自己游戏结束

 

 

 

 

 

 

 

 

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;
    }

    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时,在修改水平坐标,蛇在左右移动,Y轴同理,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            // console.log('水平方向掉头')
            //如果发生掉头,继续向反方向掉头
            if (value > this.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时,在修改水平坐标,蛇在上下移动,X轴同理,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            // console.log('水平方向掉头')
            //如果发生掉头,继续向反方向掉头
            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.得分测试

为了便于测试,加快升级速度,修改GameController代码

 

 

 

 

 

至此,完结撒花!!!

 

posted @ 2024-06-27 19:50  only_Memory  阅读(32)  评论(0)    收藏  举报