js css html

【Tank】4.0 位置管理、代码调优、砖墙模型渲染

2022-05-12  本文已影响0人  bobokaka

本篇章最终文件结构如下:


image.png

模型位置管理服务

image.png

暂时设定为最顶层为敌方坦克出生区域,底部为我方老巢和我方坦克出生区域。

为达到这样的效果,修改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: ConstructorModel) {
        this.positionCollection(num).forEach((position) => {
            const instance = new model(this.canvas, position.x, position.y)
            instance.render()//渲染贴图
            // 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() {
        debugger
        let x: number, y: number;
        let gridNumX = config.canvas.width / config.model.straw.width;
        // 随机格子数量
        let leftNumX = Math.floor(Math.random() * gridNumX)
        //转换成px
        x = leftNumX * config.model.straw.width


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

坐标的位置其他代码中也要使用,这时候可以剥离出来,也就是说不完全服务于我们的“草地画布”,可能其他画布要用,或者模型要用。

新建src/service/position.ts

/**
 * 服务
 * 位置生成
 */
import config from "../config";

class Position {
    public getPositionCollection(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
    }

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


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

export default new Position()

修改src/canvas/abstract/AbstractCanvas.ts

import config from "../../config";
import position from "../../service/position";
/**
 * 抽象类
 */
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

        // 最终元素要放到我们的app的div中
        // @ts-ignore
        this.app.insertAdjacentElement('afterbegin', this.el)
    }

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

防止不同模型重叠

src/service/position.ts

/**
 * 服务
 * 位置生成
 */
import config from "../config";

type positionType = { x: number, y: number }

class Position {
    // 集合包括砖墙、草地、砖块,都在里面
    collection: positionType[] = []

    public getPositionCollection(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 = this.collection.some(item =>
                    item.x == position.x && item.y == position.y)
                if (!exists || count > 4000) {
                    collection.push(position)
                    this.collection.push(position)
                    break;
                }
                // 防止死循环
                count++;
            }
        }
        return collection
    }

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


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

export default new Position()

画布重绘不断新建实例的bug修复

这里有一个画布的bug,在我们不断地重绘过程中,会不断的生成,也就是说,比如草地,会不断的变换位置。


image.png

首先将实例新建和渲染到画布分成2步骤。
src/canvas/abstract/AbstractCanvas.ts

import config from "../../config";
import position from "../../service/position";

/**
 * 抽象类
 */
export default abstract class AbstractCanvas {
    // 元素实例
    protected models: IModel[] = []

    //构造函数渲染
    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 createModels(num: number, model: ConstructorModel) {
        position.getPositionCollection(num).forEach((position) => {
            const instance = new model(this.canvas, position.x, position.y)
            this.models.push(instance)
            // 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 renderModels() {
        this.models.forEach(model => model.render())
    }

}

src/canvas/Straw.ts

/**
 * 画布
 * 草地
 */
import config from "../config";
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelStraw from '../model/Straw'

class Straw extends AbstractCanvas {
    // 构造函数,初始时run一次
    constructor() {
        super();
        // super:调用父类的方法
        super.createModels(config.straw.num, ModelStraw)
    }

    render(): void {
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }
}


// 草地在一个图层,所以只需要new一个实例即可。
export default new Straw()

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() {
        debugger
        this.canvas.drawImage(
            image.get("straw")!,
            this.x,
            this.y,
            config.model.straw.width,
            config.model.straw.height
        )
    }
}

修改src/vite-env.d.ts

/// <reference types="vite/client" />
/**
 * 全局声明
 */

/**
 * 模型对象
 */
interface ConstructorModel {
    new(canvas: CanvasRenderingContext2D,
        x: number,
        y: number): any
}

/**
 * 模型实现的函数、方法
 */
interface IModel {
    render(): void
}

将src/model/ModelStraw.ts重命名src/model/Straw.ts
运行:


image.png

砖墙

同草地模型。

新建src/canvas/Wall.ts

/**
 * 画布
 * 墙
 */
import config from "../config";
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelWall from '../model/Wall'

class Wall extends AbstractCanvas {
    // 构造函数,初始时run一次
    constructor() {
        super();
        // super:调用父类的方法
        super.createModels(config.wall.num, ModelWall)
    }

    render(): void {
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }
}


// 墙在一个图层,所以只需要new一个实例即可。
export default new Wall()

修改src/model/abstract/AbstractModel.ts

import config from "../../config";

/**
 * 抽象类
 */
export default abstract class AbstractModel {
    //构造函数渲染
    constructor(
        protected canvas: CanvasRenderingContext2D,
        protected x: number,
        protected y: number
    ) {
    }

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

    // 渲染函数
    protected draw(img: HTMLImageElement) {
        debugger
        this.canvas.drawImage(
            img,
            this.x,
            this.y,
            config.model.straw.width,
            config.model.straw.height
        )
    }
}

修改src/model/Straw.ts

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

export default class ModelStraw extends AbstractModel implements IModel {
    // 继承父类抽象方法:渲染贴图
    // 一些初始化自定义的动作、行为,都在这里进行
    render(): void {
        super.draw(image.get("straw")!)
    }
}

新建src/model/Wall.ts

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

export default class ModelWall extends AbstractModel implements IModel {
    // 继承父类抽象方法:渲染贴图
    // 一些初始化自定义的动作、行为,都在这里进行
    render(): void {
        super.draw(image.get("brickWall")!)
    }
}

修改配置文件

// 草地
import imgUrlStraw from './static/images/straw/straw.png'
// 砖墙
import imgUrlBrickWall from './static/images/wall/wall.gif'
import imgUrlTankTop from './static/images/tank/top.gif'

export default {
    // 画布
    canvas: {
        width: 900,
        height: 600,
    },
    // 模型
    model: {
        // 草地
        straw: {
            width: 30,
            height: 30,
        }
    },
    straw: {
        num: 100,
    },
    wall: {
        num: 100,
    },
    // 图片
    images: {
        // 草地
        straw: imgUrlStraw,
        brickWall: imgUrlBrickWall,
        tank: imgUrlTankTop
    }
}

这时候,只需要在主函数中声明渲染即可。
src/main.ts

import config from './config'
import './style.scss'
import canvasStraw from './canvas/Straw'
import canvasWall from './canvas/Wall'
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()
    canvasWall.render()
}

void bootstrap()

效果如下:


image.png

此时html中相当于创建了2个画布,并且块级元素堆砌:


image.png

因此需要调整scss:
src/style.scss

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

    canvas {
      position: absolute;
    }

  }
}
image.png
上一篇 下一篇

猜你喜欢

热点阅读