diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts index ea1fa27..5cd39fb 100644 --- a/apps/client/src/scenes/game/GameManager.ts +++ b/apps/client/src/scenes/game/GameManager.ts @@ -8,21 +8,15 @@ BombPlacedAckPayload, BombPlacedPayload, } from "@repo/shared"; -import { config } from "@client/config"; import { socketManager } from "@client/network/SocketManager"; import { AppearanceResolver } from "./application/AppearanceResolver"; import { BombManager } from "./entities/bomb/BombManager"; import { GameNetworkSync } from "./application/GameNetworkSync"; import { GameLoop } from "./application/GameLoop"; -import { BombHitContextProvider } from "./application/BombHitContextProvider"; -import { BombHitOrchestrator } from "./application/BombHitOrchestrator"; -import { PlayerDeathPolicy } from "./application/PlayerDeathPolicy"; -import { PlayerHitEffectOrchestrator } from "./application/PlayerHitEffectOrchestrator"; import { SceneLifecycleState } from "./application/lifecycle/SceneLifecycleState"; import { GameSessionFacade } from "./application/lifecycle/GameSessionFacade"; -import { HitReportPolicy } from "./application/combat/HitReportPolicy"; +import { CombatLifecycleFacade } from "./application/combat/CombatLifecycleFacade"; import { GameSceneOrchestrator } from "./application/orchestrators/GameSceneOrchestrator"; -import type { BombHitEvaluationResult } from "./application/BombHitOrchestrator"; import type { GamePlayers } from "./application/game.types"; /** ゲームシーンの実行ライフサイクルを管理するマネージャー */ @@ -35,13 +29,10 @@ private sessionFacade = new GameSessionFacade(); private appearanceResolver = new AppearanceResolver(); private bombManager: BombManager | null = null; - private bombHitOrchestrator: BombHitOrchestrator | null = null; private networkSync: GameNetworkSync | null = null; private gameLoop: GameLoop | null = null; - private playerDeathPolicy!: PlayerDeathPolicy; - private playerHitEffectOrchestrator!: PlayerHitEffectOrchestrator; + private combatFacade: CombatLifecycleFacade; private lifecycleState = new SceneLifecycleState(); - private hitReportPolicy = new HitReportPolicy(); // サーバーからゲーム開始通知(と開始時刻)を受け取った時に呼ぶ public setGameStart(startTime: number) { @@ -93,20 +84,13 @@ this.app = new Application(); this.worldContainer = new Container(); this.worldContainer.sortableChildren = true; - this.initializeHitSubsystem(); - } - - /** 被弾時の入力制御と演出発火のサブシステムを初期化する */ - private initializeHitSubsystem(): void { - this.playerDeathPolicy = new PlayerDeathPolicy({ - myId: this.myId, - hitStunMs: config.GAME_CONFIG.PLAYER_HIT_STUN_MS, - acquireInputLock: this.lockInput.bind(this), - }); - this.playerHitEffectOrchestrator = new PlayerHitEffectOrchestrator({ + this.combatFacade = new CombatLifecycleFacade({ players: this.players, - blinkDurationMs: config.GAME_CONFIG.PLAYER_HIT_EFFECT.BLINK_DURATION_MS, - dedupWindowMs: config.GAME_CONFIG.PLAYER_HIT_EFFECT.DEDUP_WINDOW_MS, + myId: this.myId, + acquireInputLock: this.lockInput.bind(this), + onSendBombHitReport: (bombId) => { + socketManager.game.sendBombHitReport({ bombId }); + }, }); } @@ -129,7 +113,7 @@ this.container.appendChild(this.app.canvas); - this.initializeSceneSubsystems(); + this.initializeSceneSubsystems(); // サーバーへゲーム準備完了を通知 socketManager.game.readyForGame(); @@ -153,21 +137,8 @@ this.gameLoop?.tick(ticker); }; - /** 爆弾管理と当たり判定橋渡しを初期化する */ - private initializeBombHitSubsystem(): void { - const bombHitContextProvider = new BombHitContextProvider({ - players: this.players, - myId: this.myId, - }); - this.bombHitOrchestrator = new BombHitOrchestrator({ - contextProvider: bombHitContextProvider, - }); - } - /** ゲームシーンのサブシステムを初期化して配線する */ private initializeSceneSubsystems(): void { - this.initializeBombHitSubsystem(); - const orchestrator = new GameSceneOrchestrator({ app: this.app, worldContainer: this.worldContainer, @@ -185,12 +156,10 @@ this.applyPlacedBombAck(payload); }, onPlayerDeadFromNetwork: (payload) => { - this.playerDeathPolicy.applyPlayerDeadEvent(payload); - this.playerHitEffectOrchestrator.handleNetworkPlayerDead(payload.playerId, this.myId); + this.combatFacade.handleNetworkPlayerDead(payload); }, onBombExploded: (payload) => { - const result = this.bombHitOrchestrator?.handleBombExploded(payload); - this.handleBombHitEvaluation(result, payload.bombId); + this.combatFacade.handleBombExploded(payload); }, }); @@ -200,21 +169,6 @@ this.gameLoop = initializedScene.gameLoop; } - /** 爆弾当たり判定の評価結果を受け取り,後続処理へ接続する */ - private handleBombHitEvaluation( - result: BombHitEvaluationResult | undefined, - bombId: string, - ): void { - if (!this.hitReportPolicy.shouldSendReport(result, bombId)) { - return; - } - - this.playerDeathPolicy.applyLocalHitStun(); - this.playerHitEffectOrchestrator.handleLocalBombHit(this.myId); - - socketManager.game.sendBombHitReport({ bombId }); - } - /** * クリーンアップ処理(コンポーネントアンマウント時) */ @@ -225,10 +179,7 @@ } this.bombManager?.destroy(); this.bombManager = null; - this.bombHitOrchestrator?.clear(); - this.bombHitOrchestrator = null; - this.playerDeathPolicy.dispose(); - this.hitReportPolicy.clear(); + this.combatFacade.dispose(); this.sessionFacade.reset(); this.players = {}; this.joystickInput = { x: 0, y: 0 }; diff --git a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts new file mode 100644 index 0000000..2fb08fc --- /dev/null +++ b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts @@ -0,0 +1,83 @@ +/** + * CombatLifecycleFacade + * 被弾判定から硬直,演出,報告送信までの戦闘ライフサイクルを管理する + * ゲームマネージャーから被弾関連の責務を分離する + */ +import { config } from "@client/config"; +import type { PlayerDeadPayload } from "@repo/shared"; +import type { BombExplodedPayload } from "@client/scenes/game/entities/bomb/BombManager"; +import { BombHitContextProvider } from "@client/scenes/game/application/BombHitContextProvider"; +import { BombHitOrchestrator } from "@client/scenes/game/application/BombHitOrchestrator"; +import { PlayerDeathPolicy } from "@client/scenes/game/application/PlayerDeathPolicy"; +import { PlayerHitEffectOrchestrator } from "@client/scenes/game/application/PlayerHitEffectOrchestrator"; +import type { GamePlayers } from "@client/scenes/game/application/game.types"; +import { HitReportPolicy } from "./HitReportPolicy"; + +/** CombatLifecycleFacade の初期化入力 */ +export type CombatLifecycleFacadeOptions = { + players: GamePlayers; + myId: string; + acquireInputLock: () => () => void; + onSendBombHitReport: (bombId: string) => void; +}; + +/** 被弾関連ライフサイクルの制御を担当する */ +export class CombatLifecycleFacade { + private readonly myId: string; + private readonly onSendBombHitReport: (bombId: string) => void; + private readonly bombHitOrchestrator: BombHitOrchestrator; + private readonly playerDeathPolicy: PlayerDeathPolicy; + private readonly playerHitEffectOrchestrator: PlayerHitEffectOrchestrator; + private readonly hitReportPolicy = new HitReportPolicy(); + + constructor({ + players, + myId, + acquireInputLock, + onSendBombHitReport, + }: CombatLifecycleFacadeOptions) { + this.myId = myId; + this.onSendBombHitReport = onSendBombHitReport; + this.bombHitOrchestrator = new BombHitOrchestrator({ + contextProvider: new BombHitContextProvider({ + players, + myId, + }), + }); + this.playerDeathPolicy = new PlayerDeathPolicy({ + myId, + hitStunMs: config.GAME_CONFIG.PLAYER_HIT_STUN_MS, + acquireInputLock, + }); + this.playerHitEffectOrchestrator = new PlayerHitEffectOrchestrator({ + players, + blinkDurationMs: config.GAME_CONFIG.PLAYER_HIT_EFFECT.BLINK_DURATION_MS, + dedupWindowMs: config.GAME_CONFIG.PLAYER_HIT_EFFECT.DEDUP_WINDOW_MS, + }); + } + + /** 爆弾爆発時の判定と後続処理を実行する */ + public handleBombExploded(payload: BombExplodedPayload): void { + const result = this.bombHitOrchestrator.handleBombExploded(payload); + if (!this.hitReportPolicy.shouldSendReport(result, payload.bombId)) { + return; + } + + this.playerDeathPolicy.applyLocalHitStun(); + this.playerHitEffectOrchestrator.handleLocalBombHit(this.myId); + this.onSendBombHitReport(payload.bombId); + } + + /** ネットワーク被弾通知を適用する */ + public handleNetworkPlayerDead(payload: PlayerDeadPayload): void { + this.playerDeathPolicy.applyPlayerDeadEvent(payload); + this.playerHitEffectOrchestrator.handleNetworkPlayerDead(payload.playerId, this.myId); + } + + /** 管理中リソースを破棄する */ + public dispose(): void { + this.bombHitOrchestrator.clear(); + this.playerDeathPolicy.dispose(); + this.hitReportPolicy.clear(); + } +} \ No newline at end of file