TypeScript实现小游戏---贪吃蛇(超详细)下

简介: TypeScript实现小游戏---贪吃蛇(超详细)下

多模块搭建


在项目开发中我们不可能把所有的代码写到一个文件中,所以项目开发必须会灵活运用模块化开发思想,把实现的功能细化成一个个模块。


完成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
        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';
    }
}
export default Food

代码分析:


  1. 由于在配置typescript时我们设置了strict(严格)模式,因此

this.element = document.getElementById('food')!中如果我们不加!会让编译器不确定我们是否会获取到food的dom元素而发生报错。

  1. 准备了get()方法可以在控制模块中随时获取food的具体定位。
  2. change()方法为随机刷新一次food的位置。
  3. export default Food 代码加在最后。为的是把food成为全局模块暴露出去,这样的话其他的模块可以调用这个food模块。


完成ScorePanel(记分牌)类

//定义表示记分牌的类
class ScorePanel {
    score : number = 0;
    level : number = 1;
    scoreSpan :HTMLElement;
    levelEle : HTMLElement;
    //设置变量限制等级
    maxLevel : number;
    //设置一个变量多少分升级
    upScore : number;
    constructor(maxLevel : number = 10,Score : number = 10) {
        this.scoreSpan = document.getElementById('score')!;
        this.levelEle  = document.getElementById('level')!;
        this.maxLevel = maxLevel
        this.upScore = Score
    }
    //设置加分的方法
    AddScore() {
        this.score++;
        this.scoreSpan.innerHTML = this.score + ''
        if (this.score % this.upScore === 0 ) {
            this.AddLevel()
        }
    }
    //提升等级
    AddLevel() {
        if (this.level < this.maxLevel) {
            this.levelEle.innerHTML = ++this.level +''
        }
    }
}
export default ScorePanel

代码分析:


在记分牌模块主要是两种方法AddScore()和AddLevel(),分别用来控制分数增加和等级提升,重点也有设置一个变量来限制等级和设置变量来判断多少分上升一个等级


完成Snake(蛇)类

class Snake {
    //表示蛇头的元素
   head : HTMLElement;
   bodies : HTMLCollectionOf<HTMLElement>;
   //获取蛇的容器
    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;
        }
        if(value < 0 || value > 290) {
            throw new Error('蛇撞墙了!')
        }
        //修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            //如果发生的掉头,让蛇向反方向继续移动
            if(value > this.X) {
                //如果value大于旧值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;
        }
        if(value < 0 || value > 290) {
            throw new Error('蛇撞墙了!')
        }
        //修改Y时,是在修改水平坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            //如果发生的掉头,让蛇向反方向继续移动
            if(value > this.Y) {
                //如果value大于旧值X,则说明蛇在向右走,此时应该发生掉头,应该使蛇继续向左走
                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].offsetLeft && this.Y === this.bodies[i].offsetTop) {
                //进入判断说明蛇头撞到了身体,游戏结束
                throw new Error('撞到自己了')
            }
        }
    }
}
export default Snake


代码分析:


  1. 首先它自身只添加了三个功能函数addbody,movebody和checkHeadBody
  2. movebody的实现逻辑非常的巧妙,根据从后往前的顺序来确定位置,根据前一节的位置,从而让后边的位置替换到前一节的位置上,从而实现蛇可以移动的逻辑。
  3. 为什么get,set,判断蛇是否死亡机制以及之后的蛇移动的代码一定要写在constructor()函数中而不是写在外面?


在后面还有一个控制模块中

首先利用get()方法获得蛇头坐标,当蛇头移动一次以后,立刻刷新后的蛇头坐标反馈给蛇对象


蛇这个对象更新以后constructor代码就会执行一遍,执行过程中首先蛇头的坐标用set()函数重新设置,然后蛇的movebody函数就会执行一次。最后对蛇进行判断死没死。


这样一次代码就执行完成啦。此时整条蛇都前进了一次。然后我们通过定时器定个时间不断让蛇移动就可以了。


完成GameControl(控制)类


import Food from "./food";
import Snake from "./Snake";
import ScorePanel from "./ScorePanel";
//游戏控制器,控制其他所有类
class GameControl {
    snake : Snake;
    food : Food;
    scorePanel : ScorePanel
    direction : string = '';
    //创建一个变量来判断游戏是否结束
    isLive : boolean = true;
    constructor() {
        this.snake = new Snake()
        this.food = new Food()
        this.scorePanel = new ScorePanel()
        this.init()
    }
    //游戏的初始化,调用后游戏将开始
    init() {
        document.addEventListener('keydown',this.keydownHandler.bind(this))
        //调用run
        this.run()
    }
    /*ArrowUp
     ArrowDown
     ArrowLeft
     ArrowRight
     */
    //创建一个键盘按下的响应函数
    keydownHandler(event: KeyboardEvent) {
        console.log(event.key)
        this.direction = event.key
    }
    //创建一个控制蛇移动的方法
    /*
    *   根据方向(this.direction)来使蛇位置发生改变
    *
    * */
    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 'ArrowLeft':
            case 'Left':
                X -=10;
                break;
            case 'ArrowRight':
            case 'Right':
                X += 10;
                break;
        }
        (this.checkEat(X,Y))
        try {
            //修改X和Y的值
            this.snake.X = X;
            this.snake.Y = Y;
        }catch (e) {
            //进入到catch出现异常
            alert((e as any).message + '游戏结束了,老表!');
            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


代码分析:


我们设置控制类主要目的在于整合之前的三个类,从而在这个类中调用之前声明的类,在该类中重点在于初始化游戏、控制蛇的移动、检查蛇是否吃到食物,这个类相当于一个总开关。


这里还有一个重点在于this指向问题,这里使用了bind()函数,bind最直接的定义就是将this指向到当前的对象。


完成index类(启动项目)


import './style/index.less'
import GameControl from './modules/GameControl'
new GameControl()


代码分析:


大家都非常清楚,想要让对象执行,我们必须要进行实例化,这里只用new一下进行调用即可,项目就可以执行了


项目启动


最后我们打开终端输入npm start或者npm run build,项目就跑起来了,它可以自动打开浏览器进行执行

e2c32ea4117f442196cf4fa6e8878294.png


cb8ab8afdaf0426a8dcd4be3587a1870.png



总结


学习完了typescript,其实最主要的在于运用它实现面向对象的开发,我们在日常开发中基本不会用到面向对象,就算es6中涉及到类、接口等等,但是在实际中很少人去使用,面向对象的开发中其实使得项目变得更加的严谨和合理化,我们在书写代码的时候会更加的规范,ts的类型严格更加的使它方便大型项目开发!


相关文章
|
JavaScript 前端开发
【TypeScript教程】# 16:ts + webpack + less实现贪吃蛇小游戏
【TypeScript教程】# 16:ts + webpack + less实现贪吃蛇小游戏
152 0
【TypeScript教程】# 16:ts + webpack + less实现贪吃蛇小游戏
|
JavaScript API
TypeScript 贪吃蛇游戏详细教程
TypeScript 贪吃蛇游戏详细教程
185 0
TypeScript 贪吃蛇游戏详细教程
|
JavaScript 前端开发 容器
TypeScript实现小游戏---贪吃蛇(超详细)上
TypeScript实现小游戏---贪吃蛇(超详细)上
TypeScript实现小游戏---贪吃蛇(超详细)上
|
2月前
|
JavaScript
typeScript进阶(9)_type类型别名
本文介绍了TypeScript中类型别名的概念和用法。类型别名使用`type`关键字定义,可以为现有类型起一个新的名字,使代码更加清晰易懂。文章通过具体示例展示了如何定义类型别名以及如何在函数中使用类型别名。
40 1
typeScript进阶(9)_type类型别名
|
1月前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
45 0
|
1月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧
|
2月前
|
存储 JavaScript
typeScript进阶(11)_元组类型
本文介绍了TypeScript中的元组(Tuple)类型,它是一种特殊的数组类型,可以存储不同类型的元素。文章通过示例展示了如何声明元组类型以及如何给元组赋值。元组类型在定义时需要指定数组中每一项的类型,且在赋值时必须满足这些类型约束。此外,还探讨了如何给元组类型添加额外的元素,这些元素必须符合元组类型中定义的类型联合。
47 0
|
2月前
|
JavaScript
typeScript进阶(10)_字符串字面量类型
本文介绍了TypeScript中的字符串字面量类型,这种类型用来限制变量只能是某些特定的字符串字面量。通过使用`type`关键字声明,可以确保变量的值限定在预定义的字符串字面量集合中。文章通过示例代码展示了如何声明和使用字符串字面量类型,并说明了它在函数默认参数中的应用。
37 0
|
6天前
|
JavaScript 安全 前端开发
TypeScript类型声明:基础与进阶
通过本文的介绍,我们详细探讨了TypeScript的基础与进阶类型声明。从基本数据类型到复杂的泛型和高级类型,TypeScript提供了丰富的工具来确保代码的类型安全和可维护性。掌握这些类型声明能够帮助开发者编写更加健壮和高效的代码,提高开发效率和代码质量。希望本文能为您在使用TypeScript时提供实用的参考和指导。
14 2
|
19天前
|
JavaScript 开发者
在 Babel 插件中使用 TypeScript 类型
【10月更文挑战第23天】可以在 Babel 插件中更有效地使用 TypeScript 类型,提高插件的开发效率和质量,减少潜在的类型错误。同时,也有助于提升代码的可理解性和可维护性,使插件的功能更易于扩展和升级。