【Tank】8.0 爆炸效果实现、老巢、boss碰撞检测

爆炸效果实现

爆炸效果主要为8张gif图片依次切换显示组成。
src/model/abstract/AbstractModel.ts

......

    /**
     * 爆炸效果实现
     * 原理:8张gif图片依次切换显示。
     * @param model 被打炸的模型
     * @protected
     */
    protected blast(model: IModel) {
        //生成0-7的数组
        let list = Array(...Array(8).keys())
        // console.log(list)

        // 一个Promise调用完,再调用另一个Promise
        // 图片是异步加载的
        //Array.reduce 接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
        // 第一个参数为function(total,currentValue, index,arr)
        // total 必需。初始值, 或者计算结束后的返回值。
        //     currentValue 必需。当前元素
        //     currentIndex 可选。当前元素的索引
        //     arr  可选。当前元素所属的数组对象。
        // 第二个参数为传递给函数的初始值
        list.reduce((promise, index) => {
            return new Promise(resolve => {
                // 定时器,每个图片的切换都有一定的延时
                setTimeout(()=>{
                    const img = new Image
                    img.src = `src/static/images/blasts/blast${index}.gif`
                    // 图片加载完毕的监听方法
                    img.onload = () => {
                        //渲染图片
                        this.canvas.ctx.drawImage(img, model.x, model.y, model.width, model.height)
                        // 跳出结束Promise
                        resolve(promise)
                    }
                },100)
            })
        }, Promise.resolve())
    }
......

src/model/Bullet.ts

......

    // 一些初始化自定义的动作、行为,都在这里进行
    render(): void {
        // super.draw()
        // ********************* 坐标更新 *********************
        let x = this.x;
        let y = this.y;
        switch (this.direction) {
            case EnumDirection.top:
                y--
                break;
            case EnumDirection.right:
                x++
                break;
            case EnumDirection.bottom:
                y++
                break;
            case EnumDirection.left:
                x--
                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,// 砖墙
            ],
            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 (touchBlowModel) {
            // 当前子弹模型消失
            this.destroy()
            // 展示爆炸效果
            super.blast(touchBlowModel)
            // 碰撞的模型消失
            touchBlowModel.destroy();
        } else {
            this.x = x;
            this.y = y;
            // 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
            this.draw()
        }
    }
......
image.png

老巢砖块

src/service/position.ts

......
    // 返回随机位置
    public position() {
        let x: number, y: number;
        let gridNumX = config.canvas.width / config.model.common.width;
        // 随机格子数量
        let leftNumX = Math.floor(Math.random() * gridNumX)
        //转换成px
        x = leftNumX * config.model.common.width


        let gridNumY = config.canvas.height / config.model.common.height;
        // 随机格子数量
        let leftNumY = Math.floor(Math.random() * (gridNumY - 4))
        //转换成px,且顶部空出一格
        y = (leftNumY + 1) * config.model.common.height
        return {x, y}
    }
......

src/config.ts

......
    // 敌方坦克
    tank: {
        num: 40,// 数量
        speed: 40 // 速度,越小越快
    },
    // 子弹的速度
    bullet: {
        num: 40,// 数量
        speed: 10 // 速度,越小越快
    },
......

src/canvas/WallBrick.ts

   render(): void {
       // super:调用父类的方法
       super.createModels()
       // 创建老巢的墙
       this.createBossWall();
       // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
       super.renderModels();
   }

   // 创建老巢的墙
   createBossWall() {
       const casW = config.canvas.width
       const casH = config.canvas.height
       const modelW = config.model.common.width
       const modelH = config.model.common.height
       let pos = [
           {x: casW / 2 - modelW * 3, y: casH - modelH},
           {x: casW / 2 - modelW * 3, y: casH - modelH * 2},

           {x: casW / 2 - modelW * 3, y: casH - modelH * 3},
           {x: casW / 2 - modelW * 2, y: casH - modelH * 3},
           {x: casW / 2 - modelW, y: casH - modelH * 3},
           {x: casW / 2, y: casH - modelH * 3},
           {x: casW / 2 + modelW, y: casH - modelH * 3},

           {x: casW / 2 + modelW, y: casH - modelH * 2},
           {x: casW / 2 + modelW, y: casH - modelH},
           {x: casW / 2 + modelW, y: casH},
       ]
       pos.forEach(position => {
           const model = this.model() as ConstructorModel
           const instance = new model(position.x, position.y)
           this.models.push(instance)
       })
   }
image.png

boss老巢

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'

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: 10 // 速度,越小越快
    },
    // 子弹的速度
    bullet: {
        num: 40,// 数量
        speed: 10 // 速度,越小越快
    },
    // 图片
    images: {
        // 草地
        straw: imgUrlStraw,
        // 砖墙
        wallBrick: imgUrlWallBrick,
        // 钢墙
        wallSteel: imgUrlWallSteel,
        // 水域
        water: imgUrlWater,
        // 敌方坦克
        tankTop: imgUrlTankTop,
        tankRight: imgUrlTankRight,
        tankBottom: imgUrlTankBottom,
        tankLeft: imgUrlTankLeft,
        // 子弹
        bullet: imgUrlBullet,
        // boss,己方老巢
        boss:imgUrlBoss
    }
}

src/main.ts

import config from './config'
import './style.scss'
import canvasStraw from './canvas/Straw'
import canvasWallBrick from './canvas/WallBrick'
import canvasWater from './canvas/Water'
import canvasWallSteel from './canvas/WallSteel'
import canvasTank from './canvas/Tank'
import {promises} from "./service/image";
import bullet from "./canvas/Bullet";
import boss from "./canvas/Boss";


const app = document.querySelector<HTMLDivElement>("#app")
// @ts-ignore
app.style.width = config.canvas.width + 'px'
// @ts-ignore
app.style.height = config.canvas.height + 'px'


const bootstrap = async () => {
    // console.log(promises)
    //先加载各种贴图
    await Promise.all(promises)
    // console.log(image.get('straw'))
    // 调用render方法渲染
    canvasStraw.render() // 画布渲染:草地
    canvasWallBrick.render() // 画布渲染:砖墙
    canvasWallSteel.render() // 画布渲染:钢墙
    canvasTank.render() // 画布渲染:敌方坦克
    canvasWater.render() // 画布渲染:水域
    bullet.render() // 画布渲染:子弹
    boss.render() // 画布渲染:boss,老巢
}

void bootstrap()

src/style.scss

body {
  background-color: #000;
  //视图的宽度
  width: 100vw;
  //视图的高度
  height: 100vh;
  display: flex;
  /*主轴*/
  justify-content: center;
  /*交叉轴*/
  align-items: center;
  //div画布默认就是居中
  #app {
    background-color: #000;
    position: relative;
    border: solid 12px #333;
    //默认值。如果你设置一个元素的宽为 100px,
    // 那么这个元素的内容区会有 100px 宽,
    // 并且任何边框和内边距的宽度都会被增加到最后绘制出来的元素宽度中
    box-sizing: content-box;

    canvas {
      position: absolute;
    }

  }
}

src/canvas/Boss.ts

/**
 * 画布
 * 草地
 */
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelBoss from "../model/Boss";
import config from "../config";

/**
 * 画布是单例模式
 * 在一个图层,所以只需要new一个实例即可。
 */
export default new (class extends AbstractCanvas implements ICanvas {
    render(): void {
        // super:调用父类的方法
        this.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

    createModels() {
        const casW = config.canvas.width
        const casH = config.canvas.height
        const modelW = config.model.common.width
        const modelH = config.model.common.height
        let position = {x: casW / 2 - modelW, y: casH - modelH}
        const model = this.model() as ConstructorModel
        const instance = new model(position.x, position.y)
        this.models.push(instance)
    }

    // 抽象方法,返回模型
    model(): ConstructorModel {
        return ModelBoss;
    }

    // 抽象方法:返回模型数量,这里不用
    num(): number {
        return 0;
    }
})('boss')

src/model/Boss.ts

/**
 * 模型
 * 草地
 */
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import config from "../config";
import boss from "../canvas/Boss";

export default class ModelBoss extends AbstractModel implements IModel {
    name: string = 'boss';

    // 画布实例
    canvas: ICanvas = boss;

    // 继承父类抽象方法:渲染贴图
    // 一些初始化自定义的动作、行为,都在这里进行
    render(): void {
             super.draw()
    }

    // 获取贴图
    getImage(): HTMLImageElement {
        return image.get(this.name as keyof typeof config.images)!;
    }

}

src/model/abstract/AbstractModel.ts

import config from "../../config";
import {EnumDirection} from "../../enum/enumPosition";

/**
 * 抽象类
 */
export default abstract class AbstractModel {
    // 宽度,碰撞判断需要跨模型调用,所以为public
    public width = config.model.common.width;
    // 高度,碰撞判断需要跨模型调用,所以为public
    public height = config.model.common.height;
    // 抽象属性:模型名称
    abstract name: string
    // 抽象属性:画布实例
    abstract canvas: ICanvas
    // 方向,子弹的方向取决于坦克的方向,需要同层级调用
    public direction: EnumDirection = EnumDirection.top

    //构造函数渲染
    // 碰撞判断需要跨模型调用模型的坐标位置,所以为public
    constructor(
        public x: number,
        public y: number
    ) {
        // 方向随机生成
        this.randomDirection();
    }

    // 抽象方法:渲染贴图
    abstract render(): void

    // 抽象方法:获取贴图
    abstract getImage(): HTMLImageElement

    // 方向随机生成
    randomDirection(): void {
        // 随机取一个
        const index = Math.floor((Math.random() * 4))
        // 存储方向
        this.direction = Object.keys(EnumDirection)[index] as EnumDirection
    }

    // 函数:卸载(移除)模型
    public destroy(): void {
        // 让画布将模型移除掉
        this.canvas.removeModel(this)
        // 重新渲染画布
        this.canvas.renderModels()
    }

    // 函数:渲染模型
    protected draw(): void {
        this.canvas.ctx.drawImage(
            this.getImage(),
            this.x,
            this.y,
            config.model.common.width,
            config.model.common.height
        )
    }

    /**
     * 爆炸效果实现
     * 原理:8张gif图片依次切换显示。
     * @param model 被打炸的模型
     * @protected
     */
    protected blast(model: IModel) {
        //生成0-7的数组
        let list = Array(...Array(8).keys())
        // console.log(list)

        // 一个Promise调用完,再调用另一个Promise
        // 图片是异步加载的
        //Array.reduce 接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
        // 第一个参数为function(total,currentValue, index,arr)
        // total 必需。初始值, 或者计算结束后的返回值。
        //     currentValue 必需。当前元素
        //     currentIndex 可选。当前元素的索引
        //     arr  可选。当前元素所属的数组对象。
        // 第二个参数为传递给函数的初始值

        list.reduce((promise, index) => {
            return new Promise(resolve => {
                // 定时器,每个图片的切换都有一定的延时
                setTimeout(() => {
                    const img = new Image
                    img.src = `src/static/images/blasts/blast${index}.gif`
                    // 图片加载完毕的监听方法
                    img.onload = () => {
                        //渲染图片
                        this.canvas.ctx.drawImage(img, model.x, model.y, model.width, model.height)
                        // 跳出结束Promise
                        resolve(promise)
                    }
                }, 60)
            })
            // @ts-ignore
        }, Promise.resolve()).then(promise => {
        })
    }
}

image.png

boss碰撞检测

src/model/Bullet.ts

import wallSteel from "../canvas/WallSteel";
......
    // 一些初始化自定义的动作、行为,都在这里进行
    render(): void {
......
        // 打不掉的障碍
        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
        )
...... 
}
......
image.png

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):