diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts index fe3d470..856de44 100644 --- a/apps/client/src/scenes/game/GameManager.ts +++ b/apps/client/src/scenes/game/GameManager.ts @@ -11,7 +11,6 @@ import { config } from "@client/config"; import { socketManager } from "@client/network/SocketManager"; import { AppearanceResolver } from "./application/AppearanceResolver"; -import { GameMapController } from "./entities/map/GameMapController"; import { BombManager } from "./entities/bomb/BombManager"; import { GameTimer } from "./application/GameTimer"; import { GameNetworkSync } from "./application/GameNetworkSync"; @@ -22,6 +21,7 @@ import { PlayerHitEffectOrchestrator } from "./application/PlayerHitEffectOrchestrator"; import { InputGate } from "./application/lifecycle/InputGate"; import { HitReportPolicy } from "./application/combat/HitReportPolicy"; +import { GameSceneOrchestrator } from "./application/orchestrators/GameSceneOrchestrator"; import type { BombHitEvaluationResult } from "./application/BombHitOrchestrator"; import type { GamePlayers } from "./application/game.types"; @@ -32,7 +32,6 @@ private players: GamePlayers = {}; private myId: string; private container: HTMLDivElement; - private gameMap!: GameMapController; private timer = new GameTimer(); private appearanceResolver = new AppearanceResolver(); private bombManager: BombManager | null = null; @@ -135,10 +134,7 @@ this.container.appendChild(this.app.canvas); - this.initializeWorld(); - this.initializeNetworkSync(); - this.initializeBombSubsystem(); - this.initializeGameLoop(); + this.initializeSceneSubsystems(); // サーバーへゲーム準備完了を通知 socketManager.game.readyForGame(); @@ -162,22 +158,29 @@ this.gameLoop?.tick(ticker); }; - /** 背景マップとワールド描画コンテナを初期化する */ - private initializeWorld(): void { - const gameMap = new GameMapController(this.appearanceResolver); - this.gameMap = gameMap; - this.worldContainer.addChild(gameMap.getDisplayObject()); - this.app.stage.addChild(this.worldContainer); + /** 爆弾管理と当たり判定橋渡しを初期化する */ + private initializeBombHitSubsystem(): void { + const bombHitContextProvider = new BombHitContextProvider({ + players: this.players, + myId: this.myId, + }); + this.bombHitOrchestrator = new BombHitOrchestrator({ + contextProvider: bombHitContextProvider, + }); } - /** ネットワーク購読を初期化してバインドする */ - private initializeNetworkSync(): void { - this.networkSync = new GameNetworkSync({ + /** ゲームシーンのサブシステムを初期化して配線する */ + private initializeSceneSubsystems(): void { + this.initializeBombHitSubsystem(); + + const orchestrator = new GameSceneOrchestrator({ + app: this.app, worldContainer: this.worldContainer, players: this.players, myId: this.myId, - gameMap: this.gameMap, appearanceResolver: this.appearanceResolver, + getElapsedMs: () => this.timer.getElapsedMs(), + getJoystickInput: () => this.joystickInput, onGameStart: this.setGameStart.bind(this), onGameEnd: this.lockInput.bind(this), onBombPlacedFromOthers: (payload) => { @@ -190,47 +193,16 @@ this.playerDeathPolicy.applyPlayerDeadEvent(payload); this.playerHitEffectOrchestrator.handleNetworkPlayerDead(payload.playerId, this.myId); }, - }); - this.networkSync.bind(); - } - - /** 爆弾管理と当たり判定橋渡しを初期化する */ - private initializeBombSubsystem(): void { - const bombHitContextProvider = new BombHitContextProvider({ - players: this.players, - myId: this.myId, - }); - this.bombHitOrchestrator = new BombHitOrchestrator({ - contextProvider: bombHitContextProvider, - }); - - this.bombManager = new BombManager({ - worldContainer: this.worldContainer, - players: this.players, - myId: this.myId, - getElapsedMs: () => this.timer.getElapsedMs(), - appearanceResolver: this.appearanceResolver, onBombExploded: (payload) => { const result = this.bombHitOrchestrator?.handleBombExploded(payload); this.handleBombHitEvaluation(result, payload.bombId); }, }); - } - /** ゲームループを初期化する */ - private initializeGameLoop(): void { - if (!this.bombManager) { - return; - } - - this.gameLoop = new GameLoop({ - app: this.app, - worldContainer: this.worldContainer, - players: this.players, - myId: this.myId, - getJoystickInput: () => this.joystickInput, - bombManager: this.bombManager, - }); + const initializedScene = orchestrator.initialize(); + this.networkSync = initializedScene.networkSync; + this.bombManager = initializedScene.bombManager; + this.gameLoop = initializedScene.gameLoop; } /** 爆弾当たり判定の評価結果を受け取り,後続処理へ接続する */ diff --git a/apps/client/src/scenes/game/application/orchestrators/GameSceneOrchestrator.ts b/apps/client/src/scenes/game/application/orchestrators/GameSceneOrchestrator.ts new file mode 100644 index 0000000..427b399 --- /dev/null +++ b/apps/client/src/scenes/game/application/orchestrators/GameSceneOrchestrator.ts @@ -0,0 +1,153 @@ +/** + * GameSceneOrchestrator + * ゲームシーン初期化時のサブシステム配線を担当する + * ワールド,ネットワーク,爆弾,ループの生成順序を統制する + */ +import { Application, Container } from "pixi.js"; +import type { + BombPlacedAckPayload, + BombPlacedPayload, + PlayerDeadPayload, +} from "@repo/shared"; +import { AppearanceResolver } from "@client/scenes/game/application/AppearanceResolver"; +import { GameMapController } from "@client/scenes/game/entities/map/GameMapController"; +import { GameNetworkSync } from "@client/scenes/game/application/GameNetworkSync"; +import { BombManager, type BombExplodedPayload } from "@client/scenes/game/entities/bomb/BombManager"; +import { GameLoop } from "@client/scenes/game/application/GameLoop"; +import type { GamePlayers } from "@client/scenes/game/application/game.types"; + +/** GameSceneOrchestrator の初期化入力 */ +export type GameSceneOrchestratorOptions = { + app: Application; + worldContainer: Container; + players: GamePlayers; + myId: string; + appearanceResolver: AppearanceResolver; + getElapsedMs: () => number; + getJoystickInput: () => { x: number; y: number }; + onGameStart: (startTime: number) => void; + onGameEnd: () => void; + onBombPlacedFromOthers: (payload: BombPlacedPayload) => void; + onBombPlacedAckFromNetwork: (payload: BombPlacedAckPayload) => void; + onPlayerDeadFromNetwork: (payload: PlayerDeadPayload) => void; + onBombExploded: (payload: BombExplodedPayload) => void; +}; + +/** 初期化済みサブシステム参照の戻り値型 */ +export type InitializedGameScene = { + gameMap: GameMapController; + networkSync: GameNetworkSync; + bombManager: BombManager; + gameLoop: GameLoop; +}; + +/** ゲームシーン初期化配線を担当する */ +export class GameSceneOrchestrator { + private readonly app: Application; + private readonly worldContainer: Container; + private readonly players: GamePlayers; + private readonly myId: string; + private readonly appearanceResolver: AppearanceResolver; + private readonly getElapsedMs: () => number; + private readonly getJoystickInput: () => { x: number; y: number }; + private readonly onGameStart: (startTime: number) => void; + private readonly onGameEnd: () => void; + private readonly onBombPlacedFromOthers: (payload: BombPlacedPayload) => void; + private readonly onBombPlacedAckFromNetwork: (payload: BombPlacedAckPayload) => void; + private readonly onPlayerDeadFromNetwork: (payload: PlayerDeadPayload) => void; + private readonly onBombExploded: (payload: BombExplodedPayload) => void; + + constructor({ + app, + worldContainer, + players, + myId, + appearanceResolver, + getElapsedMs, + getJoystickInput, + onGameStart, + onGameEnd, + onBombPlacedFromOthers, + onBombPlacedAckFromNetwork, + onPlayerDeadFromNetwork, + onBombExploded, + }: GameSceneOrchestratorOptions) { + this.app = app; + this.worldContainer = worldContainer; + this.players = players; + this.myId = myId; + this.appearanceResolver = appearanceResolver; + this.getElapsedMs = getElapsedMs; + this.getJoystickInput = getJoystickInput; + this.onGameStart = onGameStart; + this.onGameEnd = onGameEnd; + this.onBombPlacedFromOthers = onBombPlacedFromOthers; + this.onBombPlacedAckFromNetwork = onBombPlacedAckFromNetwork; + this.onPlayerDeadFromNetwork = onPlayerDeadFromNetwork; + this.onBombExploded = onBombExploded; + } + + /** シーン配線を順序どおり初期化し,参照を返す */ + public initialize(): InitializedGameScene { + const gameMap = this.initializeWorld(); + const networkSync = this.initializeNetworkSync(gameMap); + const bombManager = this.initializeBombSubsystem(); + const gameLoop = this.initializeGameLoop(bombManager); + return { + gameMap, + networkSync, + bombManager, + gameLoop, + }; + } + + /** 背景マップとワールド描画コンテナを初期化する */ + private initializeWorld(): GameMapController { + const gameMap = new GameMapController(this.appearanceResolver); + this.worldContainer.addChild(gameMap.getDisplayObject()); + this.app.stage.addChild(this.worldContainer); + return gameMap; + } + + /** ネットワーク購読を初期化してバインドする */ + private initializeNetworkSync(gameMap: GameMapController): GameNetworkSync { + const networkSync = new GameNetworkSync({ + worldContainer: this.worldContainer, + players: this.players, + myId: this.myId, + gameMap, + appearanceResolver: this.appearanceResolver, + onGameStart: this.onGameStart, + onGameEnd: this.onGameEnd, + onBombPlacedFromOthers: this.onBombPlacedFromOthers, + onBombPlacedAckFromNetwork: this.onBombPlacedAckFromNetwork, + onPlayerDeadFromNetwork: this.onPlayerDeadFromNetwork, + }); + networkSync.bind(); + return networkSync; + } + + /** 爆弾サブシステムを初期化する */ + private initializeBombSubsystem(): BombManager { + return new BombManager({ + worldContainer: this.worldContainer, + players: this.players, + myId: this.myId, + getElapsedMs: this.getElapsedMs, + appearanceResolver: this.appearanceResolver, + onBombExploded: this.onBombExploded, + }); + } + + /** ゲームループを初期化する */ + private initializeGameLoop(bombManager: BombManager): GameLoop { + return new GameLoop({ + app: this.app, + worldContainer: this.worldContainer, + players: this.players, + myId: this.myId, + getJoystickInput: this.getJoystickInput, + bombManager, + }); + } +} \ No newline at end of file