用 typescript 做一个贪吃蛇小游戏

typescript 做一个贪吃蛇小游戏

搭建环境

创建 tscofig.json 文件

配置如下

{
  "compilerOptions": {
    "target": "es2015",
    "module": "es2015",
    "strict": true,
    "outDir": "./dist",
    "noEmitOnError": true
  }
}

创建 webpack.config.js 文件

  1. 安装 webpack

    npm i webpack webpack-cli -D
    
  2. 配置
    配置 webpack 项目工程化,配置项目运行打包,兼容,处理 .ts .css .less html 文件

  3. 安装插件

    npm i html-webpack-plugin webpack-dev-server -D
    
    webpack-dev-server 作用

    webpack 内部服务器,可以在开发阶段项目自动运行,比如修改了代码,会检测到改动并且自动运行,不需要每次都手动运行查看,利于开发效率

    html-webpack-plugin

    html 插件,可用来提供 html 模板,打包后的 dist 文件中的 html 会根据这个模板生成

配置如下

const path = require('path')
// html 插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlPlugin = new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: './index.html'
})

module.exports = {
    mode: 'development',
    entry: './src/index.ts',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        environment: {
            // 兼容 IE ,禁止使用箭头函数
            arrowFunction: false,
            // 兼容 IE ,禁止使用es6语法
            const: false
        }
    },
    module: {
        rules: [{
                test: /.ts$/,
                use: [
                    // 配置babel
                    {
                        // 指定加载器
                        loader: 'babel-loader',
                        // 设置 babel
                        options: {
                            // 设置预定义环境
                            presets: [
                                [
                                    // 指定环境插件
                                    "@babel/preset-env",
                                    // 配置信息
                                    {
                                        // 要兼容的目标浏览器
                                        targets: {
                                            "chrome": "58"
                                        },
                                        // 指定 corejs 版本
                                        "corejs": "3",
                                        // 使用 corejs 的方式"usage"表示按需加载
                                        "useBuiltIns": "usage"
                                    }
                                ]
                            ]
                        }
                    }, 'ts-loader'
                ],
                exclude: /node_modules/
            },
            {
                test: /.less$/,
                use: [
                    "style-loader",
                    "css-loader",
                    // 引入 postcss 
                    {
                        loader: "postcss-loader",
                        options: {
                            postcssOptions: {
                                plugins: [
                                    [
                                        "postcss-preset-env",
                                        {
                                            browsers: 'last 2 versions'
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]
            }
        ]
    },
    plugins: [htmlPlugin],
    resolve: {
        extensions: ['.ts', '.js']
    }
}

创建 package.json 文件

在项目名称是英文的情况下使用

npm init -y

如果项目名称不是英文,使用如下命令,给它设置一个英文名称

npm init

安装项目所需的全部开发依赖,如下

在这里插入图片描述

配置就说这么多,下面开始项目代码

采用的是结构与数据分离

先布局基本样式
在项目根目录下创建 src 文件夹,在src 文件夹下创建 index.html 和 index.ts 文件

./src/index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇</title>
</head>

<body>
    <div class="box">
        <div class="top">
            // 蛇
            <div id="snake">
                // 蛇的每一节
                <div></div>
            </div>
            // 食物
            <div id="food"></div>
        </div>
        // 记分牌
        <div class="bottom">
            <div>SCORE:<span id="score">3</span></div>
            <div>LEVEL:<span id="level">1</span></div>
        </div>
    </div>
</body>

</html>

在 src 目录下创建 style 文件夹存放样式文件 index.less

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font: bold 20px "Courier";
}

.box {
  width: 360px;
  height: 420px;
  background-color: #b7d4a8;
  border: 10px solid #000;
  margin: 100px auto;
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
}

.top {
  width: 304px;
  height: 304px;
  border: 2px solid #000;
  position: relative;
}

.bottom {
  width: 304px;
  display: flex;
  justify-content: space-between;
}

#snake {
  position: relative;
}

#snake div {
  width: 10px;
  height: 10px;
  background-color: black;
  border: 1px solid #b7d4a8;
  position: absolute;
  top: 0;
  left: 0;
}

#food {
  width: 10px;
  height: 10px;
  background-color: black;
  position: absolute;
  border: 1px solid #b7d4a8;
}

在 index.ts 文件中引入样式

import './style/index.less'
  • 不要忘记安装 less

在这里插入图片描述
开始业务逻辑代码

在 src 文件下创建 modules 文件夹,存放每个类

Food.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
    // Math.round(Math.random() * 29) * 10
    this.element.style.top = Math.round(Math.random() * 29) * 10 + 'px'
    this.element.style.left = Math.round(Math.random() * 29) * 10 + 'px'
  }
}

// let food = new Food()
// food.change()
// console.log(food.X, food.Y)

export default Food

创建 ScorePanel.ts,控制记分牌的变化

// 定义记分牌
class ScorePanel {
  // score, level 记录分数和等级
  score = 0
  level = 1

  // 分数和等级初始化
  scoreEle: HTMLElement
  levelEle: HTMLElement

  // shezhibianliang
  maxLevel: number
  maxScore: number

  constructor(maxLevel: number = 10, maxScore: number = 10) {
    this.scoreEle = document.getElementById('score')!
    this.levelEle = document.getElementById('level')!
    this.maxLevel = maxLevel
    this.maxScore = maxScore
  }

  // 设置加分的方法
  addScore() {
    this.score++
    this.scoreEle.innerHTML = this.score + ''
    if (this.score % this.maxScore === 0) {
      this.levelUp()
    }
  }

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

export default ScorePanel

创建 Snake.ts,控制蛇的长度/位置/是否撞墙或者撞到自己

class Snake {
  // 表示蛇头的元素
  head: HTMLElement
  // 蛇的身体
  bodies: HTMLCollection
  element: HTMLElement

  constructor() {
    this.element = document.getElementById('snake')!
    this.head = document.querySelector('#snake > div')!
    this.bodies = this.element.getElementsByTagName('div')
  }

  // 获取蛇的坐标
  get X() {
    return this.head.offsetLeft
  }

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

  // 设置蛇头的坐标
  set X(value: number) {
    if (this.X === value) return
    // 判断是否撞墙 X值的合法范围
    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()
  }

  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++){
      if (this.X === (this.bodies[i] as HTMLElement).offsetLeft && this.Y === (this.bodies[i] as HTMLElement).offsetTop) {
        throw new Error("撞到自己了");
      }
    }
  }
}

export default Snake

创建 GameControl.ts,控制蛇的移动,把蛇、食物、记分牌联系到一块

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

class GameControl {
  // 定义三个属性
  // 蛇
  snake: Snake
  food: Food
  scorePanel: ScorePanel

  // c创建一个属性来存储蛇的移动方向
  direction: string = 'Right'

  // 创建一个属性用来记录游戏是否结束
  isLive: boolean = true

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

    this.init()
  }

  // 游戏初始化方法
  init() {
    // 绑定键盘事件
    // 修改this指向
    document.addEventListener('keydown', this.keydownHandler.bind(this))
    // 调用 run 方法
    this.run()
  }

  // 创建键盘按下的函数
  keydownHandler(e: KeyboardEvent) {
    // 修改 dirention 的值
    this.direction = e.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 "ArrowRight":
      case "Right":
        X += 10
        break;
      case "ArrowLeft":
      case "Left":
        X -= 10
        break;
    
      default:
        break;
    }

    this.checkEat(X, Y)

    try {
      this.snake.X = X
      this.snake.Y = Y
    } catch (e) {
      alert(' 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

在 src 下的 index.ts 中引入 GameControl 并且实例化,就可以运行代码

import GameControl from './modules/GameControl'

new GameControl()

gitee 项目地址:
https://gitee.com/jinyang465/snake-demo

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇

)">
下一篇>>