3

自学 TypeScript 第五天,手把手项目搭建 TS 篇 - 六扇有伊人

 1 year ago
source link: https://www.cnblogs.com/LiuSan/p/16915033.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

昨天咱们已经把贪吃蛇的页面写好了,今天咱们来写 TS 部分

TS 我们要用面向对象的形式去编写我们的功能,所以我们要以一个功能去定义一个对象

把这个项目分成几个模块,也就是几个对象功能

Food类(食物类):

写对象的前提,我们要去定义类,用类去创建对象

首先我们直接 class 一个 Food 类,由于我们的食物是一个 div 所以我们的 Food 类里面必须有一个属性来存放我们的元素

// 食物 Food 类
class Food{
    element:HTMLElement;
    constructor(){
        // 拿到元素 id
        this.element = document.getElementById('food')!;
    }
}

元素有了,现在我们想食物还有哪些功能?

我们的食物被蛇吃掉以后是不是要消失啊,那怎么确定我的蛇吃到食物了呢,看代码

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

如果我们蛇的坐标和食物的坐标一样了,是不是就证明我们吃到了食物,所以我们在 Food 类里面直接写俩 get 方法 获取 offsetTop/Left 就获取到了 x y 轴坐标

 坐标有了,但我们现在食物的位置是固定的,那能固定吗?我们的蛇吃完食物,食物的位置是不是要改变,所以我们的食物一定是随机一个位置

    // 修改食物位置
    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'
    }

因为我们的盒子是 300 我们的食物是 10 所以我们食物的位置最大是290,最小是0

而且我们设计蛇移动一次是一格也就是 10 所以我们食物的坐标必须是10的倍数

这样我们食物的 Food 类就完成了

计分板,ScorePanel 类:

接下来我们再写一个简单的,我们的计分板,我们的计分板有两个值,一个是积分一个是等级

还是同理,首先先获取元素

// 计分板 定义一个 ScorePanel 类
class ScorePanel{
    // 积分
    score = 0
    lecel = 0
    // 元素
    scoreSpan:HTMLElement
    lecelSpan:HTMLElement
    constructor(){
        this.scoreSpan = document.getElementById('score')!;
        this.lecelSpan = document.getElementById('lecel')!;
    }
}

获取完元素之后,我们的分数和等级是不是要进行一个增加

    addScore(){
        // this.score++
        this.scoreSpan.innerHTML = ++this.score + '';
        // 判断我们多少积分升一级
        if(this.score % 10 === 0){
            this.lecelup();
        }
    }
    // 等级提升
    lecelup(){
        // 判断是否到达最大等级
        if(this.lecel < 10){
            this.lecelSpan.innerHTML = ++this.lecel + '';
        }
    }

蛇类,Snake:

 蛇的类是我们这里最主要的一个类也是最难写的一个类,所以我们这里先写一个基础

还是跟前面的一样,我们先获取元素,但是蛇这里我们会麻烦一点,因为蛇是一个容器,我们要给他分为蛇头和蛇身子

// 设置我们的蛇类
class Snake{
    // 蛇的容器
    element:HTMLElement
    // 蛇的头部
    head:HTMLElement
    // 蛇的身体(包括蛇头的) HTMLCollection 是一个集合,特点:它是会实时的刷新的
    bodies:HTMLCollection
    constructor(){
        // 容器元素
        this.element = document.getElementById('snake')!;
        // 获取蛇头的元素也就是我们元素里面的第一个 div
        this.head = document.querySelector('#snake > div')! as HTMLElement;
        // 获取容器里面的所有 div
        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 + ''
    }
    set y(value:number){
        this.head.style.top = value + ''
    }

这样就获取到了,设置蛇的坐标我们现在先写一个简单的,等会再完善

现在我们先写一个吃完食物蛇的身子增加的方法,其实这个增加就是向元素里添加 div

    // 蛇增加身体的一个方法
    addBody(){
        // 向 element 中添加 div
        this.element.insertAdjacentHTML('beforeend','<div></div>')
    }

这样一个基础的模型就写好了,但却还有问题,我们的坐标还没有完善,而我们蛇还不能动

到现在谁把三个类都放到一个 TS 里面了 ?

看起来不多吗,修改起来太墨迹了,所以我们要把这三个类差分,放到不同的 TS 文件里,然后每一个文件都作为一个模块暴露出去

首先再 src 下边创建一个 modules 的文件夹,然后创建三个 TS 文件

1713050-20221123104734688-694854322.jpg

然后每一个 TS 都作为模块暴露出去,代码为

export default class类名;

都弄完了以后我们还需要一个 TS 文件来把我们这三个 TS 进行一个整合

新建一个 GameControl.ts 它现在就是我们的游戏控制器,控制其他的所有类

GameControl,游戏控制类:

首先先引入其他的类,然后来定义,方便使用,同时 GameControl 这个 TS 也是当一个模块去暴露出去的,给到我们最终的 index.ts

// 引入其他类
import Food from './Food' // 食物类 Food
import ScorePanel from './ScorePanel' // 记分类 ScorePanel 
import Snake from './Snake' // 蛇 Snake
// 游戏控制器,控制其他的所有类
class GameControl{
    // 定义三个属性
    food:Food // 食物
    scorepanel:ScorePanel // 记分类
    snake:Snake // 蛇
    constructor(){
        this.food = new Food();
        this.scorepanel = new ScorePanel();
        this.snake = new Snake();
      this.init();
    }
}
export default GameControl;

定义完之后,肯定要有一个游戏的开始,或者一个游戏的初始化方法

那第一件事就是让我们的蛇可以动,首先绑定一个键盘事件

// 创建一个属性来存储我们的按键,蛇的移动方向
  direction:string = ''
 // 游戏初始化方法,调用之后游戏开始
    init(){
        // 键盘摁下的事件
        document.addEventListener('keydown',this.keydomnHandler.bind(this))
    }
    // ArrowUp
    // ArrowDown
    // ArrowLeft
    // ArrowRight
    // 创建一个键盘按下的响应
    keydomnHandler(event:KeyboardEvent){
        // 用户是否合法,用了正确的摁键
        // 触发按下 修改值
        // console.log(event.key)
        this.direction = event.key
    }

绑定写完了,我们现在来让蛇动起来,也就是改蛇的 left 和 top ,向上 top 减少,向下 top 增加,left 同理

    // 创建一个属性来存储我们的按键,蛇的移动方向
    direction:string = ''
    init(){
        // 键盘摁下的事件
        document.addEventListener('keydown',this.keydomnHandler.bind(this));
        this.run();
    }
    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":
                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;
        setTimeout(this.run.bind(this), 500 - (this.scorepanel.lecel -1) * 50); 
    }

setTinmeout 重复执行我们的代码,让蛇一直动,500 的数值越大,蛇走的越慢,让它随着我们的等级来走,等级乘 50 就行

接下来我们来写游戏失败的方法,贪吃蛇失败无疑就是撞墙或装自己身体

而我们再写 class 类的时候要注意,谁的事情,尽量让谁去处理,蛇死了,是蛇自己的事情,所以我们在 Snake 也就是蛇类,去处理

我们修改蛇的移动是调用了,Snake 里 set x 和 set y,所以只要给这俩前边加一个限制

    // 设置我们蛇头的坐标
    set X(value:number){
        // 新值和旧值相同,不会去改
        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:number){
        if(this.y === value){
            return;
        }
        // x 值的合法范围 也就是 0 - 290 之间
        if(value < 0 || value > 290){
            // 进入判断说明我的蛇撞墙了
            throw new Error('蛇撞墙了!')
        }
        this.head.style.top = value + 'px'
    }

接下来就是如何吃到食物了

    checkEat(x:number,y:number){
        if(x === this.food.x && y === this.food.y){
            // 食物改变位置
            this.food.change()
            // 积分版
            this.scorepanel.addScore()
            // 蛇添加一节
            this.snake.addBody()
        }
    }

我们之前就都定义好了,直接拿来用就行了,剩下的就是我们的身体如何跟头去移动,还有撞自己身体游戏结束的方法就完成了我们的贪吃蛇

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

this.bodies 就是蛇的身体长度,那我们蛇怎么前进呢,就是后边 div 的位置等于前边 div 的位置

这样我们的蛇身子就动起来了,接下来我们写蛇头撞身子游戏结束

    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("撞自己了")
            }
        }
    }

之后我们把这个方法写到 set x、y里面最后一段的位置调用

接下来我们固定一下左右,在 set 里面写

        // 修改 x 时候 是在修改水平坐标,蛇向左移动时候不能向右 反之同理
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value){
            console.log("水平掉头了")
            if(value > this.y){
                // 新值 大于 x 旧值 掉头 应该继续走
                value = this.y - 10
            }else{
                value = this.y + 10 
            }
        }

y 轴是同理的

这样下来我们整个一个贪吃蛇就完成了

 以下贴出所有代码

Food.ts

// 食物 Food 类
class Food{
    element:HTMLElement;
    constructor(){
        // 拿到元素 id 直接赋值
        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;

ScorePanel.ts

// 计分板 定义一个 ScorePanel 类
class ScorePanel{
    // 用来记录我们的分数和等级
    score = 0
    lecel = 1
    // 记住我们分数和等级 元素,再构造函数中初始化
    scoreSpan:HTMLElement
    lecelSpan:HTMLElement
    // 设置我们的等级最大值
    maxLevel:number
    // 设置多少积分升一级
    upScore:number
    constructor(maxLevel:number = 10,upScore:number = 10){
        this.scoreSpan = document.getElementById('score')!;
        this.lecelSpan = document.getElementById('lecel')!;
        this.maxLevel = maxLevel;
        this.upScore = upScore;
    }
    // 设置一个加分的方法
    addScore(){
        this.score++
        this.scoreSpan.innerHTML = this.score + '';
        // 判断我们多少积分升一级
        if(this.score % this.upScore === 0){
            this.lecelup();
        }
    }
    // 等级提升
    lecelup(){
        // 判断是否到达最大等级
        if(this.lecel < this.maxLevel){
            this.lecel++
            this.lecelSpan.innerHTML = this.lecel + '';
        }
    }
}
export default ScorePanel;

Snake.ts

// 设置我们的蛇类
class Snake{
    // 蛇的容器
    element:HTMLElement
    // 蛇的头部
    head:HTMLElement
    // 蛇的身体(包括蛇头的) HTMLCollection 是一个集合,特点:它是会实时的刷新的
    bodies:HTMLCollection
    constructor(){
        // 容器元素
        this.element = document.getElementById('snake')!;
        // 获取蛇头的元素也就是我们元素里面的第一个 div
        this.head = document.querySelector('#snake > div')! as HTMLElement;
        // 获取容器里面的所有 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 值的合法范围 也就是 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){
                // 新值 大于 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;
        }
        // x 值的合法范围 也就是 0 - 290 之间
        if(value < 0 || value > 290){
            // 进入判断说明我的蛇撞墙了
            throw new Error('蛇撞墙了!')
        }
        // 修改 x 时候 是在修改水平坐标,蛇向左移动时候不能向右 反之同理
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value){
            console.log("水平掉头了")
            if(value > this.y){
                // 新值 大于 x 旧值 掉头 应该继续走
                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','<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;

GameControl.ts

// 引入其他类
import Food from './Food' // 食物类 Food
import ScorePanel from './ScorePanel' // 记分类 ScorePanel 
import Snake from './Snake' // 蛇 Snake
// 游戏控制器,控制其他的所有类
class GameControl{
    // 定义三个属性
    food:Food // 食物
    scorepanel:ScorePanel // 记分类
    snake:Snake // 蛇
    // 创建一个属性来存储我们的按键,蛇的移动方向
    direction:string = ''
    // 创建一个属性来记录我们游戏是否结束
    isLive = true
    constructor(){
        this.food = new Food();
        this.scorepanel = new ScorePanel();
        this.snake = new Snake();
        this.init();
    }
    // 游戏初始化方法,调用之后游戏开始
    init(){
        // 键盘摁下的事件
        console.log("执行")
        document.addEventListener('keydown',this.keydomnHandler.bind(this));
        this.run();
    }
    // ArrowUp
    // ArrowDown
    // ArrowLeft
    // ArrowRight
    // 创建一个键盘按下的响应
    keydomnHandler(event:KeyboardEvent){
        // 用户是否合法,用了正确的摁键
        // 触发按下 修改值
        console.log(event.key,1111)
        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":
                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{
            // 捕获异常,游戏结束
            alert('GAME OVER!')
            this.isLive = false
        }
        // isLive 开关 ture 的话重复执行
        this.isLive && setTimeout(this.run.bind(this), 500 - (this.scorepanel.lecel -1) * 50); 
    }
    // 定义一个方法,用来检查是否吃到食物
    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;

index.ts

import "./style/index.less"

import GameControl from './modules/GameControl'

new GameControl();

这里是六扇有伊人,我们有缘再见


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK