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

创建项目

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

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

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