当前位置 : 主页 > 手机教程 > 手机资讯 >

利用TypeScript编写贪吃蛇游戏

来源:互联网 收集:自由互联 发布时间:2023-01-20
目录 Explanation 1. tsconfig.json配置 2. HTML CSS 布局相关 3. TS核心逻辑 项目源码链接 先来康康效果图 我接下来将讲解相关配置和代码,源码链接放在最底下了,在GitHub上。 Explanation 1. tscon
目录
  • Explanation
    • 1. tsconfig.json配置
    • 2. HTML & CSS 布局相关
    • 3. TS核心逻辑
  • 项目源码链接

    先来康康效果图

    我接下来将讲解相关配置和代码,源码链接放在最底下了,在GitHub上。

    Explanation

    1. tsconfig.json配置

    {
        "compilerOptions": {
            "target": "ES2015",
            "module": "ES2015",
            "strict": true,
            "noEmitOnError": true 
        }
    }
    

    此处是对编译选项进行配置

    • target: 我们将TS转译成JS的版本。
    • module: 模块化的版本。
    • strict: 所有相关的严格模式是否开启。
    • noEmitOnError: 当出现错误时是否停止编译。

    2. HTML & CSS 布局相关

    先来看看我们整体的布局

    <!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>Document</title>
    </head>
    <body scroll="no">
        <!-- 创建游戏的主容器 -->
        <div id="main">
            <!-- 设置游戏舞台 -->
            <div id="stage">
                <!-- 设置蛇 -->
                <div id="snack">
                    <!-- snack内部的div表示蛇的各部分 -->
                    <div></div>
                </div>
                <div id="food">
                    <!-- 设置食物的样式 -->
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                </div>
            </div>
            <!-- 设置游戏的积分牌 -->
            <div id="scoreStage">
                <div>SCORE:<span id="score">0</span></div>
                <div>LEVEL:<span id="level">1</span></div>
            </div>
        </div>
    </body>
    </html>

    CSS代码

    // 设置变量
    @bgColor: #b7d4a8;
    
    // 清除默认样式清除默认样式
    * {
        margin: 0;
        padding: 0;
        // 怪异模式
        box-sizing: border-box;
    }
    
    body {
        font: bold 20px "Courier";
        width: 100%;
        height: 100%;
        overflow: hidden;
    }
    
    // 设置主窗口的样式
    #main {
        width: 360px;
        height: 420px;
        background-color: @bgColor;
        margin: 100px auto;
        border: 10px solid #000;
        border-radius: 40px;
        display: flex;
        flex-direction: column;
        align-items: center;
        // 主轴对齐方式
        justify-content: space-around;
        
    }
    
    #stage {
        width: 304px;
        height: 304px;
        border: 2px solid black;
        position: relative;
    }
    
    #scoreStage {
        width: 300px;
        display: flex;
        justify-content: space-between;
    }
    
    #snack {
        &>div {
            width: 10px;
            height: 10px;
            background-color: #000;
            border: 1px solid @bgColor;
            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: #000;
            transform: rotate(45deg);
            // border: 1px solid @bgColor;
        }
    }
    
    body {
        scroll-behavior: unset;
    }
    

    这里面比较有意思是可以通过@xxx来设置CSS变量

    比如这里的:@bgColor: #b7d4a8;

    3. TS核心逻辑

    TS的核心在于Class,所以我们需要定义出非常多的类来对这个贪吃蛇小游戏进行分析。

    我们先来看看这个贪吃蛇小游戏有几个主要的部分。

    • 食物
    • 分数版
    • 游戏操控

    食物

    食物有几个核心逻辑

    食物这个类。

    首先,我们需要获取其中的横纵坐标。可以设置get来获取X与Y。

    其次,当蛇蛇碰到食物的时候,这个食物的位置会改变,可以设置一个change()方法。

    class Food {
        // 属性 & 方法
        // 定义食物所对应的元素
        element: HTMLElement;
    
        constructor() {
            // 加一个 “!”表示这玩意不会为空
            this.element = document.getElementById('food')!;
        }
        // 方法
        // 1. 获取食物的x坐标的方法
        get X() {
            return this.element.offsetLeft;
        }
        // 2. 获取食物的y坐标的方法
        get Y() {
            return this.element.offsetTop;
        }
        
        // 修改食物位置的方法
        change() {
            // 使用random,生成随机位置
            // 蛇移动一次就是一格,大小为10
            const left = Math.round(Math.random()*29)*10;
            const top = Math.round(Math.random()*29)*10;
            this.element.style.left = left + 'px';
            this.element.style.top = top + 'px';
        }
    
    }
    
    export default Food
    

    蛇的话,话头就很多了。

    首先,我们需要获取到蛇头的横纵坐标,还要能够给横纵坐标赋值。

    其次,我们需要有方法增加蛇的身子addBody

    同时还需要增加身子移动的方式moveBody

    当然还需要增加检测机制,舌头不能与身子重叠

    这个比较复杂,我们分而析之

    1.元素设置与constructor

    // 表示蛇的元素
    head: HTMLElement;
    // 蛇的身体,包括蛇头
    bodies: HTMLCollection;
    // 获取蛇的容器
    element: HTMLElement;
    constructor() {
       // 断言一下 | 找到蛇头
      this.head = document.querySelector('#snack > div') as HTMLElement;
      this.element = document.getElementById('snack')!
      this.bodies = this.element.getElementsByTagName('div');
    }
    

    这里面的!是用来确定存在id为snack这个元素的。

    2.增加身子

    addBody() {
        this.element.insertAdjacentHTML("beforeend", "<div></div>")
    }
    

    3.移动身子

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

    4.检测机制

        checkHeadBody() {
            // 获取所有的身体,检查其是否和蛇头的坐标发生重叠
            for(let i=1;i<this.bodies.length;i++) {
                const bd = (this.bodies[i] as HTMLElement)
                if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
                    // 说明出现了碰撞
                    throw Error('撞到自己了~~~')
                }
            }
        }
    

    5.完整代码

    class Snack {
        // 表示蛇的元素
        head: HTMLElement;
        // 蛇的身体,包括蛇头
        bodies: HTMLCollection;
        // 获取蛇的容器
        element: HTMLElement;
        constructor() {
            // 断言一下 | 找到蛇头
            this.head = document.querySelector('#snack > div') as HTMLElement;
            this.element = document.getElementById('snack')!
            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('您撞墙了')
            }
            // 蛇在往左走,不能往右走
            if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
                // 让蛇向反方向继续移动
                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) {
            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) {
                    // 如果新值大于旧值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++) {
                const bd = (this.bodies[i] as HTMLElement)
                if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
                    // 说明出现了碰撞
                    throw Error('撞到自己了~~~')
                }
            }
        }
    }
    
    export default Snack
    

    得分面板

    这个就比较简单了,主要是得分增加的方法与等级提升的方法。

    class scorePanel {
        // score和level用来记录分数和等级
        score: number = 0;
        level: number = 1;
        scoreSpan: HTMLElement;
        levelSpan: HTMLElement;
    
        // 设置等级
        maxLevel: number;
        // 设置一个变量表示多少分升级
        upScore: number;
        // 给两个需要修改的元素赋值
        constructor(maxLevel: number = 10, upScore: number = 2) {
            this.scoreSpan = document.getElementById('score')!;
            this.levelSpan = document.getElementById('level')!;
    
            this.maxLevel = maxLevel
            this.upScore = upScore
        }
    
        // method
        // 设置加分的方法
        addScore() {
            // 分数自增
            this.score += 1
            this.scoreSpan.innerHTML = this.score + '';
            // 判断一下分数是多少
            if(this.score % this.upScore === 0) {
                this.levelUp()
            }
        }
    
    
        // 提升等级的方法
        levelUp() {
            if(this.level < this.maxLevel) {
                this.level += 1
                this.levelSpan.innerHTML = this.level + '';
            }
        }
    }
    
    export default scorePanel
    

    控制面板

    这个逻辑的核心之一是整合,之二是监控键盘keydown事件

    关于整合:我们会把其中的蛇,面板,食物都整合到这个类中,所谓一个启动游戏的开关。

        snack: Snack;
        food: Food;
        scorePanel: scorePanel;
        arrowDirection: string = '';
        // 创建一个属性用来记录游戏是否结束
        isLeave: boolean = true
        constructor() {
            this.snack = new Snack();
            this.food = new Food();
            this.scorePanel = new scorePanel();
            this.init();
        }
    

    关于监控键盘事件

    这里的核心逻辑就是监控,看是上下左右中的哪一个,然后对应的改变蛇蛇的方向。

    其中蛇的移动需要不断的调用run这个函数,所以我们使用isLeave作为开关,用递归来多次调用run这个函数。

    import Snack from './snack';
    import Food from "./Food";
    import scorePanel from './scorePanel';
    
    class GameControl {
        snack: Snack;
        food: Food;
        scorePanel: scorePanel;
        arrowDirection: string = '';
        // 创建一个属性用来记录游戏是否结束
        isLeave: boolean = true
        constructor() {
            this.snack = new Snack();
            this.food = new Food();
            this.scorePanel = new scorePanel();
            this.init();
        }
        // 游戏的初始化方法
        init() {
            // 绑定键盘按下的时间
            // const _this = this
            // 如果不改变这个this,则会绑定到document上面
            // document.addEventListener('keydown', _this.keyDownHandler)
            document.addEventListener('keydown', this.keyDownHandler.bind(this));
            // 调用run方法
            this.run();
        }
        // 创建一个键盘按下的响应函数
        /**
         *  ArrowRight Right
            ArrowLeft Left
            ArrowDown Down
            ArrowUp Up
        */
        keyDownHandler(event: KeyboardEvent) {
            this.arrowDirection = event.key
            // this.run();
        }
    
        // 创建一个控制蛇移动的方法
        run() {
            // 根据方向(this.direction)来使蛇的位置改变
            // 向上 top -
            // 向下 top +
            // 向左 left - 
            // 向右 left +
            let X = this.snack.X;
            let Y = this.snack.Y;
            // 根据按键方向修改值
            switch (this.arrowDirection) {
                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
            }
            try {
                this.snack.X = X;
                this.snack.Y = Y;
            } catch(e: any) {
                alert(e.message)
                this.isLeave = false
            }
            
            this.checkEat(X, Y)
            // 开启定时调用
            // 这是递归调用
            this.isLeave && 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.snack.addBody()
            }
        }
    }
    
    export default GameControl
    

    项目源码链接

    Github

    以上就是利用TypeScript编写贪吃蛇游戏的详细内容,更多关于TypeScript贪吃蛇游戏的资料请关注自由互联其它相关文章!

    上一篇:Promise+async+Generator的实现原理
    下一篇:没有了
    网友评论