Extract explosion logic to ExplosionManager class
Some checks failed
Build / build (push) Failing after 25s

Refactored explosion particle system management from RockFactory to a dedicated ExplosionManager class for better code organization and reusability.

- Created ExplosionManager with particle system pooling
- Removed explosion-specific code from RockFactory
- Simplified collision handler in RockFactory

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-10-30 10:29:56 -05:00
parent cb96b4ea6c
commit bf5d33e1cb
2 changed files with 159 additions and 62 deletions

147
src/explosionManager.ts Normal file
View File

@ -0,0 +1,147 @@
import {
MeshBuilder,
ParticleHelper,
ParticleSystem,
ParticleSystemSet,
Scene,
Vector3
} from "@babylonjs/core";
/**
* Configuration for explosion effects
*/
export interface ExplosionConfig {
/** Size of the explosion pool */
poolSize?: number;
/** Duration of explosion in milliseconds */
duration?: number;
/** Rendering group ID for particles */
renderingGroupId?: number;
}
/**
* Manages explosion particle effects with pooling for performance
*/
export class ExplosionManager {
private explosionPool: ParticleSystemSet[] = [];
private scene: Scene;
private config: Required<ExplosionConfig>;
// Default configuration
private static readonly DEFAULT_CONFIG: Required<ExplosionConfig> = {
poolSize: 10,
duration: 2000,
renderingGroupId: 1
};
constructor(scene: Scene, config?: ExplosionConfig) {
this.scene = scene;
this.config = { ...ExplosionManager.DEFAULT_CONFIG, ...config };
}
/**
* Initialize the explosion pool by pre-creating particle systems
*/
public async initialize(): Promise<void> {
console.log(`Pre-creating ${this.config.poolSize} explosion particle systems...`);
for (let i = 0; i < this.config.poolSize; i++) {
const set = await ParticleHelper.CreateAsync("explosion", this.scene);
set.systems.forEach((system) => {
system.renderingGroupId = this.config.renderingGroupId;
});
this.explosionPool.push(set);
}
console.log(`Created ${this.config.poolSize} explosion particle systems in pool`);
}
/**
* Get an explosion from the pool
*/
private getExplosionFromPool(): ParticleSystemSet | null {
return this.explosionPool.pop() || null;
}
/**
* Return an explosion to the pool after use
*/
private returnExplosionToPool(explosion: ParticleSystemSet): void {
explosion.dispose();
ParticleHelper.CreateAsync("explosion", this.scene).then((set) => {
set.systems.forEach((system) => {
system.renderingGroupId = this.config.renderingGroupId;
});
this.explosionPool.push(set);
});
}
/**
* Play an explosion at the specified position with optional scaling
*/
public playExplosion(position: Vector3, scaling: Vector3 = Vector3.One()): void {
const explosion = this.getExplosionFromPool();
if (!explosion) {
// Pool is empty, create explosion on the fly
console.log("Explosion pool empty, creating new explosion on demand");
ParticleHelper.CreateAsync("explosion", this.scene).then((set) => {
const point = MeshBuilder.CreateSphere("explosionPoint", {
diameter: 0.1
}, this.scene);
point.position = position.clone();
point.isVisible = false;
set.start(point);
setTimeout(() => {
set.dispose();
point.dispose();
}, this.config.duration);
});
} else {
// Use pooled explosion
const point = MeshBuilder.CreateSphere("explosionPoint", {
diameter: 10
}, this.scene);
point.position = position.clone();
point.isVisible = false;
point.scaling = scaling.multiplyByFloats(0.2, 0.3, 0.2);
console.log("Using pooled explosion with", explosion.systems.length, "systems at", position);
// Set emitter and start each system individually
explosion.systems.forEach((system: ParticleSystem, idx: number) => {
system.emitter = point;
system.start();
console.log(` System ${idx}: emitter set to`, system.emitter, "activeCount=", system.getActiveCount());
});
// Stop and return to pool after duration
setTimeout(() => {
explosion.systems.forEach((system: ParticleSystem) => {
system.stop();
});
this.returnExplosionToPool(explosion);
point.dispose();
}, this.config.duration);
}
}
/**
* Get the current number of available explosions in the pool
*/
public getPoolSize(): number {
return this.explosionPool.length;
}
/**
* Dispose of all pooled explosions
*/
public dispose(): void {
this.explosionPool.forEach(explosion => {
explosion.dispose();
});
this.explosionPool = [];
}
}

View File

@ -19,6 +19,7 @@ import {Debug} from "@babylonjs/core/Legacy/legacy";
import {createSphereLightmap} from "./sphereLightmap";
import { GameConfig } from "./gameConfig";
import { MaterialFactory } from "./materialFactory";
import { ExplosionManager } from "./explosionManager";
let _particleData: any = null;
export class Rock {
@ -38,20 +39,17 @@ export class RockFactory {
private static _rockMesh: AbstractMesh;
private static _rockMaterial: PBRMaterial;
private static _originalMaterial: PBRMaterial = null;
private static _explosionPool: ParticleSystemSet[] = [];
private static _poolSize: number = 10;
private static _explosionManager: ExplosionManager;
private static _viewer: PhysicsViewer = null;
public static async init() {
// Pre-create explosion particle systems for pooling
console.log("Pre-creating explosion particle systems...");
for (let i = 0; i < this._poolSize; i++) {
const set = await ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene);
set.systems.forEach((system) => {
system.renderingGroupId =1;
})
this._explosionPool.push(set);
}
console.log(`Created ${this._poolSize} explosion particle systems in pool`);
// Initialize explosion manager
this._explosionManager = new ExplosionManager(DefaultScene.MainScene, {
poolSize: 10,
duration: 2000,
renderingGroupId: 1
});
await this._explosionManager.initialize();
if (!this._rockMesh) {
await this.loadMesh();
@ -85,16 +83,6 @@ export class RockFactory {
importMesh.meshes[0].dispose();
}
}
private static getExplosionFromPool(): ParticleSystemSet | null {
return this._explosionPool.pop() || null;
}
private static returnExplosionToPool(explosion: ParticleSystemSet) {
explosion.dispose();
ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene).then((set) => {
this._explosionPool.push(set);
})
}
public static async createRock(i: number, position: Vector3, size: Vector3,
score: Observable<ScoreEvent>): Promise<Rock> {
@ -145,46 +133,8 @@ export class RockFactory {
eventData.collidedAgainst.transformNode.dispose();
eventData.collidedAgainst.dispose();
// Get explosion from pool (or create new if pool empty)
let explosion = RockFactory.getExplosionFromPool();
if (!explosion) {
console.log("Pool empty, creating new explosion");
ParticleHelper.CreateAsync("explosion", DefaultScene.MainScene).then((set) => {
const point = MeshBuilder.CreateSphere("point", {diameter: 0.1}, DefaultScene.MainScene);
point.position = position.clone();
point.isVisible = false;
set.start(point);
setTimeout(() => {
set.dispose();
point.dispose();
}, 2000);
});
} else {
// Use pooled explosion
const point = MeshBuilder.CreateSphere("point", {diameter: 10}, DefaultScene.MainScene);
point.position = position.clone();
point.isVisible = false;
point.scaling = scaling.multiplyByFloats(.2,.3,.2);
console.log("Using pooled explosion with", explosion.systems.length, "systems at", position);
// Set emitter and start each system individually
explosion.systems.forEach((system: ParticleSystem, idx: number) => {
system.emitter = point; // Set emitter to the collision point
system.start(); // Start this specific system
console.log(` System ${idx}: emitter set to`, system.emitter, "activeCount=", system.getActiveCount());
});
setTimeout(() => {
explosion.systems.forEach((system: ParticleSystem) => {
system.stop();
});
RockFactory.returnExplosionToPool(explosion);
point.dispose();
}, 2000);
}
// Play explosion using ExplosionManager
RockFactory._explosionManager.playExplosion(position, scaling);
}
}
});