美文网首页TypeScript基础学习
TypeScript:项目实战—贪吃蛇

TypeScript:项目实战—贪吃蛇

作者: 生命里那束光 | 来源:发表于2022-03-23 09:07 被阅读0次

    项目实战—贪吃蛇

    配置文件 => 画界面 => 写样式 => 写交互 =>最后打包

    先讲一下大概思路再上详细代码:
    • 1.进行项目的搭建,安装项目中需要的包,完成项目前的准备
    • 2.进行项目界面的制作,就是画界面,把大概样子画出来,此时没有任何交互。
    • 3.进行类的定义,定义贪吃蛇里面的各类元素:
      • 食物(Food类): 获取食物的坐标、修改食物的位置(随机生成)
      • 记分牌(ScorePanel类): 记录分数和等级、实现加分功能、实现升级功能
      • 蛇(Snake类): 获取和设置蛇头的坐标、蛇身体变长、蛇不能掉头、蛇身体移动、检查蛇头是否撞到身体
      • 游戏控制器(GameControl类): 键盘事件、使蛇移动、蛇撞墙、吃食检测
    • 4.进行项目的归类,通过导出和引用,优化文件结构和代码,实现index.ts直接使用。

    先展示一下最后效果:

    1.项目搭建

    • 准备好之前的webpack.config.js、tsconfig.json、package.json、package-lock.json四个文件,然后执行npm i安装依赖
    • 安装其他依赖:npm i -D less less-loader css-loader style-loader(四个包,因为要使用到less)如果有其他web资源的话则还需引入web资源的加载器,引入方法类似
    • 修改webpack配置文件—在rules中添加
    //设置less文件的处理
    {
        test: /\.less$/,
        use:[
            "style-loader",
            "css-loader",
            "less-loader"
        ]
    }
    

    这样就能在项目中使用less了。执行npm run build并打开dist中的index.html即可看到效果。

    • 安装postcss来处理css的浏览器兼容性问题:npm i -D postcss postcss-loader postcss-preset-env,并在webpack中引入
    //设置less文件的处理
    {
        test: /\.less$/,
        use:[
            "style-loader",
            "css-loader",
            //引入postcss
            {
                loader: "postcss-loader",
                options: {
                    postcssOptions: {
                        plugins: [
                            [
                                "postcss-preset-env",
                                {
                                    browsers: 'last 2 versions'
                                }
                            ]
                        ]
                    }
                }
            },
            "less-loader"
        ]
    }
    

    这样就可以看到在打包后的js文件中,有些css属性会加上浏览器前缀。

    2.项目界面

    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 id="main">
            <!-- 设置游戏的舞台 -->
            <div id="stage">
                <!-- 设置蛇 -->
                <div id="snake">
                    <!-- snake内部的div 表示蛇的各部分 -->
                    <div></div>
                </div>
    
                <!-- 设置食物 -->
                <div id="food">
                    <!-- 添加4个小div 来设置食物的样式 -->
                    <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.less

    // 设置变量
    @bg-color: #b7d4a8;
    
    // 清除默认样式
    * {
        margin: 0;
        padding: 0;
        // 改变盒子模型的计算方式
        box-sizing: border-box;
    
    }
    
    body {
        font: bold 20px "Courier";
    }
    
    // 设置主窗口的样式
    #main {
        width: 360px;
        height: 420px;
        // 设置背景颜色
        background-color: @bg-color;
        // 设置居中
        margin: 100px auto;
        border: 10px solid black;
        // 设置圆角
        border-radius: 40px;
    
        // 开启弹性盒模型
        display: flex;
        // 设置主轴的方向
        flex-flow: column;
        // 设置辅轴(侧轴)对其方式
        align-items: center;
        // 设置主轴的对齐方式
        justify-content: space-around;
    
        // 游戏舞台
        #stage {
            width: 304px;
            height: 304px;
            border: 2px solid black;
            // 开启相对定位
            position: relative;
    
            // 设置蛇的样式
            #snake {
                &>div {
                    width: 10px;
                    height: 10px;
                    background-color: #000;
                    border: 1px solid @bg-color;
                    // 开启绝对定位
                    position: absolute;
                }
            }
    
            #food {
                // 开启绝对定位
                width: 10px;
                height: 10px;
                position: absolute;
                // background-color: red;
    
                // 开启弹性盒子
                display: flex;
                // 设置横轴为主轴, wrap表示会自动换行
                flex-flow: row wrap;
    
                // 设置主轴和侧轴的空白空间分配到元素之间
                justify-content: space-between;
                align-content: space-between;
    
                left: 40px;
                top: 100px;
    
                &>div{
                    width: 4px;
                    height: 4px;
                    background-color: black;
                    // 使四个div旋转45度
                    transform: rotate(45deg);
                }
            }
        }
    }
    
    // 记分牌
    #score-panel {
        width: 300px;
        display: flex;
        // 设置主轴对齐方式
        justify-content: space-between;
    }
    

    3.定义Food类

    Food类为定义食物的类

    主要实现

    • 获取食物的坐标
    • 修改食物的位置(随机生成)
    // 定义食物类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
            // 蛇移动一次就是一格,一格大小就是10,所以要求食物的坐标必须是整10
    
            // Math.round(Math.random() * 290);//生成一个[0,290]的整数
           let top = Math.round(Math.random() * 29) * 10;
           let left = Math.round(Math.random() * 29) * 10;
            // Math.floor(Math.random() * 30) * 10;//向下取整
    
            this.element.style.left = top + 'px';
            this.element.style.top = left + 'px';
        }
    }
    
    //导出食物模块
    export default Food;
    

    4.定义ScorePanel类

    ScorePanel类为定义记分牌的类

    主要实现

    • 记录分数和等级
    • 实现加分功能
    • 实现升级功能
    // 定义表示记分牌的类
    class ScorePanel{
        // score和level用来记录分数和等级
        score = 0;
        level = 1;
    
        // 分数和等级所在的元素,在构造函数中进行初始化
        scoreEle: HTMLElement;
        levelEle: HTMLElement;
    
        // 设置一个变量限制等级
        maxLevel: number;
        // 设置一个变量表示多少分时升级
        upScore: number;
    
        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 + '';
            this.scoreEle.innerHTML = ++this.score + '';
            // 判断分数是多少
            if (this.score % this.upScore === 0) {
                this.levelUp();
                
            }
        }
    
        // 提升等级的方法
        levelUp(){
            if (this.level < this.maxLevel) {
                this.levelEle.innerHTML = ++this.level + '';
            }
            
        }
    }
    
    
    //导出记分牌模块
    export default ScorePanel;
    

    5.定义Snake类

    Snake类为定义蛇的类

    主要实现

    • 获取和设置蛇头的坐标
    • 蛇身体变长
    • 蛇不能掉头
    • 蛇身体移动
    • 检查蛇头是否撞到身体
    class Snake{
        // 表示蛇头的元素
        head: HTMLElement;
    
        // 蛇的身体(包括蛇头)
        bodies: HTMLCollection;
    
        // 获取蛇的容器
        element: HTMLElement;
    
        constructor(){
            this.element = document.getElementById('snake')!;
            this.head = document.querySelector('#snake > div') as HTMLElement;
            // document.querySelectorAll('#snake > div');// nodeList
            this.bodies = this.element.getElementsByTagName('div');
        }
    
        // 获取蛇的坐标(蛇头坐标)
        get X(){
            return this.head.offsetLeft;
        }
    
        // 获取蛇的Y轴坐标
        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) {
                    // 如果新值value大于旧值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;
            }
            // Y值的合法范围0-290之间
            if (value <0 || value > 290 ) {
                // 进入判断说明蛇撞墙了
                throw new Error("蛇撞墙了~~");
            }
            
            // 修改Y时,是在修改水平坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
            if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
                // console.log('垂直方向发生了掉头');
                // 如果发生了掉头,让蛇向方向继续移动
                if (value > this.Y) {
                    // 如果新值value大于旧值Y, 则说明蛇在向下走,此时发生掉头,应该使蛇继续向上走
                    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(){
            /* 
                从后往前改
                将后面的身体设置为前面身体的位置
                    举例子:
                        第4节 = 第3节的位置
                        第3节 = 第2节的位置
                        第2节 = 蛇头的位置
            */
            //遍历获取所有的身体
            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;
    

    6.定义GameControl类

    GameControl类为游戏控制器,来控制其他的所有类。

    主要实现

    • 键盘事件
    • 使蛇移动
    • 蛇撞墙
    • 吃食检测
    // 引入其他的类
    import Snake from "./Snake";
    import Food from "./Food";
    import ScorePanel from "./ScorePanel";
    
    // 游戏控制器,控制其他的所有类
    class GameControl {
        // 定义三个属性
        // 蛇
        snake: Snake;
        // 食物
        food: Food;
        // 记分牌
        scorePanel: ScorePanel;
    
        // 创建一个属性来存储蛇的移动方向(也就是按键的方向)
        direction: string = '';
        // 创建一个属性用来记录游戏是否结束
        isLive = true;
    
        constructor() {
            this.snake = new Snake();
            this.food = new Food();
            this.scorePanel = new ScorePanel(10, 2);
    
            this.init();
        }
    
        // 游戏的初始化方法,调用后游戏即开始
        init() {
            // 绑定键盘按下的事件
            document.addEventListener('keydown', this.keydownHandler.bind(this));
            // 涉及到this和bind知识
    
            // 调用run()方法,使蛇移动
            this.run();
    
        }
    
        /*  谷歌    ie
            ArrowUp Up
            ArrowDown Down
            ArrowRight Right
            ArrowLeft Left
        */
        // 创建一个键盘按下的响应函数
        keydownHandler(event: KeyboardEvent) {
            // console.log(this);
            // 需要检查event.key的值是否合法(用户是否按了正确的按键)
            // 修改direction属性
            this.direction = event.key
            // console.log(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":
                    // 向上移动 top 减少
                    Y -= 10; 
                    break;
                case "ArrowDown":
                case "Down":
                    // 向下移动 top 增加
                    Y += 10;
                    break;
                case "ArrowLeft":
                case "Left":
                    // 向左移动 left 减少
                    X -= 10;
                    break;
                case "ArrowRight":
                case "Right":
                    // 向右移动 left 增加
                    X += 10;
                    break;
                
    
            }
    
            // 检查蛇是否吃到了食物
            this.checkEat(X, Y);
            // if (this.checkEat(X, Y)) {
            //     console.log('吃到食物了~~');
            //     // 食物的位置进行重置
            //     this.food.change();
            //     // 分数增加
            //     this.scorePanel.addScore();
            //     // 蛇要增加一节
            //     this.snake.addBody();
            // }
    
            // 修改蛇的X和Y值
            try {
                this.snake.X = X;
                this.snake.Y = Y;
            } catch (e) {
                // 进入到catch, 说明出现了异常,游戏结束,弹出一个提示信息
                alert(e.message+ 'GAME OVER!');
                // 将isLive设置为false
                this.isLive = false;
            }
        
    
            // 开启一个定时调用
            clearTimeout();
            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) {
                console.log('吃到食物了~~');
                // 食物的位置进行重置
                this.food.change();
                // 分数增加
                this.scorePanel.addScore();
                // 蛇要增加一节
                this.snake.addBody();
            } 
        }
    
    
    }
    
    //导出游戏控制器模块
    export default GameControl;
    

    7.项目入口文件index.ts

    将上述定义的四个类放在文件夹modules中,并将GameControl引入到index.ts中。

    // 引入样式
    import './style/index.less';
    import GameControl from "./modules/GameControl";
    
    const gameControl = new GameControl();
    
    // setInterval(()=>{
    //     console.log(gameControl.direction);
    // }, 1000);
    

    相关文章

      网友评论

        本文标题:TypeScript:项目实战—贪吃蛇

        本文链接:https://www.haomeiwen.com/subject/dvifjrtx.html