TypeScript基础案例demo(一)

158 阅读7分钟

学完TS基础,用一个贪吃蛇的小案例来巩固一下自己学习的知识点。 coding,可以看一哈视频: www.bilibili.com/video/BV1Xy…

1. 项目搭建

1.1 项目配置信息

  1. 使用webpack打包工具管理项目
    1. 创建新项目安装项目所需的依赖
npm install --save-dev webpack@5.6.0
npm install --save-dev webpack-cli@4.10.0
npm install --save-dev typescript@4.1.2
npm install --save-dev ts-loader@8.0.1
npm install --ave-dev html-webpack-plugin@4.5.0 // 自定编译html的插件
npm install --save-dev webpack-dev-server@3.11.0 // 用于配置自动启动浏览器打包
npm install --save-dev clean-webpack-plugin@4.0.0 // 每次编译前将dist文件夹清空
// 用于支持Babel的插件,做浏览器兼容处理
npm install --save-dev @babel/core@7.12.9 
npm install --save-dev @babel/preset-env@7.12.7 
npm install babel-loader@8.2.2 --save-dev 
npm install core-js@3.8.0 --save-dev
// 安装预处理css的sass-loader
npm install --save-dev @node-sass@7.0.1
npm install --save-dev @sass-loader@13.0.2
npm install --save-dev @style-loader@3.3.1
npm install --save-dev @css-loader@6.7.1
// 对css属性兼容性的loader
npm install --save-dev postcss@8.4.14
npm install --save-dev @postcss-loader@7.0.1
npm install --save-dev @postcss-preset-env@7.7.2
  1. webpack.config.js配置文件
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  mode: "production",
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: [
          // 配置Babel
          {
            // 指定加载器
            loader: "babel-loader",
            // 设置babel
            options: {
              // 设置预定于的环境
              presets: [
                [
                  // 指定环境插件
                  "@babel/preset-env",
                  // 配置信息
                  {
                    targets: {
                      edge: "12",
                      firefox: "60",
                      chrome: "60",
                      safari: "11.1",
                      ie: "11",
                    },
                    // 使用corejs的方式, usage表示按需加载
                    useBuiltIns: "usage",
                    // 指定corejs的版本
                    corejs: "3",
                  },
                ],
              ],
            },
          },
          "ts-loader",
        ],
        exclude: /node_modules/,
      },
      // 配置sass
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          "css-loader",
          // 配置 postcss 处理css兼容性问题
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-preset-env",
                    {
                      browsers: "last 2 versions",
                    },
                  ],
                ],
              },
            },
          },
          "sass-loader",
        ],
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      title: "my app",
      filename: "index.html",
      template: "./src/index.html",
    }),
    new CleanWebpackPlugin(),
  ],
  // 用来设置引用模块
  resolve: {
    extensions: [".js", ".ts"],
  },
};

  1. 项目tsconfig.js文件的配置
{
  "compilerOptions": {
    "target": "ES2015",
    "module": "ES2015",
    "strict": true
  },
  "include": [
    "src/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

2.项目界面

  1. 搭建项目静态的html文件
    <!--路径: ./src/index.html -->
  <div class="main">
    <!-- 设置游戏的区域 -->
    <div class="game_area">
      <!-- 设置蛇 -->
      <div class="snake">
        <div class="snake_item"></div>
      </div>
      <!-- 设置食物 -->
      <div class="foods" id="foods">
        <div></div>
        <div></div>
        <div></div>
        <div></div>
      </div>
    </div>
    <!-- 设置游戏的提示区域 -->
    <div class="game_prompt">
      <div class="prompt_left">
        <span>SCORE:</span>
        <span class="score" id="score">0</span>
      </div>
      <div class="prompt_right">
        <span>LEVEL:</span>
        <span class="level" id="level">1</span>
      </div>
    </div>
  </div>
  1. 项目scss样式文件
// 设置变量
$bg-color: #b7d4a8;
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.main {
  width: 360px;
  height: 420px;
  background-color: $bg-color;
  margin: 100px auto;
  border: 10px solid black;
  border-radius: 40px;
  .game_area {
    width: 304px;
    height: 304px;    
    border: 2px solid black;
    margin: 10px auto;
    position: relative;
    .snake {
      .snake_item {
        width: 10px;
        height: 10px;
        background-color: #000;
        border: 1px solid $bg-color;
        position: absolute;
      }
    }
    #foods {
      width: 10px;
      height: 10px;
      position: absolute;
      left: 40px;
      top: 100px;
      display: flex;
      flex-wrap: wrap;
      justify-content: space-evenly;
      &>div {
        width: 4px;
        height: 4px;
        background-color: #000;
        transform: rotate(45deg);
      }
    }
  }
  .game_prompt {
    display: flex;
    justify-content: space-between;
    padding: 30px;
    span {
      font-size: 18px;
      font-weight: 600;
    }
  }
}
  1. 页面效果图 image.png

3. 项目中所需类的定义

3.1 定义页面中小方块食物类foods

// 页面路径 ./src/module/foods.ts
// 添加一个食物类
class Food {
  // 定义一个属性表示食物所对应的元素
  element: HTMLElement;
  constructor() {
    // 将页面的foods元素给类的属性
    this.element = document.getElementById("foods")!;
  }
  // 定义食物元素 在页面的X Y 坐标
  get X() {
    return this.element.offsetLeft;
  }
  get Y() {
    return this.element.offsetTop;
  }
  // 食物的位置是需要改变的,定义修改食物位置的方法
  /**
   * 生成随机的数字需要考虑的问题:
   *  1. 生成的随机数应该是10的倍数
   *  2. 蛇身体每次移动应该是以10px的距离移动
   */
  change() {
    const stepX = Math.round(Math.random() * 29) * 10;
    const stepY = Math.round(Math.random() * 29) * 10;
    this.element.style.left = stepX + "px";
    this.element.style.top = stepY + "px";
  }
}

export { Food };

3.2 定义页面中积分,等级类scorePanel

// 路径: ./src/module/scorePanel.ts
// 定义一个类,用于表示分数,等级
class Panel {
  score = 0;
  level = 1;
  scoreElement: HTMLElement;
  levelElement: HTMLElement;
  // 设置一个限制等级的变量
  maxLevel: number;
  // 设置一个到达分数升级的变量
  upLevel: number;
  constructor(maxLevel: number = 10, upLevel: number = 10) {
    this.scoreElement = document.getElementById("score")!;
    this.levelElement = document.getElementById("level")!;
    // this.scoreElement = document.querySelector(".score")!;
    // this.levelElement = document.querySelector(".level")!;
    this.maxLevel = maxLevel;
    this.upLevel = upLevel;
  }
  // 获取元素内容的方法
  get scoreValue() {
    return this.scoreElement.innerHTML;
  }
  get levelValue() {
    return this.levelElement.innerHTML;
  }
  // 修改元素内容的方法
  changeScore() {
    this.score++;
    this.scoreElement.innerHTML = this.score + "";
    // 判断如果分数是10的倍数时,升一级
    if (this.score % this.upLevel === 0) {
      this.changeLevel();
    }
  }
  changeLevel() {
    if (this.level < this.maxLevel) {
      this.level++;
      this.levelElement.innerHTML = this.level + "";
    }
  }
}
export { Panel };

3.4 定义面板中snake类

class Snake {
  // 蛇的容器
  element: HTMLElement;
  // 定义蛇头
  head: HTMLElement;
  // 身体
  body: HTMLCollection;
  constructor() {
    this.element = document.getElementById("snake")!;
    this.head = document.querySelector(".snake>div") as HTMLElement;
    this.body = document.getElementById("snake")!.getElementsByTagName("div");
  }
  // 获取蛇头的X,Y坐标
  get X() {
    return this.head.offsetLeft;
  }
  get Y() {
    return this.head.offsetTop;
  }
  // 设置蛇头的X,Y坐标
  set X(value: number) {
    this.head.style.left = value + "px";
  }
  set Y(value: number) {
    this.head.style.top = value + "px";
  }
  // 增加身体的方法
  addBody() {
    // 向蛇的容器中添加一个元素
    /**
     * insertAdjacentHTML方法
     * 方法名称:insertAdjacentHTML(where,html)
     * where:插入位置。包括beforeBegin,beforeEnd,afterBegin,afterEnd。
     * html:要插入的html代码
     */
    this.element.insertAdjacentHTML("beforeend", "<div class='snake_item'></div>");
  }
}

export { Snake };

3.5 控制游戏区域,控制所有其他类

// 文件路径: ./src/module/gameControl.ts
// 引入其他类
import { Foods } from "./foods";
import { Snake } from "./snake";
import { Panel } from "./scorePanel";

// 定义游戏控制的类
class GameControl {
  // 定义蛇的属性 , 定义键盘按下要存储的值
  snake: Snake;
  foods: Foods;
  panel: Panel;
  direction: string = "";
  // 构造方法
  constructor() {
    this.snake = new Snake();
    this.foods = new Foods();
    this.panel = new Panel();
    // 调用游戏开始的方法
    this.init();
  }
  // 游戏的初始化方法,调用后游戏开始
  init() {
    // 绑定键盘按键按下的事件
    document.addEventListener("keydown", this.keydownHandler.bind(this));
  }
  // 键盘按下的回调函数
  keydownHandler(event: KeyboardEvent) {
    // event.key 可以获取到那个键盘按键按下
    this.direction = event.key;
  }
  // init() {
  //   document.addEventListener("keydown", (event) => {
  //     this.direction = event.key;
  //     console.log(this);
  //   });
  // }
}

export { GameControl };

3.5 在入口index.ts文件中引入

// 路径: ./src/index.ts
import "../style/css/index.scss";
import { GameControl } from "./module/gameControl";

3.6 游戏面板中蛇移动

  1. 根据键盘按下的方向,this.direction来使蛇位置改变
    1. ArrowUp top值减少
    2. ArrowDown top值增加
    3. ArrowLeft left值减少
    4. ArrowRight left值增加
  2. 设置定时器,调用蛇移动的函数
    1. 随着游戏等级的提高,增加调用蛇移动的函数
    2. 在定时器调用蛇移动的函数时要先判断游戏有没有over
// 文件路径: ./src/module/gameControl.ts
// 引入其他类
import { Foods } from "./foods";
import { Snake } from "./snake";
import { Panel } from "./scorePanel";

// 定义游戏控制的类
class GameControl {
  // 定义蛇的属性 , 定义键盘按下要存储的值
  snake: Snake;
  foods: Foods;
  panel: Panel;
  direction: string = "";
  // 创建一个属性用来标记游戏是不是结束
  isLive: boolean = false;
  // 构造方法
  constructor() {
    this.snake = new Snake();
    this.foods = new Foods();
    this.panel = new Panel();
    // 调用游戏开始的方法
    this.init();
  }
  // 游戏的初始化方法,调用后游戏开始
  init() {
    // 绑定键盘按键按下的事件
    document.addEventListener("keydown", this.keydownHandler.bind(this));
    // 调用蛇移动的方法
    this.run();
  }
  // 键盘按下的回调函数
  keydownHandler(event: KeyboardEvent) {
    this.direction = event.key;
  }
  /**
   *  创建一个函数,控制蛇移动的方法
   *      根据键盘按下的方向,this.direction来使蛇位置改变
   *      ArrowUp top值减少
   *      ArrowDown top值增加
   *      ArrowLeft left值减少
   *      ArrowRight left值增加
   */
  run() {
    // 获取蛇当前的坐标
    let X = this.snake.X;
    let Y = this.snake.Y;
    // 通过键盘按下获取的值判断
    switch (this.direction) {
      case "ArrowUp":
        // 设置snake的top值
        this.snake.Y -= 10;
        break;
      case "ArrowDown":
        this.snake.Y += 10;
        break;
      case "ArrowLeft":
        this.snake.X -= 10;
        break;
      case "ArrowRight":
        this.snake.X += 10;
        break;
    }
    // 修改蛇移动的位置
    this.snake.X = X
    this.snake.Y = Y
    // 设置定时器
    setTimeout(() => {
      // 判断游戏是不是结束
      if (this.isLive) {
        this.run();
      }
      // 随着等级的提升,速度加快
    }, 300 - (this.panel.level - 1) * 30);
  }
}
export { GameControl };

3.7 蛇移动过程中撞墙和吃东西检测

  1. 在snake类中对蛇头属性X,Y也就是(X轴坐标和Y轴坐标)的值进行判断
    1. 判断蛇移动过程中传入的X值没有改变,则不修改X的值
    2. 判断蛇移动过程中传入的Y值没有改变,则不修改Y的值
    3. 判断移动过程中传入的X,Y值(X轴Y轴坐标)合不合法,也就是说是不是在游戏区域内。(0-290之间为合法)
    4. 如果不合法就是撞墙了,抛出异常
  • snake类中的改变,文件路径: ./src/module/snake.ts

image.png

  • gameControl类中的改变,文件路径: ./src/module/snake.ts

image.png

  1. 检查蛇是不是吃到了食物
    1. 在gameControl类中增加一个检查有没有吃到食物的方法
    2. 通过传入的参数X,Y,来判断有没有和食物X,Y坐标重合,重合,说明吃到了食物
    3. 吃到食物后需要做三件事情
    4. (1) 生成新食物 (2) 蛇身体增加一截 (3) 当前记分板分数+1
  2. 更改代码截图,路径: ./src/module/gameControl.ts image.png

3.8 处理蛇移动过程中X,Y方向掉头问题及蛇头碰到蛇身问题

  1. 判断掉头的问题
    1. 当蛇有两截且第二截移动的距离等于蛇头移动的距离坐标时,说明掉头了
    2. 若发生掉头,让蛇反方向移动
    3. 改变当前的坐标
  2. 修改代码 路径 ./src/module/snake.ts image.png)
  3. 判断蛇头碰到蛇身问题
    1. 定义一个检查碰撞的函数
    2. 循环遍历身体的长度
    3. 通过判断每一截身体的坐标与当前蛇头的坐标有没有重叠
    4. 有重叠说明蛇头已经碰到身体的某一截
    5. 最后,在修改蛇头坐标后调用检查碰撞的函数 4.代码更改,路径./src/module/snake.ts

image.png