TypeScript 贪吃蛇项目(上)

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

项目构建

新建项目或者修改之前的项目,删去没用的代码,只留下之前写的几个配置文件

在这里插入图片描述

如果是新建项目,可以把 package.json,tsconfig.json,webpack.config.js 拿到根目录,然后执行 npm i来下载模块

src 目录下新建 index.ts 和 index.html,ts 文件中随便写点东西,执行 npm run build,如果生成了 dist 文件即为构建成功了

在这里插入图片描述

Tips:右键 package.json - show npm scripts 就可以展示设置的命令,双击即可执行,和上边结果一样的

在这里插入图片描述

安装插件 less

Less 是一个Css 预编译器,意思指的是它可以扩展Css语言,添加功能如允许变量(variables),混合(mixins),函数(functions) 和许多其他的技术,让你的Css更具维护性,主题性,扩展性

①、安装

cnpm i -D less less-loader css-loader style-loader

②、配置

......

//webpack 中的所有配置都应该写在 module.exports 中
module.exports = {
   ......
    //指定webpack打包时使用的模块
    module: {
        //指定要加载的规则
        rules: [
            {
             	......
                //要排除的文件
                exclude: /node_modules/
            },
            //设置less文件的处理
            {
                test: /\.less$/,
                use:[
                    "style-loader",
                    "css-loader",
                    "less-loader"
                ]
            }
        ]
    },

    //配置webpack插件
    ......
};

接下来验证下能否成功使用,在 src 中新建 style 文件夹,在其中 增加一个 index.less (新建 style sheet,选择 less即可)

在这里插入图片描述

构建,再运行,可以看到我们的网页已经修改了颜色了

在这里插入图片描述

安装插件 postcss

postcss 是一个通过 js 插件来转换 css 的工具,通过这些插件可以支持变量和混合,可以通过追加浏览器前缀生成兼容性的样式

①、安装

cnpm i -D postcss postcss-loader postcss-preset-env

less 把代码转换成 css 之后,应立刻处理兼容性,然后再交给 css-loader 处理,所以应该放在 less-loader 之后,css-loader 之前

//引入一个包
const path = require('path')
//引入插件
const HTMLWebpackPlugin = require("html-webpack-plugin")
//引入clean插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

//webpack 中的所有配置都应该写在 module.exports 中
module.exports = {
   
    module: {
       		......
            //设置less文件的处理
            {
                test: /\.less$/,
                use:[
                    "style-loader",
                    "css-loader",
                    //引入postloader
                    {
                        loader: "postcss-loader",
                        options:{
                            postcssOptions:{
                                plugins: [
                                    [
                                        "postcss-preset-env",
                                        {
                                            browsers:"last 2 versions"
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]
            }
        ]
    },
   ......
};

创建界面

在这里插入图片描述

index.less

//设置变量
@bg-color: #b7d4a8;
//清除默认样式
*{
  margin: 0;
  padding: 0;
  //改变盒子模型的计算方法
  //https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-sizing
  box-sizing: border-box;
}
body{
  font: bold 20px "Courier New";
}

//设置主窗口样式
#main {
  width: 360px;
  height: 420px;
  background-color: @bg-color;
  margin: 100px auto;
  border: 10px solid black;
  border-radius: 10px;
  //开启弹性盒模型
  display: flex;
  //设置主轴方向
  flex-flow: column;
  //设置侧轴对齐方向
  align-items: center;
  //设置主轴对其方向
  justify-content: space-around;

  //游戏舞台
  #stage {
    width: 304px;
    height: 304px;
    border: 2px solid black;
    position: relative;

    //设置蛇的样式
    #snake{
      //id snake下的div的样式
      &>div{
        width: 10px;
        height: 10px;
        background-color: black;
        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;

      &>div{
        width: 4px;
        height: 4px;
        background-color: black;
        //旋转
        transform: rotate(45deg);
      }
    }
  }

  //游戏积分牌
  #score-panel {
    width: 300px;
    display: flex;
    //设置主轴对其方向
    justify-content: space-around;
  }
}

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 id="score-panel">
            <div>
                SCORE:<span id="score">0</span>
            </div>
            <div>
                LEVEL:<span id="level">1</span>
            </div>
        </div>
    </div>
</body>
</html>

创建食物类

修改 index.ts

import './style/index.less';

//定义食物类
class Food {
    //食物对应的元素
    element: HTMLElement;

    constructor() {
        //获取页面中 food 元素
        //最后加一个叹号表示不会为空,否则 this.element 有为空警告
        this.element = document.getElementById('food')!;
    }

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

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

    //修改食物位置的方法
    change() {
        //生成一个随机的位置
        //食物的位置最小是0,最大是290
        //蛇移动一次是1格 也就是10,所以蛇的位置是整10的
        //Math.random()生成0-1的随机数(不包括0和1)
        //* 29 也就会生成0-29的随机数(不包括0和29)
        //Math.round进行四舍五入取整,这样就可以包括0和29
        //然后整体 * 10 就得到10的整数倍了
        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();
console.log(food.X + "," + food.Y);
food.change();

运行代码,这样每次刷新页面食物的位置都会改变

在这里插入图片描述

创建记分牌类

修改 index.ts

//记分牌类
class ScorePanel{
    //记录分数
    score=0;
    //记录等级
    level=1;
    scoreEle:HTMLElement;
    levelEle:HTMLElement;
    //设置一个变量限制等级
    maxLevel:number;
    //设置一个变量限制多少分升级
    upScore:number;

    //maxLevel默认10,如果不传就是10
    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.scoreEle.innerHTML = this.score+"";
        //每10分升级
        if(this.score % this.upScore === 0){
            this.levelUp();
        }
    }

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

//测试代码
const scorePanel = new ScorePanel();
for(let i=0;i<10;i++){
    scorePanel.addScore();
}

分模块

src 下新建 moduls 文件夹,再新建 Food.ts 和 ScorePanel.ts,分别把之前写的代码粘进来

在这里插入图片描述

不过两个文件都需要在最后分别加上:

//做为默认模块暴露出来
export default Food;
//做为默认模块暴露出来
export default ScorePanel;

蛇类

先初步写一个 蛇的类,后边再继续完善

class Snake {
    //表示蛇头的元素
    head: HTMLElement;
    //蛇的身体(包括蛇头),HTMLCollection 是一个接口,表示 HTML 元素的集合
    // HTMLCollection 是“活”的;如果基本的文档改变时,那些改变通过所有 HTMLCollection 对象会立即显示出来。
    bodies: HTMLCollection;
    //获取蛇的容器
    element: HTMLElement;

    constructor() {
        this.element = document.getElementById("snake")!;
        //querySelector() 方法返回文档中匹配指定 CSS 选择器的一个元素。只获取第一个
        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: number) {
        this.head.style.left = value + "px";
    }

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

    //蛇的身体增加的方法
    addBody() {
        //向element增加一个div
        //insertAdjacentHTML() 将指定的文本解析为HTML或XML,并将结果节点插入到DOM树中的指定位置
        //语法:element.insertAdjacentHTML(position, text);
        //beforeend: 插入元素内部的最后一个子节点之后。
        this.element.insertAdjacentHTML("beforeend", "<div></div>");
    }
}

//做为默认模块暴露出来
export default Snake;

键盘类

我们先简单写一个键盘类,然后调用看看

//引入其他类
import Snake from "./Snake";
import ScorePanel from "./ScorePanel";
import Food from "./Food";

//游戏控制器,控制其他所有类
class GameControl {
    //定义3个属性
    snake: Snake;
    food: Food;
    scorePanel: ScorePanel;
    //创建一个属性来存储蛇的移动方向,也就是按键的方向
    direction: string = '';

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

    //游戏初始化
    init() {
        //绑定键盘按下事件
        document.addEventListener('keydown', this.keyDownHandler)

    }

    /**
     * Chrome:
     * ArrowUp/ArrowDown/ArrowLeftArrowRight
     * IE:
     * Up/Down/Left/Right
     */
    //创建键盘按下响应函数
    keyDownHandler(event: KeyboardEvent) {
        //console.log(event.key);
        //注意这里的this是init方法中调用这个方法的document
        this.direction = event.key;
    }
}

export default GameControl;

在 GameControl 的 init() 方法绑定了键盘按下事件,然后用一个变量 direction 接收,我们在 index.ts 中打印看下结果 在 index.ts 中调用

import './style/index.less';

import GameControl from "./moduls/GameControl";

const gc = new GameControl();
setInterval(()=>{
    console.log(gc.direction);
},1000);

在这里插入图片描述

发现并没有拿到 direction 的值,这是因为 keyDownHandler() 方法改变 direction 的值时,this 表示的是init方法中调用这个方法的document,

TypeScript 中如果在回调函数中用了this 的话, 就要小心了, 这个 this 不一定是指向当前类对象了,如果想确保指向的还是那个对象的话, 需要在传递那个方法的时候, 先调用 bind(this)

所以我们修改 init

init() {
	//绑定键盘按下事件
	document.addEventListener('keydown', this.keyDownHandler.bind(this))
}

学习更多:详解Typescript里的This

再运行就可以得到方向了

在这里插入图片描述

猜你喜欢

转载自juejin.im/post/7126500494410776583
今日推荐