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

Here's the polished English translation and refinement of the provided content: --- ### **Implementation of Explosion Effects**

Explosion effects are implemented using 8 GIF images that are sequentially displayed.

/** * Explosion effect implementation * Principle: 8 GIF images are sequentially displayed. * @param model The model to be exploded * @protected */ protected blast(model: IModel) { // Generate an array of 0-7 let list = Array(...Array(8).keys()); // console.log(list) // Use Promise chain for sequential execution // GIF images are loaded asynchronously list.reduce((promise, index) => { return new Promise(resolve => { // Timer with delay for each image transition setTimeout(() => { const img = new Image(); img.src = `src/static/images/blasts/blast${index}.gif`; // Image load completion handler img.onload = () => { // Render the image this.canvas.ctx.drawImage(img, model.x, model.y, model.width, model.height); // Exit the Promise chain resolve(promise); }; }, 60); }); }, Promise.resolve()); } --- ### **Old Base Blocks**

Code for generating random positions:

public position(): {x: number, y: number} { let x: number, y: number; let gridNumX = config.canvas.width / config.model.common.width; // Random number of grid cells let leftNumX = Math.floor(Math.random() * gridNumX); // Convert to pixels x = leftNumX * config.model.common.width; let gridNumY = config.canvas.height / config.model.common.height; // Random number of grid cells let leftNumY = Math.floor(Math.random() * (gridNumY - 4)); // Convert to pixels, with top space y = (leftNumY + 1) * config.model.common.height; return {x, y}; } --- ### **Boss Base Layout**

Code for generating random positions:

public position(): {x: number, y: number} { let x: number, y: number; let gridNumX = config.canvas.width / config.model.common.width; // Random number of grid cells let leftNumX = Math.floor(Math.random() * gridNumX); // Convert to pixels x = leftNumX * config.model.common.width; let gridNumY = config.canvas.height / config.model.common.height; // Random number of grid cells let leftNumY = Math.floor(Math.random() * (gridNumY - 4)); // Convert to pixels, with top space y = (leftNumY + 1) * config.model.common.height; return {x, y}; } --- ### **Canvas Rendering**

Code for rendering walls and obstacles:

render(): void { // Super call super.createModels(); // Create boss wall this.createBossWall(); // Prevent re-creation of model instances super.renderModels(); } // Create boss wall createBossWall() { const casW = config.canvas.width; const casH = config.canvas.height; const modelW = config.model.common.width; const modelH = config.model.common.height; let pos = [ {x: casW / 2 - modelW * 3, y: casH - modelH}, {x: casW / 2 - modelW * 3, y: casH - modelH * 2}, {x: casW / 2 - modelW * 3, y: casH - modelH * 3}, {x: casW / 2 - modelW * 2, y: casH - modelH * 3}, {x: casW / 2 - modelW, y: casH - modelH * 3}, {x: casW / 2, y: casH - modelH * 3}, {x: casW / 2 + modelW, y: casH - modelH * 3}, {x: casW / 2 + modelW, y: casH - modelH * 2}, {x: casW / 2 + modelW, y: casH - modelH}, {x: casW / 2 + modelW, y: casH}, ]; pos.forEach(position => { const model = this.model() as ConstructorModel; const instance = new model(position.x, position.y); this.models.push(instance); }); } --- ### **Image Resources**

Image imports for various elements:

// Grass import imgUrlStraw from './static/images/straw/straw.png'; // Wall import imgUrlWallBrick from './static/images/wall/wall.gif'; // Water import imgUrlWater from './static/images/water/water.gif'; // Steel wall import imgUrlWallSteel from './static/images/wall/steels.gif'; // Tank 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'; // Bullet import imgUrlBullet from './static/images/bullet/bullet.jpg'; // Boss (own base) import imgUrlBoss from './static/images/boss/boss.png'; export default { // Canvas canvas: { width: 900, height: 600, }, // Model model: { common: { width: 30, height: 30, }, bullet: { width: 3, height: 3, }, }, // Grass straw: { num: 100, }, // Wall wallBrick: { num: 100, }, // Steel wall wallSteel: { num: 30, }, // Water water: { num: 40, }, // Enemy tank tank: { num: 40, speed: 10, }, // Bullet speed bullet: { num: 40, speed: 10, }, // Images images: { straw: imgUrlStraw, wallBrick: imgUrlWallBrick, wallSteel: imgUrlWallSteel, water: imgUrlWater, tankTop: imgUrlTankTop, tankRight: imgUrlTankRight, tankBottom: imgUrlTankBottom, tankLeft: imgUrlTankLeft, bullet: imgUrlBullet, boss: imgUrlBoss, } }; --- ### **Main Application**

Code for initializing the application:

import config from './config'; import './style.scss'; import canvasStraw from './canvas/Straw'; import canvasWallBrick from './canvas/WallBrick'; import canvasWater from './canvas/Water'; import canvasWallSteel from './canvas/WallSteel'; import canvasTank from './canvas/Tank'; import { promises } from "./service/image"; import bullet from "./canvas/Bullet"; import boss from "./canvas/Boss"; const app = document.querySelector("#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); // Load all images first await Promise.all(promises); // console.log(image.get('straw')); // Render all canvases canvasStraw.render(); // Grass canvasWallBrick.render(); // Wall canvasWallSteel.render(); // Steel wall canvasTank.render(); // Enemy tank canvasWater.render(); // Water bullet.render(); // Bullet boss.render(); // Boss (base) }; void bootstrap(); --- ### **Style Sheet**

Styling for the application:

body { background-color: #000; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; #app { background-color: #000; position: relative; border: solid 12px #333; box-sizing: content-box; canvas { position: absolute; } } } --- ### **Boss Canvas**

Canvas implementation for the boss base:

import AbstractCanvas from "./abstract/AbstractCanvas"; import ModelBoss from "../model/Boss"; import config from "../config"; export default new (class extends AbstractCanvas implements ICanvas { render(): void { this.createModels(); super.renderModels(); } createModels() { const casW = config.canvas.width; const casH = config.canvas.height; const modelW = config.model.common.width; const modelH = config.model.common.height; let position = {x: casW / 2 - modelW, y: casH - modelH}; const model = this.model() as ConstructorModel; const instance = new model(position.x, position.y); this.models.push(instance); } model(): ConstructorModel { return ModelBoss; } num(): number { return 0; } })('boss'); --- ### **Boss Model**

Model implementation for the boss base:

import AbstractModel from "./abstract/AbstractModel"; import {image} from "../service/image"; import config from "../config"; import boss from "../canvas/Boss"; export default class ModelBoss extends AbstractModel implements IModel { name: string = 'boss'; canvas: ICanvas = boss; render(): void { super.draw(); } getImage(): HTMLImageElement { return image.get(this.name as keyof typeof config.images)!; } } --- ### **Abstract Model**

Abstract base class for models:

import config from "../../config"; import {EnumDirection} from "../../enum/enumPosition"; /** * Abstract class */ export default abstract class AbstractModel { public width = config.model.common.width; public height = config.model.common.height; abstract name: string; abstract canvas: ICanvas; public direction: EnumDirection = EnumDirection.top; constructor( public x: number, public y: number ) { this.randomDirection(); } randomDirection(): void { const index = Math.floor((Math.random() * 4)); this.direction = Object.keys(EnumDirection)[index] as EnumDirection; } destroy(): void { this.canvas.removeModel(this); this.canvas.renderModels(); } draw(): void { this.canvas.ctx.drawImage( this.getImage(), this.x, this.y, config.model.common.width, config.model.common.height ); } blast(model: IModel) { let list = Array(...Array(8).keys()); list.reduce((promise, index) => { return new Promise(resolve => { setTimeout(() => { const img = new Image(); img.src = `src/static/images/blasts/blast${index}.gif`; img.onload = () => { this.canvas.ctx.drawImage(img, model.x, model.y, model.width, model.height); resolve(promise); }; }, 60); }); }, Promise.resolve()).then(promise => { }); } } --- ### **Boss Collision Detection**

Collision detection code for the boss:

import wallSteel from "../canvas/WallSteel"; ... // Obstacles that can't be destroyed let touchNotBlowModel = utils.modelTouch(x, y, [ ...wallSteel.models, ], config.model.bullet.width, config.model.bullet.height ); // Obstacles that can be destroyed let touchBlowModel = utils.modelTouch(x, y, [ ...wallBrick.models, ...boss.models, ], config.model.bullet.width, config.model.bullet.height ); --- ### **Final Notes** - All code is preserved with original formatting and syntax. - Technical terms like `ICanvas`, `IModel`, `ConstructorModel` are retained. - Image file names (e.g., `image.png`) are kept as-is. - Code structure is maintained for readability and technical accuracy.