美文网首页让前端飞
【2.0 tank】坦克大战项目初始化、设计思想、编程思想

【2.0 tank】坦克大战项目初始化、设计思想、编程思想

作者: bobokaka | 来源:发表于2022-05-11 07:44 被阅读0次

    创建项目

    npm create vite
    Ok to proceed? (y) y
    ? Project name: » tank
    >   vanilla
    >   vanilla-ts
    

    通过vscode或者webstorm打开项目,并运行:

    npm install
    
    image.png

    本篇章最终的文件结构:


    image.png

    package.json

    {
      "name": "tank",
      "private": true,
      "version": "0.0.0",
      "scripts": {
        "dev": "vite",
        "build": "tsc && vite build",
        "preview": "vite preview"
      },
      "devDependencies": {
        "sass": "^1.51.0",
        "typescript": "^4.5.4",
        "vite": "^2.9.7"
      }
    }
    

    开发思路

    类似于ps的图层,不同的图层叠加,形成一张图片。
    不同的图层管理不同的内容,比如:
    一张图层管理草地;
    一张图层管理墙;
    一张图层管理敌方坦克;
    一张图层管理我方坦克;

    然后,就是控制多久将某个图层重新画一次。

    画草地

    首先需要初始化一下样式。
    当然不需要自己造轮子。
    打开https://www.bootcdn.cn/

    image.png
    image.png
    将minireset.min.css弄到项目下。
    image.png
    修改index.html
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" type="image/svg+xml" href="favicon.svg" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Vite App</title>
        <link href="./src/style/minireset.min.css" rel="stylesheet">
      </head>
      <body>
        <div id="app"></div>
        <script type="module" src="/src/main.ts"></script>
      </body>
    </html>
    

    全局配置项src/config.ts

    export default {
        canvas: {
            width: 900,
            height: 600,
        }
    }
    

    修改src/main.ts

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

    修改src/style.scss

    body {
      background-color: #000;
      //视图的宽度
      width: 100vw;
      //视图的高度
      height: 100vh;
      display: flex;
      /*主轴*/
      justify-content: center;
      /*交叉轴*/
      align-items: center;
      //div画布默认就是居中
      #app {
        background-color: red;
      }
    }
    

    画布abstract抽象类

    首先准备图片,比如草地。
    src/static/images/straw/straw.png

    straw.png
    新建草地图层。草地在一个图层,所以只需要new一个实例即可。
    src/canvas/straw.ts
    import config from "../config";
    
    class straw {
        //构造函数渲染
    
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            el.width = config.canvas.width
            // @ts-ignore
            el.height = config.canvas.height
    
            //最终元素要放到我们的app的div中
            // @ts-ignore
            app.insertAdjacentElement('afterbegin',el)
        }
    }
    
    
    // 草地在一个图层,所以只需要new一个实例即可。
    export default new straw()
    

    引入src/main.ts

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

    其实无论是草地、砖墙、还是坦克,都是类似的构造和创建类,因此可以将方法抽象如下:
    新建src/canvas/abstract/canvas.ts

    import config from "../../config";
    
    /**
     * 抽象类
     */
    export default abstract class canvasAbstract{
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            el.width = config.canvas.width
            // @ts-ignore
            el.height = config.canvas.height
    
            //最终元素要放到我们的app的div中
            // @ts-ignore
            app.insertAdjacentElement('afterbegin',el)
        }
    }
    

    修改src/canvas/straw.ts

    import canvasAbstract from "./abstract/canvas";
    
    class straw extends canvasAbstract{
    
    }
    
    
    // 草地在一个图层,所以只需要new一个实例即可。
    export default new straw()
    
    

    新建src/canvas/tank.ts

    import canvasAbstract from "./abstract/canvas";
    
    class tank extends canvasAbstract{
    
    }
    
    // 坦克在一个图层,所以只需要new一个实例即可。
    export default new tank()
    

    效果一致,代码达到复用状态。

    将构造函数优化代码。
    src/canvas/abstract/canvas.ts

    import config from "../../config";
    
    /**
     * 抽象类
     */
    export default abstract class canvasAbstract {
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
        }
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
            // 测试画布
            // 定义填充颜色
            this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    }
    
    
    image.png

    为了多个画布叠加,就需要像图层一样,画布透明叠加。

    模型

    比如,坦克的画布有了。
    那需要准备需要敌方坦克,可能需要20,30个坦克,每一个坦克都是一个对象,
    每一个坦克对象需要记录很多属性,包括但不限于坐标、方向、血量。
    每一个坦克对象不能有重叠。
    坦克还可以被打爆。

    所以,就需要有一个容器,来记录坦克。

    我们有4个地方可以放置坦克数量。第一个是配置工具类src/config.ts,另一个就是src/canvas/tank.ts,画布,因为每一局坦克画布是唯一的;第三个地方是抽象类,相当于每一个画布中都记录有多少个元素。第四个,单独建立一个配置文件存储。
    这里我们通过画布抽象类进行初始化。
    src/canvas/abstract/canvas.ts

    import config from "../../config";
    
    /**
     * 抽象类
     */
    export default abstract class canvasAbstract {
        // 元素
        protected atom = []
    
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
        }
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
    
            // 测试画布
            // 定义填充颜色
            // this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            // this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    }
    

    在画布中渲染模型

    修改src/canvas/abstract/canvas.ts

    import config from "../../config";
    import imgUrl from '../../static/images/straw/straw.png'
    
    /**
     * 抽象类
     */
    export default abstract class canvasAbstract {
        // 元素
        protected atom = []
    
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
            this.drawModels()
        }
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
    
            // 测试画布
            // 定义填充颜色
            // this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            // this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    
        // 绘制模型
        protected drawModels() {
            const img = document.createElement('img')
            img.src = imgUrl;
            //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
            img.onload = () => {
                this.canvas.drawImage(img, 0, 0, 50, 50);
            }
    
        }
    }
    

    修改src/style.scss

    body {
      background-color: #000;
      //视图的宽度
      width: 100vw;
      //视图的高度
      height: 100vh;
      display: flex;
      /*主轴*/
      justify-content: center;
      /*交叉轴*/
      align-items: center;
      //div画布默认就是居中
      #app {
        background-color: #333;
      }
    }
    

    如下,效果就出来了:


    image.png

    将模型位置随机和大小可配置化。
    修改src/config.ts

    export default {
        canvas: {
            width: 900,
            height: 600,
        },
        model:{
            // 草地
            straw:{
                width: 30,
                height: 30,
            }
        }
    }
    

    修改src/canvas/abstract/canvas.ts

    import config from "../../config";
    import imgUrl from '../../static/images/straw/straw.png'
    
    /**
     * 抽象类
     */
    export default abstract class canvasAbstract {
        // 元素
        protected atom = []
    
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
            this.drawModels()
        }
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
    
            // 测试画布
            // 定义填充颜色
            // this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            // this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    
        // 绘制模型
        protected drawModels() {
            const img = document.createElement('img')
            img.src = imgUrl;
            //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
            img.onload = () => {
                const position=this.position()
                this.canvas.drawImage(img, position.x, position.y, config.model.straw.width, config.model.straw.height);
            }
        }
    
        // 返回随机位置
        protected position() {
            return {
                x: 20, y: 30
            }
        }
    }
    

    效果如下:


    image.png

    这里可以看出模型的渲染如果多次多个的话,代码特别冗余,可以抽写成工具函数。
    新建src/service/image.ts

    /**
     * 服务
     * 图片处理
     */
    import config from "../config";
    // keyof typeof config.images 提供非常好的类型提示
    type mapKey = keyof typeof config.images
    /**
     * 贴图图片
     */
    export const image = new Map<mapKey, HTMLImageElement>()
    
    // Object.entries() 返回给定对象自身可枚举属性的键值对数组
    // 例如
    // { foo: 'bar', baz: 42 }  => [ ['foo', 'bar'], ['baz', 42] ]
    // 'foo'                    => [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]
    export const promises = Object.entries(config.images).map(([key, value]) => {
        // console.log(key,value)
        return new Promise((resolve) => {
            const img = document.createElement('img')
            img.src = value
            // //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
            img.onload = () => {
                image.set(key as mapKey, img)
                resolve(img)
            }
        })
    })
    

    修改src/config.ts

    import imgUrlStraw from './static/images/straw/straw.png'
    import imgUrlTankTop from './static/images/tank/top.gif'
    
    export default {
        // 画布
        canvas: {
            width: 900,
            height: 600,
        },
        // 模型
        model: {
            // 草地
            straw: {
                width: 30,
                height: 30,
            }
        },
        // 图片
        images: {
            // 草地
            straw: imgUrlStraw,
            tank: imgUrlTankTop
        }
    }
    
    

    修改src/main.ts

    import config from './config'
    import './style.scss'
    import './canvas/straw'
    import {image, promises} from "./service/image";
    
    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'))
    }
    
    void bootstrap()
    
    
    image.png

    优化模型贴图逻辑

    修改src/main.ts

    import config from './config'
    import './style.scss'
    import canvasStraw from './canvas/straw'
    import {promises} from "./service/image";
    
    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()
    }
    
    void bootstrap()
    

    增加抽象方法。
    src/canvas/abstract/canvas.ts

    import config from "../../config";
    import {image} from "../../service/image";
    
    /**
     * 抽象类
     */
    export default abstract class canvasAbstract {
        // 元素
        protected atom = []
    
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
    
        }
    
        // 抽象方法:渲染贴图
        abstract render(): void
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
    
            // 测试画布
            // 定义填充颜色
            // this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            // this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    
        // 绘制模型
        // protected:子类可以调用,外部不能调用
        protected drawModels() {
            const position = this.position()
            this.canvas.drawImage(
                image.get('straw')!,
                position.x,
                position.y,
                config.model.straw.width,
                config.model.straw.height
            );
            // const img = document.createElement('img')
            // img.src = imgUrl;
            // //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
            // img.onload = () => {
            //     const position = this.position()
            //     this.canvas.drawImage(img, position.x, position.y, config.model.straw.width, config.model.straw.height);
            // }
        }
    
        // 返回随机位置
        protected position() {
            return {
                x: 20, y: 30
            }
        }
    }
    

    继承子类修复报错即可。
    src/canvas/straw.ts

    /**
     * 画布
     * 草地
     */
    import canvasAbstract from "./abstract/canvas";
    
    class straw extends canvasAbstract{
        render(): void {
            // super:调用父类的方法
            super.drawModels()
        }
    }
    
    
    // 草地在一个图层,所以只需要new一个实例即可。
    export default new straw()
    

    多个草地生成

    为遵循开发规范,将src/canvas/abstract/canvas.ts修改为src/canvas/abstract/AbstractCanvas.ts

    import config from "../../config";
    import {image} from "../../service/image";
    
    /**
     * 抽象类
     */
    export default abstract class AbstractCanvas {
        // 元素
        protected atom = []
    
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
    
        }
    
        // 抽象方法:渲染贴图
        abstract render(): void
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
    
            // 测试画布
            // 定义填充颜色
            // this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            // this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    
        // 绘制模型
        // protected:子类可以调用,外部不能调用
        //num: 渲染多少个数量
        protected drawModels(num: number) {
            Array(num).fill('').forEach(() => {
                const position = this.position()
                this.canvas.drawImage(
                    image.get('straw')!,
                    position.x,
                    position.y,
                    config.model.straw.width,
                    config.model.straw.height
                );
            })
    
            // const img = document.createElement('img')
            // img.src = imgUrl;
            // //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
            // img.onload = () => {
            //     const position = this.position()
            //     this.canvas.drawImage(img, position.x, position.y, config.model.straw.width, config.model.straw.height);
            // }
        }
    
        // 返回随机位置
        protected position() {
            return {
                x: Math.floor(
                        Math.random() *
                        config.canvas.width /
                        config.model.straw.width) *
                    config.model.straw.width,
                y: Math.floor(
                        Math.random() *
                        config.canvas.height /
                        config.model.straw.height) *
                    config.model.straw.height,
            }
        }
    }
    

    修改src/config.ts

    import imgUrlStraw from './static/images/straw/straw.png'
    import imgUrlTankTop from './static/images/tank/top.gif'
    
    export default {
        // 画布
        canvas: {
            width: 900,
            height: 600,
        },
        // 模型
        model: {
            // 草地
            straw: {
                width: 30,
                height: 30,
            }
        },
        straw: {
            num: 20
        },
        // 图片
        images: {
            // 草地
            straw: imgUrlStraw,
            tank: imgUrlTankTop
        }
    }
    

    修改src/canvas/straw.ts为src/canvas/Straw.ts

    /**
     * 画布
     * 草地
     */
    import config from "../config";
    import AbstractCanvas from "./abstract/AbstractCanvas";
    
    class Straw extends AbstractCanvas {
        render(): void {
            // super:调用父类的方法
            super.drawModels(config.straw.num)
        }
    }
    
    
    // 草地在一个图层,所以只需要new一个实例即可。
    export default new Straw()
    
    

    这里随机20个草地:


    image.png

    解决草地重叠问题——批量生成唯一坐标

    新增批量生成草地的方法
    修改src/canvas/abstract/AbstractCanvas.ts

    import config from "../../config";
    import {image} from "../../service/image";
    
    /**
     * 抽象类
     */
    export default abstract class AbstractCanvas {
        // 元素
        protected atom = []
    
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
        }
    
        // 抽象方法:渲染贴图
        abstract render(): void
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
    
            // 测试画布
            // 定义填充颜色
            // this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            // this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    
        // 绘制模型
        // protected:子类可以调用,外部不能调用
        //num: 渲染多少个数量
        protected drawModels(num: number) {
            this.positionCollection(num).forEach((position) => {
                this.canvas.drawImage(
                    image.get('straw')!,
                    position.x,
                    position.y,
                    config.model.straw.width,
                    config.model.straw.height
                );
            })
            // Array(num).fill('').forEach(() => {
            //     const position = this.position()
            //     this.canvas.drawImage(
            //         image.get('straw')!,
            //         position.x,
            //         position.y,
            //         config.model.straw.width,
            //         config.model.straw.height
            //     );
            // })
    
            // const img = document.createElement('img')
            // img.src = imgUrl;
            // //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
            // img.onload = () => {
            //     const position = this.position()
            //     this.canvas.drawImage(img, position.x, position.y, config.model.straw.width, config.model.straw.height);
            // }
        }
    
        protected positionCollection(num: number) {
            const collection = [] as { x: number, y: number }[]
            for (let i = 0; i < num; i++) {
                let count = 0
                while (true) {
                    const position = this.position()
                    const exists = collection.some(item =>
                        item.x == position.x && item.y == position.y)
                    if (!exists || count > 4000) {
                        collection.push(position)
                        break;
                    }
                    // 防止死循环
                    count++;
                }
            }
            return collection
        }
    
        // 返回随机位置
        protected position() {
            return {
                x: Math.floor(
                        Math.random() *
                        config.canvas.width /
                        config.model.straw.width) *
                    config.model.straw.width,
                y: Math.floor(
                        Math.random() *
                        config.canvas.height /
                        config.model.straw.height) *
                    config.model.straw.height,
            }
        }
    }
    
    image.png

    模型构建

    以上只是在构造类中通过草地举例。下面将模型的渲染封装到model中。
    将src/model/straw.ts改为src/model/ModelStraw.ts

    /**
     * 模型
     * 草地
     */
    import {image} from "../service/image";
    import config from "../config";
    
    export default class modelStraw {
        constructor(
            protected canvas: CanvasRenderingContext2D,
            protected x: number,
            protected y: number
        ) {
            this.canvas.drawImage(
                image.get("straw")!,
                x,
                y,
                config.model.straw.width,
                config.model.straw.height
            )
        }
    }
    

    修改src/canvas/abstract/AbstractCanvas.ts

    import config from "../../config";
    
    /**
     * 抽象类
     */
    export default abstract class AbstractCanvas {
        // 元素
        protected atom = []
    
        //构造函数渲染
        constructor(
            protected app = document.querySelector('#app') as HTMLDivElement,
            // @ts-ignore
            protected el = document.createElement<HTMLCanvasElement>('canvas')!,
            // @ts-ignore
            protected canvas = el.getContext('2d')!
        ) {
            this.createCanvas()
        }
    
        // 抽象方法:渲染贴图
        abstract render(): void
    
        // 初始化canvas
        protected createCanvas() {
            // 元素的宽高就是全局canvas得到宽高
            // @ts-ignore
            this.el.width = config.canvas.width
            // @ts-ignore
            this.el.height = config.canvas.height
    
            // 测试画布
            // 定义填充颜色
            // this.canvas.fillStyle = '#16a085'
            // 绘制矩形
            // this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
    
            // 最终元素要放到我们的app的div中
            // @ts-ignore
            this.app.insertAdjacentElement('afterbegin', this.el)
        }
    
        // 绘制模型
        // protected:子类可以调用,外部不能调用
        //num: 渲染多少个数量
        //model: 模型
        protected drawModels(num: number, model: any) {
            this.positionCollection(num).forEach((position) => {
                new model(this.canvas, position.x, position.y)
                // this.canvas.drawImage(
                //     image.get('straw')!,
                //     position.x,
                //     position.y,
                //     config.model.straw.width,
                //     config.model.straw.height
                // );
            })
            // Array(num).fill('').forEach(() => {
            //     const position = this.position()
            //     this.canvas.drawImage(
            //         image.get('straw')!,
            //         position.x,
            //         position.y,
            //         config.model.straw.width,
            //         config.model.straw.height
            //     );
            // })
    
            // const img = document.createElement('img')
            // img.src = imgUrl;
            // //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
            // img.onload = () => {
            //     const position = this.position()
            //     this.canvas.drawImage(img, position.x, position.y, config.model.straw.width, config.model.straw.height);
            // }
        }
    
        protected positionCollection(num: number) {
            const collection = [] as { x: number, y: number }[]
            for (let i = 0; i < num; i++) {
                let count = 0
                while (true) {
                    const position = this.position()
                    const exists = collection.some(item =>
                        item.x == position.x && item.y == position.y)
                    if (!exists || count > 4000) {
                        collection.push(position)
                        break;
                    }
                    // 防止死循环
                    count++;
                }
            }
            return collection
        }
    
        // 返回随机位置
        protected position() {
            return {
                x: Math.floor(
                        Math.random() *
                        config.canvas.width /
                        config.model.straw.width) *
                    config.model.straw.width,
                y: Math.floor(
                        Math.random() *
                        config.canvas.height /
                        config.model.straw.height) *
                    config.model.straw.height,
            }
        }
    }
    

    修改src/canvas/Straw.ts

    /**
     * 画布
     * 草地
     */
    import config from "../config";
    import AbstractCanvas from "./abstract/AbstractCanvas";
    import ModelStraw from '../model/ModelStraw'
    
    class Straw extends AbstractCanvas {
        render(): void {
            // super:调用父类的方法
            super.drawModels(config.straw.num, ModelStraw)
        }
    }
    
    
    // 草地在一个图层,所以只需要new一个实例即可。
    export default new Straw()
    

    效果一致。


    image.png

    模型抽象类

    同样,模型的构造也可以1抽象成公共构造类
    新建src/model/abstract/AbstractModel.ts

    import {image} from "../../service/image";
    import config from "../../config";
    
    /**
     * 抽象类
     */
    export default abstract class AbstractModel {
        //构造函数渲染
        constructor(
            protected canvas: CanvasRenderingContext2D,
            protected x: number,
            protected y: number
        ) {
            this.canvas.drawImage(
                image.get("straw")!,
                x,
                y,
                config.model.straw.width,
                config.model.straw.height
            )
        }
    }
    

    修改src/model/ModelStraw.ts

    /**
     * 模型
     * 草地
     */
    import AbstractModel from "./abstract/AbstractModel";
    
    export default class modelStraw extends AbstractModel{
    }
    

    结果没有区别。

    为草地等模型提供TS支持

    可以将类型约束声明为全局。
    修改src/vite-env.d.ts

    /// <reference types="vite/client" />
    /**
     * 全局声明
     */
    
    /**
     * 模型对象
     */
    interface ConstructorModel {
        new(canvas: CanvasRenderingContext2D,
            x: number,
            y: number): any
    }
    
    /**
     * 模型实现的函数、方法
     */
    interface IModel {
    }
    

    修改src/model/abstract/AbstractModel.ts

    ......
    import {image} from "../../service/image";
    import config from "../../config";
    
    /**
     * 抽象类
     */
    export default abstract class AbstractModel {
        //构造函数渲染
        constructor(
            protected canvas: CanvasRenderingContext2D,
            protected x: number,
            protected y: number
        ) {
            this.render()
        }
    
        // 抽象方法:渲染贴图
        abstract render(): void
    
        // 渲染函数
        protected draw() {
            this.canvas.drawImage(
                image.get("straw")!,
                this.x,
                this.y,
                config.model.straw.width,
                config.model.straw.height
            )
        }
    }
    ......
    

    修改src/canvas/abstract/AbstractCanvas.ts

    ......
        // 绘制模型
        // protected:子类可以调用,外部不能调用
        //num: 渲染多少个数量
        //model: 模型
        protected drawModels(num: number, model: ConstructorModel) {
            this.positionCollection(num).forEach((position) => {
                const instance = new model(this.canvas, position.x, position.y)
                instance.render()//渲染贴图
        }
    ......
    

    修改src/model/ModelStraw.ts,实现接口和父抽象类的抽象方法。

    /**
     * 模型
     * 草地
     */
    import AbstractModel from "./abstract/AbstractModel";
    
    export default class modelStraw extends AbstractModel implements IModel {
        // 继承父类抽象方法:渲染贴图
        // 一些初始化自定义的动作、行为,都在这里进行
        render(): void {
            super.draw()
        }
    }
    

    效果不变:


    image.png

    相关文章

      网友评论

        本文标题:【2.0 tank】坦克大战项目初始化、设计思想、编程思想

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