diff --git a/src/explosionManager.ts b/src/explosionManager.ts new file mode 100644 index 0000000..56ac0bc --- /dev/null +++ b/src/explosionManager.ts @@ -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; + + // Default configuration + private static readonly DEFAULT_CONFIG: Required = { + 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 { + 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 = []; + } +} diff --git a/src/rockFactory.ts b/src/rockFactory.ts index bb15456..f35b272 100644 --- a/src/rockFactory.ts +++ b/src/rockFactory.ts @@ -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): Promise { @@ -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); } } });