【Tank】8.0 爆炸效果实现、老巢、boss碰撞检测
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.