From b6bfdcb95f1970a33ab6fcdfb481c2480ee27dca Mon Sep 17 00:00:00 2001 From: neki-dev Date: Fri, 3 Nov 2023 15:43:08 +0300 Subject: [PATCH] Particles refactoring --- .../scenes/world/effects/particles-manager.ts | 301 ++++++++++++++++++ src/game/scenes/world/effects/particles.ts | 12 +- .../entities/building/variants/generator.ts | 21 +- src/game/scenes/world/entities/npc/npc.ts | 44 +-- .../entities/npc/variants/enemy/enemy.ts | 65 +--- .../npc/variants/enemy/variants/telepath.ts | 32 +- src/game/scenes/world/entities/player.ts | 49 +-- .../scenes/world/entities/shot/ball/ball.ts | 23 +- .../world/entities/shot/ball/variants/fire.ts | 24 +- .../entities/shot/ball/variants/simple.ts | 26 +- src/game/scenes/world/entities/shot/lazer.ts | 20 +- src/game/scenes/world/world.ts | 9 + src/types/world/effects/particles-manager.ts | 19 ++ src/types/world/effects/particles.ts | 3 +- src/types/world/world.ts | 6 + 15 files changed, 373 insertions(+), 281 deletions(-) create mode 100644 src/game/scenes/world/effects/particles-manager.ts create mode 100644 src/types/world/effects/particles-manager.ts diff --git a/src/game/scenes/world/effects/particles-manager.ts b/src/game/scenes/world/effects/particles-manager.ts new file mode 100644 index 00000000..329fbc35 --- /dev/null +++ b/src/game/scenes/world/effects/particles-manager.ts @@ -0,0 +1,301 @@ +import { ENEMY_SIZE_PARAMS, ENEMY_TEXTURE_SIZE } from '~const/world/entities/enemy'; +import { Environment } from '~lib/environment'; +import { GameFlag, GameSettings } from '~type/game'; +import { IWorld } from '~type/world'; +import { IParticlesParent, ParticlesTexture } from '~type/world/effects'; +import { IParticlesManager } from '~type/world/effects/particles-manager'; +import { IBuilding } from '~type/world/entities/building'; +import { INPC } from '~type/world/entities/npc'; +import { EnemyTexture, IEnemy } from '~type/world/entities/npc/enemy'; +import { IPlayer } from '~type/world/entities/player'; +import { ISprite } from '~type/world/entities/sprite'; +import { PositionAtWorld } from '~type/world/level'; + +import { Particles } from './particles'; + +export class ParticlesManager implements IParticlesManager { + private scene: IWorld; + + constructor(scene: IWorld) { + this.scene = scene; + } + + public createDustEffect(parent: IPlayer) { + if (!this.isEffectsEnabled()) { + return null; + } + + return new Particles(parent, { + key: 'dust', + texture: ParticlesTexture.BIT, + dynamic: true, + params: { + followOffset: { + x: 0, + y: -parent.gamut * parent.scaleY * 0.5, + }, + lifespan: { min: 150, max: 300 }, + scale: 0.6, + speed: 10, + frequency: 150, + alpha: { start: 1.0, end: 0.0 }, + emitting: false, + }, + }); + } + + public createBloodEffect(parent: ISprite) { + if ( + !parent.active + || !Environment.GetFlag(GameFlag.BLOOD) + || !this.isEffectsEnabled() + || ParticlesManager.IsExist(parent, 'blood') + ) { + return null; + } + + const scale = Math.min(2.0, parent.displayWidth / 22); + + return new Particles(parent, { + key: 'blood', + texture: ParticlesTexture.BIT_SOFT, + dynamic: true, + params: { + duration: 250, + followOffset: parent.getBodyOffset(), + lifespan: { min: 100, max: 250 }, + scale: { start: scale, end: scale * 0.25 }, + speed: 60, + maxAliveParticles: 6, + tint: 0xdd1e1e, + }, + }); + } + + public createFrozeEffect(parent: INPC) { + if ( + !parent.active + || !this.isEffectsEnabled() + || ParticlesManager.IsExist(parent, 'froze') + ) { + return null; + } + + const lifespan = Math.min(400, parent.displayWidth * 8); + + return new Particles(parent, { + key: 'froze', + texture: ParticlesTexture.BIT_SOFT, + dynamic: true, + params: { + duration: lifespan, + followOffset: parent.getBodyOffset(), + color: [0xffffff, 0x8cf9ff, 0x00f2ff], + colorEase: 'quad.out', + lifespan: { min: lifespan / 2, max: lifespan }, + scale: { start: 1.0, end: 0.5 }, + speed: 80, + }, + }); + } + + public createFireEffect(parent: IEnemy) { + if ( + !parent.active + || !this.isEffectsEnabled() + || ParticlesManager.IsExist(parent, 'fire') + ) { + return null; + } + + const lifespan = parent.displayWidth * 6; + const scale = Math.min(2.0, parent.displayWidth / 22); + + return new Particles(parent, { + key: 'fire', + texture: ParticlesTexture.BIT_SOFT, + dynamic: true, + params: { + duration: lifespan, + followOffset: parent.getBodyOffset(), + color: [0xfacc22, 0xf89800, 0xf83600, 0x9f0404], + colorEase: 'quad.out', + lifespan: { min: lifespan / 2, max: lifespan }, + scale: { start: scale, end: scale * 0.2 }, + alpha: { start: 1.0, end: 0.0 }, + speed: 80, + }, + }); + } + + public createLongFireEffect(parent: IEnemy, params: { duration: number }) { + if (!parent.active || !this.isEffectsEnabled()) { + return null; + } + + const lifespan = parent.displayWidth * 25; + + return new Particles(parent, { + key: 'long-fire', + texture: ParticlesTexture.BIT_SOFT, + dynamic: true, + params: { + followOffset: parent.getBodyOffset(), + duration: params.duration, + color: [0xfacc22, 0xf89800, 0xf83600, 0x9f0404], + colorEase: 'quad.out', + lifespan: { min: lifespan / 2, max: lifespan }, + alpha: { start: 1.0, end: 0.0 }, + angle: { min: -100, max: -80 }, + scale: { + start: parent.displayWidth / 20, + end: 1.0, + ease: 'sine.out', + }, + speed: 40, + advance: 10, + }, + }); + } + + public createLazerEffect(parent: IEnemy) { + if ( + !parent.active + || !this.isEffectsEnabled() + || ParticlesManager.IsExist(parent, 'lazer') + ) { + return null; + } + + const lifespan = parent.displayWidth * 5; + const scale = Math.min(2.25, parent.displayWidth / 18); + + return new Particles(parent, { + key: 'lazer', + texture: ParticlesTexture.BIT_SOFT, + dynamic: true, + params: { + duration: lifespan, + followOffset: parent.getBodyOffset(), + lifespan: { min: lifespan / 2, max: lifespan }, + scale: { start: scale, end: scale * 0.2 }, + alpha: { start: 1.0, end: 0.0 }, + speed: 80, + tint: 0xb136ff, + }, + }); + } + + public createGlowEffect(parent: IParticlesParent, params: { speed: number; color: number }) { + if (!parent.active || !this.isEffectsEnabled()) { + return null; + } + + return new Particles(parent, { + key: 'glow', + texture: ParticlesTexture.GLOW, + dynamic: true, + params: { + scale: 0.2 * parent.scale, + alpha: { start: 1.0, end: 0.0 }, + lifespan: 20000 / params.speed, + frequency: 10000 / params.speed, + tint: params.color, + blendMode: 'ADD', + }, + }); + } + + public createSpawnEffect(parent: IEnemy) { + if (!this.isEffectsEnabled()) { + return null; + } + + // Native body.center isn't working at current state + const size = ENEMY_SIZE_PARAMS[ENEMY_TEXTURE_SIZE[parent.texture.key as EnemyTexture]]; + const position: PositionAtWorld = { + x: parent.x, + y: parent.y - size.height / 2, + }; + const duration = Math.min(700, parent.displayHeight * 17); + const scale = parent.displayWidth / 16; + + return new Particles(parent, { + key: 'spawn', + texture: ParticlesTexture.BIT_SOFT, + position, + params: { + duration, + lifespan: { min: duration / 2, max: duration }, + scale: { start: scale, end: scale / 2 }, + alpha: { start: 1.0, end: 0.0 }, + speed: 40, + quantity: 1, + tint: 0x000000, + }, + }); + } + + public createHealEffect(parent: ISprite, params: { duration: number }) { + if ( + !this.isEffectsEnabled() + || ParticlesManager.IsExist(parent, 'heal') + ) { + return null; + } + + return new Particles(parent, { + key: 'heal', + texture: ParticlesTexture.PLUS, + dynamic: true, + params: { + followOffset: { + x: 0, + y: -parent.displayHeight, + }, + duration: params.duration, + lifespan: params.duration, + alpha: { start: 1.0, end: 0.0 }, + angle: { + min: -110, + max: -70, + }, + scale: { + start: 1.0, + end: 0.5, + }, + speed: 20, + maxAliveParticles: 1, + }, + }); + } + + public createGenerationEffect(parent: IBuilding) { + if (!this.isEffectsEnabled()) { + return null; + } + + return new Particles(parent, { + key: 'generate', + texture: ParticlesTexture.BIT, + position: parent.getTopFace(), + params: { + duration: 300, + lifespan: { min: 100, max: 200 }, + scale: { start: 1.0, end: 0.5 }, + alpha: { start: 1.0, end: 0.0 }, + speed: 60, + maxAliveParticles: 8, + tint: 0x2dffb2, + }, + }); + } + + private isEffectsEnabled() { + return this.scene.game.isSettingEnabled(GameSettings.EFFECTS); + } + + static IsExist(parent: IParticlesParent, key: string) { + return Boolean(parent.effects?.[key]); + } +} diff --git a/src/game/scenes/world/effects/particles.ts b/src/game/scenes/world/effects/particles.ts index 6968e0a7..6be6c41f 100644 --- a/src/game/scenes/world/effects/particles.ts +++ b/src/game/scenes/world/effects/particles.ts @@ -1,5 +1,3 @@ -import { v4 as uuidv4 } from 'uuid'; - import { WORLD_DEPTH_EFFECT } from '~const/world'; import { Assets } from '~lib/assets'; import { IWorld } from '~type/world'; @@ -24,21 +22,17 @@ export class Particles implements IParticles { constructor( parent: IParticlesParent, { - key, position, texture, params, dynamic, replay = false, + key, position, texture, params, dynamic, }: ParticlesData, ) { this.scene = parent.scene; this.parent = parent; - this.key = key ?? uuidv4(); + this.key = key; if (!this.parent.effects) { this.parent.effects = {}; } else if (this.parent.effects[this.key]) { - if (replay) { - this.parent.effects[this.key].destroy(); - } else { - return; - } + this.parent.effects[this.key].destroy(); } this.parent.effects[this.key] = this; diff --git a/src/game/scenes/world/entities/building/variants/generator.ts b/src/game/scenes/world/entities/building/variants/generator.ts index 034834eb..c76fcc29 100644 --- a/src/game/scenes/world/entities/building/variants/generator.ts +++ b/src/game/scenes/world/entities/building/variants/generator.ts @@ -1,12 +1,9 @@ import { DIFFICULTY } from '~const/world/difficulty'; import { Building } from '~entity/building'; import { Tutorial } from '~lib/tutorial'; -import { Particles } from '~scene/world/effects'; -import { GameSettings } from '~type/game'; import { LangPhrase } from '~type/lang'; import { TutorialStep } from '~type/tutorial'; import { IWorld } from '~type/world'; -import { ParticlesTexture } from '~type/world/effects'; import { BuildingTexture, BuildingVariant, @@ -73,22 +70,6 @@ export class BuildingGenerator extends Building { private generateResource() { this.scene.player.giveResources(1); - - if (this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - new Particles(this, { - key: 'generate', - texture: ParticlesTexture.BIT, - position: this.getTopFace(), - params: { - duration: 300, - lifespan: { min: 100, max: 200 }, - scale: { start: 1.0, end: 0.5 }, - alpha: { start: 1.0, end: 0.0 }, - speed: 60, - maxAliveParticles: 8, - tint: 0x2dffb2, - }, - }); - } + this.scene.particles.createGenerationEffect(this); } } diff --git a/src/game/scenes/world/entities/npc/npc.ts b/src/game/scenes/world/entities/npc/npc.ts index 4bfee732..427da55b 100644 --- a/src/game/scenes/world/entities/npc/npc.ts +++ b/src/game/scenes/world/entities/npc/npc.ts @@ -6,11 +6,8 @@ import { NPC_PATH_FIND_RATE } from '~const/world/entities/npc'; import { LEVEL_MAP_PERSPECTIVE } from '~const/world/level'; import { Sprite } from '~entity/sprite'; import { isPositionsEqual, getIsometricAngle, getIsometricDistance } from '~lib/dimension'; -import { Particles } from '~scene/world/effects'; import { Level } from '~scene/world/level'; -import { GameSettings } from '~type/game'; import { IWorld } from '~type/world'; -import { ParticlesTexture } from '~type/world/effects'; import { EntityType } from '~type/world/entities'; import { INPC, NPCData } from '~type/world/entities/npc'; import { PositionAtWorld } from '~type/world/level'; @@ -87,37 +84,18 @@ export class NPC extends Sprite implements INPC { public freeze(duration: number, effects: boolean = false) { this.freezeTimestamp = this.scene.getTime() + duration; - if (!effects) { - return; - } - - if (this.freezeEffectTimer) { - this.freezeEffectTimer.elapsed = 0; - } else { - this.setTint(0x00f2ff); - this.freezeEffectTimer = this.scene.time.delayedCall(duration, () => { - this.clearTint(); - this.freezeEffectTimer = null; - }); - } + if (effects) { + this.scene.particles.createFrozeEffect(this); - if (this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - const lifespan = Math.min(400, this.displayWidth * 8); - - new Particles(this, { - key: 'freeze', - texture: ParticlesTexture.BIT_SOFT, - dynamic: true, - params: { - duration: lifespan, - followOffset: this.getBodyOffset(), - color: [0xffffff, 0x8cf9ff, 0x00f2ff], - colorEase: 'quad.out', - lifespan: { min: lifespan / 2, max: lifespan }, - scale: { start: 1.0, end: 0.5 }, - speed: 80, - }, - }); + if (this.freezeEffectTimer) { + this.freezeEffectTimer.elapsed = 0; + } else { + this.setTint(0x00f2ff); + this.freezeEffectTimer = this.scene.time.delayedCall(duration, () => { + this.clearTint(); + this.freezeEffectTimer = null; + }); + } } } diff --git a/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts b/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts index ce4f58f1..244be48d 100644 --- a/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts +++ b/src/game/scenes/world/entities/npc/variants/enemy/enemy.ts @@ -10,17 +10,17 @@ import { NPC } from '~entity/npc'; import { Assets } from '~lib/assets'; import { Environment } from '~lib/environment'; import { progressionLinear, progressionQuadratic } from '~lib/progression'; -import { Effect, Particles } from '~scene/world/effects'; +import { Effect } from '~scene/world/effects'; import { GameFlag, GameSettings } from '~type/game'; import { InterfaceFont } from '~type/interface'; import { IWorld } from '~type/world'; -import { EffectTexture, ParticlesTexture } from '~type/world/effects'; +import { EffectTexture } from '~type/world/effects'; import { EntityType } from '~type/world/entities'; import { IEnemyTarget, EnemyData, EnemyTexture, IEnemy, EnemyAudio, } from '~type/world/entities/npc/enemy'; import { PlayerEvents, PlayerSuperskill } from '~type/world/entities/player'; -import { PositionAtWorld, TileType } from '~type/world/level'; +import { TileType } from '~type/world/level'; Assets.RegisterAudio(EnemyAudio); Assets.RegisterSprites(EnemyTexture, (texture) => ( @@ -250,37 +250,6 @@ export class Enemy extends NPC implements IEnemy { }); } - private addFireEffect(duration: number) { - if (!this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - return; - } - - const lifespan = this.displayWidth * 25; - - new Particles(this, { - key: 'fire', - texture: ParticlesTexture.BIT_SOFT, - dynamic: true, - replay: true, - params: { - followOffset: this.getBodyOffset(), - duration, - color: [0xfacc22, 0xf89800, 0xf83600, 0x9f0404], - colorEase: 'quad.out', - lifespan: { min: lifespan / 2, max: lifespan }, - alpha: { start: 1.0, end: 0.0 }, - angle: { min: -100, max: -80 }, - scale: { - start: this.displayWidth / 20, - end: 1.0, - ease: 'sine.out', - }, - speed: 40, - advance: 10, - }, - }); - } - private addBloodEffect() { if ( !this.currentBiome?.solid @@ -320,31 +289,7 @@ export class Enemy extends NPC implements IEnemy { }); }, 0); - if (this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - // Native body.center isn't working at current state - const size = ENEMY_SIZE_PARAMS[ENEMY_TEXTURE_SIZE[this.texture.key as EnemyTexture]]; - const position: PositionAtWorld = { - x: this.x, - y: this.y - size.height / 2, - }; - const duration = Math.min(700, this.displayHeight * 17); - const scale = this.displayWidth / 16; - - new Particles(this, { - key: 'spawn', - texture: ParticlesTexture.BIT_SOFT, - position, - params: { - duration, - lifespan: { min: duration / 2, max: duration }, - scale: { start: scale, end: scale / 2 }, - alpha: { start: 1.0, end: 0.0 }, - speed: 40, - quantity: 1, - tint: 0x000000, - }, - }); - } + this.scene.particles.createSpawnEffect(this); } private handlePlayerSuperskill() { @@ -370,7 +315,7 @@ export class Enemy extends NPC implements IEnemy { retardationLevel: DIFFICULTY.ENEMY_HEALTH_GROWTH_RETARDATION_LEVEL, }) * DIFFICULTY.SUPERSKILL_FIRE_FORCE; - this.addFireEffect(duration); + this.scene.particles.createLongFireEffect(this, { duration }); this.addOngoingDamage(damage, duration); break; } diff --git a/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts b/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts index 657b1515..8994d709 100644 --- a/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts +++ b/src/game/scenes/world/entities/npc/variants/enemy/variants/telepath.ts @@ -3,10 +3,7 @@ import { } from '~const/world/entities/enemy'; import { LEVEL_MAP_PERSPECTIVE } from '~const/world/level'; import { getIsometricDistance } from '~lib/dimension'; -import { Particles } from '~scene/world/effects'; -import { GameSettings } from '~type/game'; import { IWorld } from '~type/world'; -import { ParticlesTexture } from '~type/world/effects'; import { EntityType } from '~type/world/entities'; import { EnemyVariantData, EnemyTexture, IEnemy } from '~type/world/entities/npc/enemy'; @@ -89,32 +86,9 @@ export class EnemyTelepath extends Enemy { enemy.live.addHealth(healthAmount); - if (this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - new Particles(enemy, { - key: 'regeneration', - texture: ParticlesTexture.PLUS, - dynamic: true, - params: { - followOffset: { - x: 0, - y: -enemy.displayHeight, - }, - duration: ENEMY_REGENERATION_EFFECT_DURATION, - lifespan: ENEMY_REGENERATION_EFFECT_DURATION, - alpha: { start: 1.0, end: 0.0 }, - angle: { - min: -110, - max: -70, - }, - scale: { - start: 1.0, - end: 0.5, - }, - speed: 20, - maxAliveParticles: 1, - }, - }); - } + this.scene.particles.createHealEffect(enemy, { + duration: ENEMY_REGENERATION_EFFECT_DURATION, + }); }); } } diff --git a/src/game/scenes/world/entities/player.ts b/src/game/scenes/world/entities/player.ts index c4b0c38c..34cd745c 100644 --- a/src/game/scenes/world/entities/player.ts +++ b/src/game/scenes/world/entities/player.ts @@ -13,16 +13,14 @@ import { Crystal } from '~entity/crystal'; import { Sprite } from '~entity/sprite'; import { Assets } from '~lib/assets'; import { getClosestByIsometricDistance, isPositionsEqual } from '~lib/dimension'; -import { Environment } from '~lib/environment'; import { progressionLinear, progressionQuadratic } from '~lib/progression'; import { Tutorial } from '~lib/tutorial'; import { eachEntries } from '~lib/utils'; -import { Particles } from '~scene/world/effects'; import { Level } from '~scene/world/level'; -import { GameEvents, GameFlag, GameSettings } from '~type/game'; +import { GameEvents, GameSettings } from '~type/game'; import { TutorialStep } from '~type/tutorial'; import { IWorld, WorldEvents, WorldMode } from '~type/world'; -import { IParticles, ParticlesTexture } from '~type/world/effects'; +import { IParticles } from '~type/world/effects'; import { EntityType } from '~type/world/entities'; import { BuildingVariant } from '~type/world/entities/building'; import { ICrystal } from '~type/world/entities/crystal'; @@ -483,25 +481,7 @@ export class Player extends Sprite implements IPlayer { this.scene.game.sound.play(audio); } - if ( - this.scene.game.isSettingEnabled(GameSettings.EFFECTS) - && Environment.GetFlag(GameFlag.BLOOD) - ) { - new Particles(this, { - key: 'blood', - texture: ParticlesTexture.BIT_SOFT, - dynamic: true, - params: { - duration: 200, - followOffset: this.getBodyOffset(), - lifespan: { min: 100, max: 250 }, - scale: { start: 1.0, end: 0.25 }, - speed: 60, - maxAliveParticles: 6, - tint: 0xdd1e1e, - }, - }); - } + this.scene.particles.createBloodEffect(this); super.onDamage(amount); } @@ -695,30 +675,11 @@ export class Player extends Sprite implements IPlayer { } private addDustEffect() { - if ( - this.dustEffect - || !this.scene.game.isSettingEnabled(GameSettings.EFFECTS) - ) { + if (this.dustEffect) { return; } - this.dustEffect = new Particles(this, { - key: 'dust', - texture: ParticlesTexture.BIT, - dynamic: true, - params: { - followOffset: { - x: 0, - y: -this.gamut * this.scaleY * 0.5, - }, - lifespan: { min: 150, max: 300 }, - scale: 0.6, - speed: 10, - frequency: 150, - alpha: { start: 1.0, end: 0.0 }, - emitting: false, - }, - }); + this.dustEffect = this.scene.particles.createDustEffect(this); } private removeDustEffect() { diff --git a/src/game/scenes/world/entities/shot/ball/ball.ts b/src/game/scenes/world/entities/shot/ball/ball.ts index 1047bdd7..d35cc73a 100644 --- a/src/game/scenes/world/entities/shot/ball/ball.ts +++ b/src/game/scenes/world/entities/shot/ball/ball.ts @@ -3,10 +3,8 @@ import Phaser from 'phaser'; import { Analytics } from '~lib/analytics'; import { Assets } from '~lib/assets'; import { getIsometricDistance } from '~lib/dimension'; -import { Particles } from '~scene/world/effects'; -import { GameSettings } from '~type/game'; import { IWorld } from '~type/world'; -import { ParticlesTexture } from '~type/world/effects'; +import { IParticles } from '~type/world/effects'; import { EntityType } from '~type/world/entities'; import { IEnemy } from '~type/world/entities/npc/enemy'; import { @@ -30,7 +28,7 @@ export class ShotBall extends Phaser.Physics.Arcade.Image implements IShotBall { private audio: ShotBallAudio; - private effect: Nullable = null; + private effect: Nullable = null; private startPosition: Nullable = null; @@ -118,19 +116,10 @@ export class ShotBall extends Phaser.Physics.Arcade.Image implements IShotBall { this.setActive(true); this.setVisible(true); - if (this.glow && this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - this.effect = new Particles(this, { - key: 'glow', - texture: ParticlesTexture.GLOW, - dynamic: true, - params: { - scale: 0.2 * this.scale, - alpha: { start: 1.0, end: 0.0 }, - lifespan: 20000 / this.params.speed, - frequency: 10000 / this.params.speed, - tint: this.color, - blendMode: 'ADD', - }, + if (this.glow) { + this.effect = this.scene.particles.createGlowEffect(this, { + speed: this.params.speed, + color: this.color, }); } diff --git a/src/game/scenes/world/entities/shot/ball/variants/fire.ts b/src/game/scenes/world/entities/shot/ball/variants/fire.ts index 0310da25..4bff6e2e 100644 --- a/src/game/scenes/world/entities/shot/ball/variants/fire.ts +++ b/src/game/scenes/world/entities/shot/ball/variants/fire.ts @@ -1,9 +1,6 @@ import { SHOT_BALL_DAMAGE_SPREAD_FACTOR, SHOT_BALL_DAMAGE_SPREAD_MAX_DISTANCE } from '~const/world/entities/shot'; import { getIsometricDistance } from '~lib/dimension'; -import { Particles } from '~scene/world/effects'; -import { GameSettings } from '~type/game'; import { IWorld } from '~type/world'; -import { ParticlesTexture } from '~type/world/effects'; import { EntityType } from '~type/world/entities'; import { IEnemy } from '~type/world/entities/npc/enemy'; import { ShotBallAudio, ShotData, ShotParams } from '~type/world/entities/shot'; @@ -23,26 +20,7 @@ export class ShotBallFire extends ShotBall { public hit(target: IEnemy) { super.hit(target); - if (this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - const lifespan = target.displayWidth * 5; - const scale = Math.min(2.0, target.displayWidth / 22); - - new Particles(target, { - key: 'fire-mini', - texture: ParticlesTexture.BIT_SOFT, - dynamic: true, - params: { - duration: lifespan, - followOffset: target.getBodyOffset(), - color: [0xfacc22, 0xf89800, 0xf83600, 0x9f0404], - colorEase: 'quad.out', - lifespan: { min: lifespan / 2, max: lifespan }, - scale: { start: scale, end: scale * 0.2 }, - alpha: { start: 1.0, end: 0.0 }, - speed: 80, - }, - }); - } + this.scene.particles.createFireEffect(target); if (this.params.damage) { this.spreadDamage(target, this.params.damage * SHOT_BALL_DAMAGE_SPREAD_FACTOR); diff --git a/src/game/scenes/world/entities/shot/ball/variants/simple.ts b/src/game/scenes/world/entities/shot/ball/variants/simple.ts index e19a5846..cd795c5d 100644 --- a/src/game/scenes/world/entities/shot/ball/variants/simple.ts +++ b/src/game/scenes/world/entities/shot/ball/variants/simple.ts @@ -1,8 +1,4 @@ -import { Environment } from '~lib/environment'; -import { Particles } from '~scene/world/effects'; -import { GameSettings, GameFlag } from '~type/game'; import { IWorld } from '~type/world'; -import { ParticlesTexture } from '~type/world/effects'; import { IEnemy } from '~type/world/entities/npc/enemy'; import { ShotBallAudio, ShotData, ShotParams } from '~type/world/entities/shot'; @@ -20,27 +16,7 @@ export class ShotBallSimple extends ShotBall { public hit(target: IEnemy) { super.hit(target); - if ( - this.scene.game.isSettingEnabled(GameSettings.EFFECTS) - && Environment.GetFlag(GameFlag.BLOOD) - ) { - const scale = Math.min(2.0, target.displayWidth / 22); - - new Particles(target, { - key: 'blood', - texture: ParticlesTexture.BIT_SOFT, - dynamic: true, - params: { - duration: 200, - followOffset: target.getBodyOffset(), - lifespan: { min: 100, max: 250 }, - scale: { start: scale, end: scale * 0.25 }, - speed: 60, - maxAliveParticles: 6, - tint: 0xdd1e1e, - }, - }); - } + this.scene.particles.createBloodEffect(target); if (this.params.damage) { target.live.damage(this.params.damage); diff --git a/src/game/scenes/world/entities/shot/lazer.ts b/src/game/scenes/world/entities/shot/lazer.ts index f26be6df..d74995b9 100644 --- a/src/game/scenes/world/entities/shot/lazer.ts +++ b/src/game/scenes/world/entities/shot/lazer.ts @@ -104,25 +104,7 @@ export class ShotLazer extends Phaser.GameObjects.Line implements IShotLazer { return; } - if (this.scene.game.isSettingEnabled(GameSettings.EFFECTS)) { - const lifespan = this.target.displayWidth * 5; - const scale = Math.min(2.25, this.target.displayWidth / 18); - - new Particles(this.target, { - key: 'lazer', - texture: ParticlesTexture.BIT_SOFT, - dynamic: true, - params: { - duration: lifespan, - followOffset: this.target.getBodyOffset(), - lifespan: { min: lifespan / 2, max: lifespan }, - scale: { start: scale, end: scale * 0.2 }, - alpha: { start: 1.0, end: 0.0 }, - speed: 80, - tint: 0xb136ff, - }, - }); - } + this.scene.particles.createLazerEffect(this.target); const momentDamage = this.params.damage / SHOT_LAZER_REPEAT; diff --git a/src/game/scenes/world/world.ts b/src/game/scenes/world/world.ts index a93f4c61..15f37838 100644 --- a/src/game/scenes/world/world.ts +++ b/src/game/scenes/world/world.ts @@ -14,6 +14,7 @@ import { progressionLinear } from '~lib/progression'; import { hashString } from '~lib/utils'; import { Builder } from '~scene/world/builder'; import { Camera } from '~scene/world/camera'; +import { ParticlesManager } from '~scene/world/effects/particles-manager'; import { WorldUI } from '~scene/world/interface'; import { Level } from '~scene/world/level'; import { Spawner } from '~scene/world/spawner'; @@ -25,6 +26,7 @@ import { } from '~type/world'; import { IBuilder } from '~type/world/builder'; import { ICamera } from '~type/world/camera'; +import { IParticlesManager } from '~type/world/effects/particles-manager'; import { EntityType } from '~type/world/entities'; import { BuildingVariant, IBuilding } from '~type/world/entities/building'; import { ICrystal } from '~type/world/entities/crystal'; @@ -78,6 +80,12 @@ export class World extends Scene implements IWorld { private set spawner(v) { this._spawner = v; } + private _particles: IParticlesManager; + + public get particles() { return this._particles; } + + private set particles(v) { this._particles = v; } + private _camera: ICamera; public get camera() { return this._camera; } @@ -109,6 +117,7 @@ export class World extends Scene implements IWorld { this.input.setPollAlways(); this.level = new Level(this, data); + this.particles = new ParticlesManager(this); this.camera = new Camera(this); this.spawner = new Spawner(this); diff --git a/src/types/world/effects/particles-manager.ts b/src/types/world/effects/particles-manager.ts new file mode 100644 index 00000000..afe6ffe5 --- /dev/null +++ b/src/types/world/effects/particles-manager.ts @@ -0,0 +1,19 @@ +import { IParticles, IParticlesParent } from '~type/world/effects/particles'; +import { IBuilding } from '~type/world/entities/building'; +import { INPC } from '~type/world/entities/npc'; +import { IEnemy } from '~type/world/entities/npc/enemy'; +import { IPlayer } from '~type/world/entities/player'; +import { ISprite } from '~type/world/entities/sprite'; + +export interface IParticlesManager { + createBloodEffect(parent: ISprite): Nullable + createFireEffect(parent: IEnemy): Nullable + createLongFireEffect(parent: IEnemy, params: { duration: number }): Nullable + createLazerEffect(parent: IEnemy): Nullable + createGlowEffect(parent: IParticlesParent, params: { speed: number; color: number }): Nullable + createFrozeEffect(parent: INPC): Nullable + createDustEffect(parent: IPlayer): Nullable + createGenerationEffect(parent: IBuilding): Nullable + createSpawnEffect(parent: IEnemy): Nullable + createHealEffect(parent: ISprite, params: { duration: number }): Nullable +} diff --git a/src/types/world/effects/particles.ts b/src/types/world/effects/particles.ts index 3cc64589..8ff45a54 100644 --- a/src/types/world/effects/particles.ts +++ b/src/types/world/effects/particles.ts @@ -37,10 +37,9 @@ export enum ParticlesTexture { } export type ParticlesData = { - key?: string + key: string position?: PositionAtWorld texture: ParticlesTexture dynamic?: boolean - replay?: boolean params: Phaser.Types.GameObjects.Particles.ParticleEmitterConfig }; diff --git a/src/types/world/world.ts b/src/types/world/world.ts index d4e8d8c0..c28ff317 100644 --- a/src/types/world/world.ts +++ b/src/types/world/world.ts @@ -5,6 +5,7 @@ import { IScene } from '~type/scene'; import { StorageSavePayload } from '~type/storage'; import { IBuilder } from '~type/world/builder'; import { ICamera } from '~type/world/camera'; +import { IParticlesManager } from '~type/world/effects/particles-manager'; import { EntityType } from '~type/world/entities'; import { BuildingSavePayload } from '~type/world/entities/building'; import { CrystalSavePayload } from '~type/world/entities/crystal'; @@ -36,6 +37,11 @@ export interface IWorld extends IScene { */ readonly spawner: ISpawner + /** + * Particles manager. + */ + readonly particles: IParticlesManager + /** * Level. */