美文网首页让前端飞
【Tank】9.0 创建玩家;玩家坦克控制、碰撞检测、子弹、坦克

【Tank】9.0 创建玩家;玩家坦克控制、碰撞检测、子弹、坦克

作者: bobokaka | 来源:发表于2022-05-15 14:34 被阅读0次

    创建玩家

    首先加载贴图
    src/config.ts

    ......
    // 玩家坦克
    import imgUrlPlayerTop from './static/images/player/top.gif'
    import imgUrlPlayerRight from './static/images/player/right.gif'
    import imgUrlPlayerBottom from './static/images/player/bottom.gif'
    import imgUrlPlayerLeft from './static/images/player/left.gif'
    
    
    export default {
    ......
        // 图片
        images: {
    ......
            //玩家坦克
            playerTop: imgUrlPlayerTop,
            playerRight: imgUrlPlayerRight,
            playerBottom: imgUrlPlayerBottom,
            playerLeft: imgUrlPlayerLeft
        }
    }
    
    

    从Tank.ts复制Player.ts
    src/canvas/Player.ts

    /**
     * 模型
     * 敌方坦克
     */
    import AbstractModel from "./abstract/AbstractModel";
    import {image} from "../service/image";
    import {EnumDirection} from "../enum/enumPosition";
    
    import {upperFirst} from 'lodash'
    import config from "../config";
    import water from "../canvas/Water";
    import wallBrick from "../canvas/WallBrick";
    import wallSteel from "../canvas/WallSteel";
    import player from "../canvas/Player";
    import utils from "../utils";
    
    export default class ModelTank extends AbstractModel implements IModel {
        name: string = 'player';
    
        // 画布实例
        canvas: ICanvas = player;
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            // 让坦克动
            this.move()
        }
    
        // 随机取用其中一个图片
        getImage(): HTMLImageElement {
            return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
        }
    
        // 坦克行动
        protected move(): void {
            while (true) {
                // 画布清空
                // this.canvas.clearRect(this.x, this.y, config.model.common.width, config.model.common.height);
                // ********************* 坐标更新 *********************
                let x = this.x;
                let y = this.y;
                switch (this.direction) {
                    case EnumDirection.top:
                        y -= 2
                        break;
                    case EnumDirection.right:
                        x += 2
                        break;
                    case EnumDirection.bottom:
                        y += 2
                        break;
                    case EnumDirection.left:
                        x -= 2
                        break;
                }
                if (utils.modelTouch(x, y, [
                    ...water.models,// 水域
                    ...wallBrick.models,// 砖墙
                    ...wallSteel.models,// 钢墙
                ]) || utils.isCanvasTouch(x, y)) {
                    // 随机获取方向
                    this.randomDirection()
                } else {
                    this.x = x;
                    this.y = y;
                    // 跳出while死循环
                    break;
                }
            }
            // ********************* 坐标更新 *********************
            // 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
            super.draw()
        }
    }
    

    src/model/Player.ts

    /**
     * 模型
     * 敌方坦克
     */
    import AbstractModel from "./abstract/AbstractModel";
    import {image} from "../service/image";
    import {EnumDirection} from "../enum/enumPosition";
    
    import {random, upperFirst} from 'lodash'
    import config from "../config";
    import water from "../canvas/Water";
    import wallBrick from "../canvas/WallBrick";
    import wallSteel from "../canvas/WallSteel";
    import player from "../canvas/Player";
    import utils from "../utils";
    
    export default class ModelTank extends AbstractModel implements IModel {
        name: string = 'player';
    
        // 画布实例
        canvas: ICanvas = player;
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            // 让坦克动起来:循环定时器
            // setInterval(() => {
            //     this.move()
            // }, 50)
    
            // 随机转向
            if (random(79) == 1) {
                this.randomDirection()
            }
            // 让坦克动
            this.move()
            // 增加敌方坦克向下移动的概率
            // if (Math.floor(Math.random() * 5) == 1) {
            if (random(79) == 1) {
                this.direction = EnumDirection.bottom
            }
        }
    
        // 随机取用其中一个图片
        getImage(): HTMLImageElement {
            return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
        }
    
        // 坦克行动
        protected move(): void {
            while (true) {
                // 画布清空
                // this.canvas.clearRect(this.x, this.y, config.model.common.width, config.model.common.height);
                // ********************* 坐标更新 *********************
                let x = this.x;
                let y = this.y;
                switch (this.direction) {
                    case EnumDirection.top:
                        y -= 2
                        break;
                    case EnumDirection.right:
                        x += 2
                        break;
                    case EnumDirection.bottom:
                        y += 2
                        break;
                    case EnumDirection.left:
                        x -= 2
                        break;
                }
                if (utils.modelTouch(x, y, [
                    ...water.models,// 水域
                    ...wallBrick.models,// 砖墙
                    ...wallSteel.models,// 钢墙
                ]) || utils.isCanvasTouch(x, y)) {
                    // 随机获取方向
                    this.randomDirection()
                } else {
                    this.x = x;
                    this.y = y;
                    // 跳出while死循环
                    break;
                }
            }
            // ********************* 坐标更新 *********************
            // 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
            super.draw()
        }
    }
    

    src/main.ts

    import player from "./canvas/Player";
    ......
    const bootstrap = async () => {
    ......
        player.render() // 画布渲染:玩家坦克
    }
    ......
    
    image.png

    玩家坦克控制

    src/model/Player.ts

    /**
     * 模型
     * 敌方坦克
     */
    import AbstractModel from "./abstract/AbstractModel";
    import {image} from "../service/image";
    
    import {upperFirst} from 'lodash'
    import config from "../config";
    import player from "../canvas/Player";
    
    export default class ModelTank extends AbstractModel implements IModel {
        name: string = 'player';
    
        // 画布实例
        canvas: ICanvas = player;
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            // 初始化模型
            super.draw()
            // 控制坦克移动,添加键盘监听事件
            document.addEventListener('keydown', this.changeDirection.bind(this))
        }
        
        /**
         * 方向改变
         * @param event 键盘对象
         */
        changeDirection(event: KeyboardEvent) {
            console.log(event)
        }
    
        // 随机取用其中一个图片
        getImage(): HTMLImageElement {
            return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
        }
    }
    
    image.png

    这里可以看到,键盘按键区别类别有code和key。这里我们通过code处理逻辑。

    src/model/Player.ts

    /**
     * 模型
     * 敌方坦克
     */
    import AbstractModel from "./abstract/AbstractModel";
    import {image} from "../service/image";
    
    import {upperFirst} from 'lodash'
    import config from "../config";
    import player from "../canvas/Player";
    import {EnumDirection} from "../enum/enumPosition";
    
    export default class ModelTank extends AbstractModel implements IModel {
        name: string = 'player';
    
        // 画布实例
        canvas: ICanvas = player;
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            //初始化时永远朝上
            // this.direction = EnumDirection.top
            // 初始化模型
            super.draw()
            // 控制坦克移动,添加键盘监听事件
            document.addEventListener('keydown', this.changeDirection.bind(this))
        }
    
        /**
         * 方向改变
         * @param event 键盘对象
         */
        changeDirection(event: KeyboardEvent) {
            // console.log(event)
            switch (event.code) {
                case 'KeyW':
                case 'ArrowUp':
                    this.direction = EnumDirection.top
                    break;
                case 'KeyD':
                case 'ArrowRight':
                    this.direction = EnumDirection.right
                    break;
                case 'KeyS':
                case 'ArrowDown':
                    this.direction = EnumDirection.bottom
                    break;
                case 'KeyA':
                case 'ArrowLeft':
                    this.direction = EnumDirection.left
                    break;
            }
            // 绘制模型
            this.canvas.renderModels()
        }
    
        // 随机取用其中一个图片
        getImage(): HTMLImageElement {
            return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
        }
    }
    

    这里切换方向,会出现卡死。


    image.png

    修复时间无限绑定

    在我们的模型代码中,调用this.canvas.renderModels(),会执行模型的render()方法,随着越按越多,模型会无限增加事件的绑定,最终导致卡死。

    image.png

    解决方法:增加一个事件是否绑定的标记位。
    src/model/Player.ts

    export default class ModelTank extends AbstractModel implements IModel {
        name: string = 'player';
    
        // 画布实例
        canvas: ICanvas = player;
        // 事件是否绑定
        isBindEvent: boolean = false
    
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            //初始化时永远朝上
            // this.direction = EnumDirection.top
            // 初始化模型
            super.draw()
            if (!this.isBindEvent) {
                // 控制坦克移动,添加键盘监听事件
                document.addEventListener('keydown', this.changeDirection.bind(this))
                this.isBindEvent = true
            }
        }
    ......
    }
    

    玩家坦克碰撞检测

    src/model/Player.ts

    import utils from "../utils";
    ......
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            //初始化时永远朝上
            // this.direction = EnumDirection.top
            // 初始化模型
            super.draw()
            if (!this.isBindEvent) {
                // 控制坦克移动,添加键盘监听事件
                document.addEventListener('keydown', this.changeDirection.bind(this))
                document.addEventListener('keydown', this.move.bind(this))
                this.isBindEvent = true
            }
    
        }
    ......
    
        //移动
        move(event: KeyboardEvent) {
            // ********************* 坐标更新 *********************
            let x = this.x;
            let y = this.y;
            switch (event.code) {
                case 'KeyW':
                case 'ArrowUp':
                    y -= 15
                    break;
                case 'KeyD':
                case 'ArrowRight':
                    x += 15
                    break;
                case 'KeyS':
                case 'ArrowDown':
                    y += 15
                    break;
                case 'KeyA':
                case 'ArrowLeft':
                    x -= 15
                    break;
            }
            if (utils.modelTouch(x, y, [
                ...water.models,// 水域
                ...wallBrick.models,// 砖墙
                ...wallSteel.models,// 钢墙
                ...boss.models, //boss
                ...tank.models //敌方坦克
            ]) || utils.isCanvasTouch(x, y)) {
                // 随机获取方向
            } else {
                this.x = x;
                this.y = y;
                // 绘制模型
                this.canvas.renderModels()
            }
        }
    ......
    

    src/model/Tank.ts

    ......
        // 坦克行动
        protected move(): void {
            while (true) {
    ......
                if (utils.modelTouch(x, y, [
                    ...water.models,// 水域
                    ...wallBrick.models,// 砖墙
                    ...wallSteel.models,// 钢墙
                    ...player.models // 我方坦克
                ]) || utils.isCanvasTouch(x, y)) {
                    // 随机获取方向
                    this.randomDirection()
                } else {
                    this.x = x;
                    this.y = y;
                    // 跳出while死循环
                    break;
                }
            }
    ......
    
    image.png

    玩家坦克子弹发射

    /**
     * 模型
     * 敌方坦克
     */
    ......
    import bullet from "../canvas/Bullet";
    
    export default class ModelTank extends AbstractModel implements IModel {
        name: string = 'player';
    
        // 画布实例
        canvas: ICanvas = player;
        // 事件是否绑定
        isBindEvent: boolean = false
    
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            //初始化时永远朝上
            // this.direction = EnumDirection.top
            // 初始化模型
            super.draw()
            if (!this.isBindEvent) {
                this.isBindEvent = true
                // 添加键盘监听事件,坦克方向改变
                document.addEventListener('keydown', this.changeDirection.bind(this))
                // 添加键盘监听事件,坦克移动
                document.addEventListener('keydown', this.move.bind(this))
                // 添加键盘监听事件,坦克发射子弹
                document.addEventListener('keydown', (event: KeyboardEvent) => {
                        console.log(event);
                        //发射子弹,空格,回车,小键盘回车
                        (event.code === 'Space'
                            || event.code === 'Enter'
                            || event.code === 'NumpadEnter') && bullet.addPlayerBullet();
                    }
                )
            }
        }
    ......
    }
    

    src/canvas/Bullet.ts

    ......
            // 我方坦克发射子弹
            addPlayerBullet() {
                this.models.push(new ModelBullet(player.models[0]))
            }
    ......
    
    image.png

    坦克互战

    src/config.ts

    // 草地
    import imgUrlStraw from './static/images/straw/straw.png'
    // 砖墙
    import imgUrlWallBrick from './static/images/wall/wall.gif'
    // 水域
    import imgUrlWater from './static/images/water/water.gif'
    // 钢墙
    import imgUrlWallSteel from './static/images/wall/steels.gif'
    // 敌方坦克
    import imgUrlTankTop from './static/images/tank/top.gif'
    import imgUrlTankRight from './static/images/tank/right.gif'
    import imgUrlTankLeft from './static/images/tank/left.gif'
    import imgUrlTankBottom from './static/images/tank/bottom.gif'
    // 子弹
    import imgUrlBullet from './static/images/bullet/bullet.jpg'
    // boss,己方老巢
    import imgUrlBoss from './static/images/boss/boss.png'
    // 玩家坦克
    import imgUrlPlayerTop from './static/images/player/top.gif'
    import imgUrlPlayerRight from './static/images/player/right.gif'
    import imgUrlPlayerBottom from './static/images/player/bottom.gif'
    import imgUrlPlayerLeft from './static/images/player/left.gif'
    
    
    export default {
        // 画布
        canvas: {
            width: 900,
            height: 600,
        },
        // 模型
        model: {
            common: {
                width: 30,
                height: 30,
            },
            // 子弹
            bullet: {
                width: 3,
                height: 3,
            },
            // boss,己方老巢
            // boss:{
            //     width: 60,
            //     height: 60,
            // }
        },
        //草地
        straw: {
            num: 100,
        },
        // 砖墙
        wallBrick: {
            num: 100,
        },
        // 钢墙
        wallSteel: {
            num: 30,
        },
        // 水域
        water: {
            num: 40
        },
        // 敌方坦克
        tank: {
            num: 40,// 数量
            speed: 3// 速度,越大越快(1-100)
        },
        // 子弹的速度
        bullet: {
            num: 40,// 数量
            speed: 8 // 速度,越大越快(1-100)
        },
        // 图片
        images: {
            // 草地
            straw: imgUrlStraw,
            // 砖墙
            wallBrick: imgUrlWallBrick,
            // 钢墙
            wallSteel: imgUrlWallSteel,
            // 水域
            water: imgUrlWater,
            // 敌方坦克
            tankTop: imgUrlTankTop,
            tankRight: imgUrlTankRight,
            tankBottom: imgUrlTankBottom,
            tankLeft: imgUrlTankLeft,
            // 子弹
            bullet: imgUrlBullet,
            // boss,己方老巢
            boss: imgUrlBoss,
            //玩家坦克
            playerTop: imgUrlPlayerTop,
            playerRight: imgUrlPlayerRight,
            playerBottom: imgUrlPlayerBottom,
            playerLeft: imgUrlPlayerLeft
        }
    }
    
    

    src/canvas/Tank.ts

    ......
    /**
     * 画布是单例模式
     * 在一个图层,所以只需要new一个实例即可。
     */
    export default new (class extends AbstractCanvas implements ICanvas {
        render(): void {
            // super:调用父类的方法
            this.createModels()
            // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
            super.renderModels();
            console.log("tank", Number((100 / config.tank.speed).toFixed(3)))
            // 让坦克画布实时刷新,每config.tank.speed毫秒擦写一次,等于速度。
            setInterval(() => {
                this.renderModels()
            }, Number((100 / config.tank.speed).toFixed(3)))
        }
    ......
    }
    

    src/canvas/Bullet.ts

    ......
    /**
     * 画布是单例模式
     * 在一个图层,所以只需要new一个实例即可。
     */
    export default new (class extends AbstractCanvas implements ICanvas {
            render(): void {
                // super:调用父类的方法
                // super.createModels()
                // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
                // super.renderModels();
                console.log("bullet", Number((100 / config.bullet.speed).toFixed(3)))
                //bind:绑定this
                setInterval(() => {
                    // this.createBullet.bind(this)
                    //自定义子弹的渲染函数:子弹创建
                    this.createModelsBullet()
                    // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
                    super.renderModels();
                }, Number((100 / config.bullet.speed).toFixed(3)))
            }
    ......
    }
    

    src/model/Bullet.ts

    ......
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            // super.draw()
            // ********************* 坐标更新 *********************
            let x = this.x;
            let y = this.y;
            // 子弹速度
            let speed = this.tank.name === 'player' ? 4 : 2
            switch (this.direction) {
                case EnumDirection.top:
                    y -= speed
                    break;
                case EnumDirection.right:
                    x += speed
                    break;
                case EnumDirection.bottom:
                    y += speed
                    break;
                case EnumDirection.left:
                    x -= speed
                    break;
            }
    
            // // ********************* 坐标更新 *********************
            // 打不掉的障碍
            let touchNotBlowModel = utils.modelTouch(x, y, [
                    ...wallSteel.models,// 钢墙
                ],
                config.model.bullet.width,
                config.model.bullet.height
            )
    
            // 打掉的障碍
            let touchBlowModel = utils.modelTouch(x, y, [
                    ...wallBrick.models,// 砖墙
                    ...boss.models,// boss
                ],
                config.model.bullet.width,
                config.model.bullet.height
            )
            // 如果是我方坦克的子弹,需要判断是否打中地方坦克
            let touchTankModel = undefined
            let touchPlayerModel = undefined
            if (this.tank.name === 'player') {
                touchTankModel = utils.modelTouch(x, y, [
                        ...tank.models,// 敌方坦克
                    ],
                    config.model.bullet.width,
                    config.model.bullet.height
                )
            } else {
                touchPlayerModel = utils.modelTouch(x, y, [
                        ...player.models,// 我方坦克
                    ],
                    config.model.bullet.width,
                    config.model.bullet.height
                )
            }
    
            //
            if (touchNotBlowModel || utils.isCanvasTouch(x, y, config.model.bullet.width, config.model.bullet.height)) {
                // 移除模型
                this.destroy()
                // 展示爆炸效果
                touchNotBlowModel && super.blast(touchNotBlowModel)
            } else if (touchPlayerModel || touchTankModel || touchBlowModel) {
                // 当前子弹模型消失
                this.destroy()
    
                // **************** 可打爆的障碍 ****************
                // 展示爆炸效果
                touchBlowModel && super.blast(touchBlowModel)
                // 碰撞的模型消失
                touchBlowModel && touchBlowModel.destroy();
                // **************** 可打爆的障碍 ****************
    
                // **************** 玩家坦克打爆敌方坦克 ****************
                // 展示爆炸效果
                touchTankModel && super.blast(touchTankModel)
                // 碰撞的模型消失
                touchTankModel && touchTankModel.destroy();
                // **************** 玩家坦克打爆敌方坦克 ****************
    
                // **************** 敌方坦克打爆玩家坦克 ****************
                // 展示爆炸效果
                touchPlayerModel && super.blast(touchPlayerModel)
                // 碰撞的模型消失
                touchPlayerModel && touchPlayerModel.destroy();
                // **************** 敌方坦克打爆玩家坦克 ****************
    
            } else {
                this.x = x;
                this.y = y;
                // 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
                this.draw()
            }
        }
    
    ......
    

    玩家坦克被打掉了:


    image.png

    相关文章

      网友评论

        本文标题:【Tank】9.0 创建玩家;玩家坦克控制、碰撞检测、子弹、坦克

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