【Tank】5.0 水域模型、钢墙模型、坦克模型

水域模型、钢墙模型

同砖墙模型的创建。
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'

export default {
    // 画布
    canvas: {
        width: 900,
        height: 600,
    },
    // 模型
    model: {
        // 草地
        straw: {
            width: 30,
            height: 30,
        }
    },
    //草地
    straw: {
        num: 100,
    },
    // 砖墙
    wallBrick: {
        num: 100,
    },
    // 钢墙
    wallSteel:{
        num: 50,
    },
    // 水域
    water:{
        num:40
    },
    // 图片
    images: {
        // 草地
        straw: imgUrlStraw,
        wallBrick: imgUrlWallBrick,
        wallSteel:imgUrlWallSteel,
        water:imgUrlWater,
        tank: imgUrlTankTop
    }
}

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 {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()
    canvasWallBrick.render()
    canvasWater.render()
    canvasWallSteel.render()
}

void bootstrap()

src/canvas/WallBrick.ts

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

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

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


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

src/canvas/WallSteel.ts

/**
 * 画布
 * 墙
 */
import config from "../config";
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelWallSteel from '../model/WallSteel'

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

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


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

src/canvas/Water.ts

/**
 * 画布
 * 水域
 */
import config from "../config";
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelWater from '../model/Water'

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

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


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

src/model/WallBrick.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("wallBrick")!)
    }
}

src/model/WallSteel.ts

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

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

src/model/Water.ts

/**
 * 模型
 * 水域
 */
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";

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

效果如下:


image.png

重构游戏元素生成逻辑

主要将模型数量和模型实例抽象出来,以便初始化。
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

    // 抽象方法,返回模型
    abstract model(): ConstructorModel

    // 抽象方法:返回模型数量
    abstract num(): number

    // 初始化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() {
        position.getPositionCollection(this.num()).forEach((position) => {
            const model = this.model()
            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 implements ICanvas{
    render(): void {
        // super:调用父类的方法
        super.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

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

    // 抽象方法:返回模型数量
    num(): number {
        return config.straw.num;
    }
}


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

src/canvas/WallBrick.ts

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

class WallBrick extends AbstractCanvas implements ICanvas {
    render(): void {
        // super:调用父类的方法
        super.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

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

    // 抽象方法:返回模型数量
    num(): number {
        return config.wallBrick.num
    }
}


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

src/canvas/WallSteel.ts

/**
 * 画布
 * 墙
 */
import config from "../config";
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelWallSteel from '../model/WallSteel'

class WallSteel extends AbstractCanvas implements ICanvas {
    render(): void {
        // super:调用父类的方法
        super.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

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

    // 抽象方法:返回模型数量
    num(): number {
        return config.straw.num
    }

}


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

src/canvas/Water.ts

/**
 * 画布
 * 水域
 */
import config from "../config";
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelWater from '../model/Water'

class Water extends AbstractCanvas implements ICanvas{
    // 抽象方法:渲染贴图
    render(): void {
        // super:调用父类的方法
        super.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

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

    // 抽象方法:返回模型数量
    num(): number {
        return config.water.num
    }
}


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

TS支持提醒优化,src/vite-env.d.ts

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

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

/**
 * 模型实现的函数、方法
 */
interface IModel {
    // 抽象方法:渲染贴图
    render(): void

}
/**
 * 画布实现的函数、方法
 */
interface ICanvas {
    // 抽象方法:渲染贴图
    render(): void

    // 抽象方法,返回模型
    model(): ConstructorModel

    // 抽象方法:返回模型数量
    num(): number
}

运行结果如下:


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'

export default {
    // 画布
    canvas: {
        width: 900,
        height: 600,
    },
    // 模型
    model: {
        // 草地
        straw: {
            width: 30,
            height: 30,
        }
    },
    //草地
    straw: {
        num: 100,
    },
    // 砖墙
    wallBrick: {
        num: 100,
    },
    // 钢墙
    wallSteel:{
        num: 40,
    },
    // 水域
    water:{
        num:40
    },
    // 水域
    tank:{
        num:40
    },
    // 图片
    images: {
        // 草地
        straw: imgUrlStraw,
        wallBrick: imgUrlWallBrick,
        wallSteel:imgUrlWallSteel,
        water:imgUrlWater,
        tank: imgUrlTankTop
    }
}

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";

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()
    canvasWater.render()
    canvasWallSteel.render()
    canvasTank.render()
}

void bootstrap()

src/model/Tank.ts

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

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

src/canvas/Tank.ts

/**
 * 画布
 * 坦克
 */
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelTank from "../model/Tank";
import config from "../config";

class Tank extends AbstractCanvas implements ICanvas {
    render(): void {
        // super:调用父类的方法
        super.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

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

    // 抽象方法:返回模型数量
    num(): number {
        return config.tank.num
    }
}

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

效果如下:


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'

export default {
    // 画布
    canvas: {
        width: 900,
        height: 600,
    },
    // 模型
    model: {
        // 草地
        straw: {
            width: 30,
            height: 30,
        }
    },
    //草地
    straw: {
        num: 100,
    },
    // 砖墙
    wallBrick: {
        num: 100,
    },
    // 钢墙
    wallSteel:{
        num: 30,
    },
    // 水域
    water:{
        num:40
    },
    // 水域
    tank:{
        num:40
    },
    // 图片
    images: {
        // 草地
        straw: imgUrlStraw,
        wallBrick: imgUrlWallBrick,
        wallSteel:imgUrlWallSteel,
        water:imgUrlWater,
        tankTop: imgUrlTankTop,
        tankRight: imgUrlTankRight,
        tankBottom: imgUrlTankBottom,
        tankLeft: imgUrlTankLeft,
    }
}

src/canvas/Tank.ts

/**
 * 画布
 * 坦克
 */
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelTank from "../model/Tank";
import config from "../config";
import position from "../service/position";

class Tank extends AbstractCanvas implements ICanvas {
    render(): void {
        // super:调用父类的方法
        this.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

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

    // 抽象方法:返回模型数量
    num(): number {
        return config.tank.num
    }

    // 重写父类方法
    // 绘制模型,生成模型实例,只负责创建实例
    createModels() {
        for (let i = 0; i < this.num(); i++) {
            const pos = position.position()
            const model = this.model()
            //Y轴永远从0开始
            const instance = new model(this.canvas, pos.x, 0)
            this.models.push(instance)
        }
    }
}

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

src/model/Tank.ts

/**
 * 画布
 * 坦克
 */
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelTank from "../model/Tank";
import config from "../config";
import position from "../service/position";

class Tank extends AbstractCanvas implements ICanvas {
    render(): void {
        // super:调用父类的方法
        this.createModels()
        // 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
        super.renderModels();
    }

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

    // 抽象方法:返回模型数量
    num(): number {
        return config.tank.num
    }

    // 重写父类方法
    // 绘制模型,生成模型实例,只负责创建实例
    createModels() {
        for (let i = 0; i < this.num(); i++) {
            const pos = position.position()
            const model = this.model()
            //Y轴永远从0开始
            const instance = new model(this.canvas, pos.x, 0)
            this.models.push(instance)
        }
    }
}

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

src/enum/enumPosition.ts

/**
 * 枚举类
 *
 */

// 方向
export enum EnumDirection {
    top = 'top',
    right = 'right',
    bottom = 'bottom',
    left = 'left'
}
image.png

Lodash——JS工具库

在src/model/Tank.ts中,如下代码可以优化如下:

......
    // 随机取用其中一个图片
    randomImage(): HTMLImageElement {
        let img: HTMLImageElement;
        switch (this.direction) {
            case EnumDirection.top:
                img = image.get('tankTop')!
                break;
            case EnumDirection.right:
                img = image.get('tankRight')!
                break;
            case EnumDirection.bottom:
                img = image.get('tankBottom')!
                break;
            case EnumDirection.left:
                img = image.get('tankLeft')!
                break;
            default:
                img = image.get('tankTop')!
                break;
        }
        return img
    }
......

安装lodash

 npm install -D  lodash
 npm install -S  @type/lodash

package.json

{
  "name": "tank",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "@types/lodash": "^4.14.182",
    "sass": "^1.51.0",
    "typescript": "^4.5.4",
    "vite": "^2.9.7"
  }
}

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 name: string

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

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

src/model/Tank.ts

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

import {upperFirst} from 'lodash'
import config from "../config";

export default class ModelTank extends AbstractModel implements IModel {

    name: string = 'tank';
    // 方向
    protected direction: EnumDirection = EnumDirection.top
    // 继承父类抽象方法:渲染贴图
    // 一些初始化自定义的动作、行为,都在这里进行
    render(): void {
        this.randomDirection();
        super.draw(this.randomImage())
    }

    randomDirection() {
        //  随机取一个
        const index = Math.floor((Math.random() * 4))
        this.direction = Object.keys(EnumDirection)[index] as EnumDirection
    }

    // 随机取用其中一个图片
    randomImage(): HTMLImageElement {
        return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
        // let img: HTMLImageElement;
        // switch (this.direction) {
        //     case EnumDirection.top:
        //         img = image.get('tankTop')!
        //         break;
        //     case EnumDirection.right:
        //         img = image.get('tankRight')!
        //         break;
        //     case EnumDirection.bottom:
        //         img = image.get('tankBottom')!
        //         break;
        //     case EnumDirection.left:
        //         img = image.get('tankLeft')!
        //         break;
        //     default:
        //         img = image.get('tankTop')!
        //         break;
        // }
        // return img
    }

}

src/model/Straw.ts、src/model/WallBrick.ts、src/model/WallSteel.ts、src/model/Water.ts修改类似,这里不重复贴代码。

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

export default class ModelStraw extends AbstractModel implements IModel {
    name: string = 'straw';// 这里的名字换成对应的贴图名称即可
    // 继承父类抽象方法:渲染贴图
    // 一些初始化自定义的动作、行为,都在这里进行
    render(): void {
        super.draw(image.get(this.name as keyof typeof config.images)!)
    }

}

效果不变:


image.png

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

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