diff --git a/apps/client/src/scenes/game/application/GameLoop.ts b/apps/client/src/scenes/game/application/GameLoop.ts index 1ac43ba..74d807f 100644 --- a/apps/client/src/scenes/game/application/GameLoop.ts +++ b/apps/client/src/scenes/game/application/GameLoop.ts @@ -12,6 +12,7 @@ import { SimulationStep } from "./loopSteps/SimulationStep"; import { CameraStep } from "./loopSteps/CameraStep"; import { BombStep } from "./loopSteps/BombStep"; +import type { LoopFrameContext, LoopStep } from "./loopSteps/LoopStep"; import { resolveFrameDelta } from "./loopSteps/frameDelta"; import type { MoveSender } from "./network/PlayerMoveSender"; @@ -35,6 +36,7 @@ private simulationStep: SimulationStep; private bombStep: BombStep; private cameraStep: CameraStep; + private steps: LoopStep[]; constructor({ app, worldContainer, players, myId, getJoystickInput, bombManager, moveSender }: GameLoopOptions) { this.app = app; @@ -47,6 +49,12 @@ }); this.bombStep = new BombStep({ bombManager }); this.cameraStep = new CameraStep(); + this.steps = [ + this.inputStep, + this.simulationStep, + this.bombStep, + this.cameraStep, + ]; } public tick = (ticker: Ticker) => { @@ -57,21 +65,17 @@ ticker, config.GAME_CONFIG.FRAME_DELTA_MAX_MS, ); - const { isMoving } = this.inputStep.run({ me, deltaSeconds }); - - this.simulationStep.run({ - me, - players: this.players, - deltaSeconds, - isMoving, - }); - - this.bombStep.run(); - - this.cameraStep.run({ + const frameContext: LoopFrameContext = { app: this.app, worldContainer: this.worldContainer, + players: this.players, me, + deltaSeconds, + isMoving: false, + }; + + this.steps.forEach((step) => { + step.run(frameContext); }); }; } diff --git a/apps/client/src/scenes/game/application/loopSteps/BombStep.ts b/apps/client/src/scenes/game/application/loopSteps/BombStep.ts index f7d9d71..3166a0c 100644 --- a/apps/client/src/scenes/game/application/loopSteps/BombStep.ts +++ b/apps/client/src/scenes/game/application/loopSteps/BombStep.ts @@ -4,6 +4,7 @@ * 爆弾エンティティの時間更新と状態遷移を実行する */ import { BombManager } from "@client/scenes/game/entities/bomb/BombManager"; +import type { LoopFrameContext, LoopStep } from "./LoopStep"; /** BombStep の初期化入力 */ type BombStepOptions = { @@ -11,7 +12,7 @@ }; /** 爆弾更新処理を担うステップ */ -export class BombStep { +export class BombStep implements LoopStep { private bombManager: BombManager; constructor({ bombManager }: BombStepOptions) { @@ -19,7 +20,7 @@ } /** 爆弾更新を実行する,時間管理は GameTimer 由来の経過時刻を利用する */ - public run(): void { + public run(_context: LoopFrameContext): void { this.bombManager.tick(); } } diff --git a/apps/client/src/scenes/game/application/loopSteps/CameraStep.ts b/apps/client/src/scenes/game/application/loopSteps/CameraStep.ts index e229842..a72d16f 100644 --- a/apps/client/src/scenes/game/application/loopSteps/CameraStep.ts +++ b/apps/client/src/scenes/game/application/loopSteps/CameraStep.ts @@ -5,6 +5,7 @@ */ import { Application, Container } from "pixi.js"; import { LocalPlayerController } from "@client/scenes/game/entities/player/PlayerController"; +import type { LoopFrameContext, LoopStep } from "./LoopStep"; type CameraStepParams = { app: Application; @@ -13,8 +14,16 @@ }; /** カメラ追従更新を担うステップ */ -export class CameraStep { - public run({ app, worldContainer, me }: CameraStepParams) { +export class CameraStep implements LoopStep { + /** ローカルプレイヤー位置へカメラを追従させる */ + public run(context: LoopFrameContext): void { + const params: CameraStepParams = { + app: context.app, + worldContainer: context.worldContainer, + me: context.me, + }; + + const { app, worldContainer, me } = params; const meDisplay = me.getDisplayObject(); worldContainer.position.set(-(meDisplay.x - app.screen.width / 2), -(meDisplay.y - app.screen.height / 2)); } diff --git a/apps/client/src/scenes/game/application/loopSteps/InputStep.ts b/apps/client/src/scenes/game/application/loopSteps/InputStep.ts index 515e1c3..5b2ace4 100644 --- a/apps/client/src/scenes/game/application/loopSteps/InputStep.ts +++ b/apps/client/src/scenes/game/application/loopSteps/InputStep.ts @@ -4,6 +4,7 @@ * ジョイスティック入力をローカルプレイヤーへ適用する */ import { LocalPlayerController } from "@client/scenes/game/entities/player/PlayerController"; +import type { LoopFrameContext, LoopStep } from "./LoopStep"; type InputStepOptions = { getJoystickInput: () => { x: number; y: number }; @@ -14,19 +15,26 @@ deltaSeconds: number; }; -type InputStepResult = { - isMoving: boolean; -}; - /** 入力段の更新処理を担うステップ */ -export class InputStep { +export class InputStep implements LoopStep { private getJoystickInput: () => { x: number; y: number }; constructor({ getJoystickInput }: InputStepOptions) { this.getJoystickInput = getJoystickInput; } - public run({ me, deltaSeconds }: InputStepParams): InputStepResult { + /** 入力文脈を適用して移動状態を更新する */ + public run(context: LoopFrameContext): void { + const params: InputStepParams = { + me: context.me, + deltaSeconds: context.deltaSeconds, + }; + + const isMoving = this.applyInput(params); + context.isMoving = isMoving; + } + + private applyInput({ me, deltaSeconds }: InputStepParams): boolean { const { x: axisX, y: axisY } = this.getJoystickInput(); const isMoving = axisX !== 0 || axisY !== 0; @@ -34,6 +42,6 @@ me.applyLocalInput({ axisX, axisY, deltaTime: deltaSeconds }); } - return { isMoving }; + return isMoving; } } \ No newline at end of file diff --git a/apps/client/src/scenes/game/application/loopSteps/LoopStep.ts b/apps/client/src/scenes/game/application/loopSteps/LoopStep.ts new file mode 100644 index 0000000..f94e9c4 --- /dev/null +++ b/apps/client/src/scenes/game/application/loopSteps/LoopStep.ts @@ -0,0 +1,23 @@ +/** + * LoopStep + * ゲームループの共通ステップ契約を定義する + * 各ステップ間で受け渡すフレーム文脈を提供する + */ +import type { Application, Container } from "pixi.js"; +import type { LocalPlayerController } from "@client/scenes/game/entities/player/PlayerController"; +import type { GamePlayers } from "@client/scenes/game/application/game.types"; + +/** 1フレーム分の更新文脈を表す型 */ +export type LoopFrameContext = { + app: Application; + worldContainer: Container; + players: GamePlayers; + me: LocalPlayerController; + deltaSeconds: number; + isMoving: boolean; +}; + +/** ゲームループ内で実行されるステップ共通インターフェース */ +export type LoopStep = { + run: (context: LoopFrameContext) => void; +}; \ No newline at end of file diff --git a/apps/client/src/scenes/game/application/loopSteps/SimulationStep.ts b/apps/client/src/scenes/game/application/loopSteps/SimulationStep.ts index 92b13b3..2f5a34c 100644 --- a/apps/client/src/scenes/game/application/loopSteps/SimulationStep.ts +++ b/apps/client/src/scenes/game/application/loopSteps/SimulationStep.ts @@ -7,6 +7,7 @@ import { LocalPlayerController, RemotePlayerController } from "@client/scenes/game/entities/player/PlayerController"; import type { MoveSender } from "@client/scenes/game/application/network/PlayerMoveSender"; import type { GamePlayers } from "../game.types"; +import type { LoopFrameContext, LoopStep } from "./LoopStep"; /** SimulationStep の初期化入力 */ type SimulationStepOptions = { @@ -22,7 +23,7 @@ }; /** シミュレーション段の更新処理を担うステップ */ -export class SimulationStep { +export class SimulationStep implements LoopStep { private readonly moveSender: MoveSender; private readonly nowMsProvider: () => number; private lastPositionSentTime = 0; @@ -33,9 +34,17 @@ this.nowMsProvider = nowMsProvider; } - public run({ me, players, deltaSeconds, isMoving }: SimulationStepParams) { - this.runLocalSimulation({ me, isMoving }); - this.runRemoteSimulation({ players, deltaSeconds }); + /** ローカル更新とリモート補間更新を実行する */ + public run(context: LoopFrameContext): void { + const params: SimulationStepParams = { + me: context.me, + players: context.players, + deltaSeconds: context.deltaSeconds, + isMoving: context.isMoving, + }; + + this.runLocalSimulation({ me: params.me, isMoving: params.isMoving }); + this.runRemoteSimulation({ players: params.players, deltaSeconds: params.deltaSeconds }); } private runLocalSimulation({ me, isMoving }: Pick) {