diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts index 2251d3b..3fd2f58 100644 --- a/apps/client/src/scenes/game/GameManager.ts +++ b/apps/client/src/scenes/game/GameManager.ts @@ -14,6 +14,7 @@ import { GameLoop } from "./application/GameLoop"; import { BombHitContextProvider } from "./application/BombHitContextProvider"; import { BombHitOrchestrator } from "./application/BombHitOrchestrator"; +import type { BombHitEvaluationResult } from "./application/BombHitOrchestrator"; import type { GamePlayers } from "./application/game.types"; /** ゲームシーンの実行ライフサイクルを管理するマネージャー */ @@ -97,56 +98,10 @@ this.container.appendChild(this.app.canvas); - // 背景マップの配置 - const gameMap = new GameMapController(this.appearanceResolver); - this.gameMap = gameMap; - this.worldContainer.addChild(gameMap.getDisplayObject()); - this.app.stage.addChild(this.worldContainer); - - this.networkSync = new GameNetworkSync({ - worldContainer: this.worldContainer, - players: this.players, - myId: this.myId, - gameMap: this.gameMap, - appearanceResolver: this.appearanceResolver, - onGameStart: this.setGameStart.bind(this), - onGameEnd: this.lockInput.bind(this), - onBombPlacedFromOthers: (payload) => { - this.applyPlacedBombFromOthers(payload); - }, - onBombPlacedAckFromNetwork: (payload) => { - this.applyPlacedBombAck(payload); - }, - }); - this.networkSync.bind(); - - 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) => { - this.bombHitOrchestrator?.handleBombExploded(payload); - }, - }); - - this.gameLoop = new GameLoop({ - app: this.app, - worldContainer: this.worldContainer, - players: this.players, - myId: this.myId, - getJoystickInput: () => this.joystickInput, - bombManager: this.bombManager, - }); + this.initializeWorld(); + this.initializeNetworkSync(); + this.initializeBombSubsystem(); + this.initializeGameLoop(); // サーバーへゲーム準備完了を通知 socketManager.game.readyForGame(); @@ -171,6 +126,78 @@ 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 initializeNetworkSync(): void { + this.networkSync = new GameNetworkSync({ + worldContainer: this.worldContainer, + players: this.players, + myId: this.myId, + gameMap: this.gameMap, + appearanceResolver: this.appearanceResolver, + onGameStart: this.setGameStart.bind(this), + onGameEnd: this.lockInput.bind(this), + onBombPlacedFromOthers: (payload) => { + this.applyPlacedBombFromOthers(payload); + }, + onBombPlacedAckFromNetwork: (payload) => { + this.applyPlacedBombAck(payload); + }, + }); + 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); + }, + }); + } + + /** ゲームループを初期化する */ + 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, + }); + } + + /** 爆弾当たり判定の評価結果を受け取り,後続処理へ接続する */ + private handleBombHitEvaluation(_result: BombHitEvaluationResult | undefined): void { + // 次フェーズでサーバー通知や被弾演出の接続に利用する + } + /** * クリーンアップ処理(コンポーネントアンマウント時) */ diff --git a/apps/client/src/scenes/game/application/BombHitOrchestrator.ts b/apps/client/src/scenes/game/application/BombHitOrchestrator.ts index 0def7b5..3d6b337 100644 --- a/apps/client/src/scenes/game/application/BombHitOrchestrator.ts +++ b/apps/client/src/scenes/game/application/BombHitOrchestrator.ts @@ -1,7 +1,7 @@ /** * BombHitOrchestrator * 爆弾爆発イベントとローカルプレイヤー情報を橋渡しして当たり判定を実行する - * 同一爆弾の重複判定を抑止して当たり判定を実行する + * 判定結果を呼び出し元へ返して後続処理へ接続しやすくする */ import { checkBombHit } from "@client/scenes/game/entities/bomb/BombHitDetector"; import type { BombExplodedPayload } from "@client/scenes/game/entities/bomb/BombManager"; @@ -12,6 +12,9 @@ contextProvider: BombHitContextProvider; }; +/** 爆弾爆発イベントの判定結果を表す型 */ +export type BombHitEvaluationResult = "duplicate" | "missing-local-player" | "no-hit" | "hit"; + /** 爆弾当たり判定の実行順序を制御する */ export class BombHitOrchestrator { private contextProvider: BombHitContextProvider; @@ -21,16 +24,16 @@ this.contextProvider = contextProvider; } - /** 爆弾爆発イベントを受けて当たり判定を実行する */ - public handleBombExploded(payload: BombExplodedPayload): void { + /** 爆弾爆発イベントを受けて当たり判定を実行し結果を返す */ + public handleBombExploded(payload: BombExplodedPayload): BombHitEvaluationResult { if (this.handledBombIds.has(payload.bombId)) { - return; + return "duplicate"; } this.handledBombIds.add(payload.bombId); const localPlayer = this.contextProvider.getLocalPlayerCircle(); if (!localPlayer) { - return; + return "missing-local-player"; } const result = checkBombHit({ @@ -44,9 +47,11 @@ }); if (!result.isHit) { - return; + return "no-hit"; } + return "hit"; + } /** 判定済み状態を初期化する */ diff --git a/apps/client/src/scenes/game/application/GameLoop.ts b/apps/client/src/scenes/game/application/GameLoop.ts index 723411a..b04bdb3 100644 --- a/apps/client/src/scenes/game/application/GameLoop.ts +++ b/apps/client/src/scenes/game/application/GameLoop.ts @@ -57,7 +57,7 @@ isMoving, }); - this.bombStep.run(); + this.bombStep.run({ deltaSeconds }); this.cameraStep.run({ app: this.app, diff --git a/apps/client/src/scenes/game/application/loopSteps/BombStep.ts b/apps/client/src/scenes/game/application/loopSteps/BombStep.ts index 326c39d..38c9d0f 100644 --- a/apps/client/src/scenes/game/application/loopSteps/BombStep.ts +++ b/apps/client/src/scenes/game/application/loopSteps/BombStep.ts @@ -10,6 +10,11 @@ bombManager: BombManager; }; +/** 爆弾更新段の実行入力 */ +type BombStepParams = { + deltaSeconds: number; +}; + /** 爆弾更新処理を担うステップ */ export class BombStep { private bombManager: BombManager; @@ -19,7 +24,7 @@ } /** 爆弾更新を実行する */ - public run(): void { + public run(_params: BombStepParams): void { this.bombManager.tick(); } } diff --git a/apps/client/src/scenes/game/entities/bomb/BombView.ts b/apps/client/src/scenes/game/entities/bomb/BombView.ts index 7c8dba1..ee04526 100644 --- a/apps/client/src/scenes/game/entities/bomb/BombView.ts +++ b/apps/client/src/scenes/game/entities/bomb/BombView.ts @@ -11,6 +11,8 @@ /** 爆弾の描画表現を管理するビュー */ export class BombView { public readonly displayObject: Container; + private static bombTexturePromise: Promise | null = null; + private static bombTexture: Texture | null = null; private bombSprite: Sprite; private bombFallbackGraphic: Graphics; @@ -42,7 +44,7 @@ const imageUrl = `${import.meta.env.BASE_URL}Bomb.svg`; try { - const texture = await Assets.load(imageUrl); + const texture = await BombView.loadBombTexture(imageUrl); if (this.isDestroyed || this.bombSprite.destroyed) { return; } @@ -59,6 +61,21 @@ } } + /** 爆弾テクスチャを共有キャッシュ経由で取得する */ + private static async loadBombTexture(imageUrl: string): Promise { + if (BombView.bombTexture) { + return BombView.bombTexture; + } + + if (!BombView.bombTexturePromise) { + BombView.bombTexturePromise = Assets.load(imageUrl); + } + + const loadedTexture = await BombView.bombTexturePromise; + BombView.bombTexture = loadedTexture; + return loadedTexture; + } + public syncPosition(gridX: number, gridY: number): void { const { GRID_CELL_SIZE } = config.GAME_CONFIG;