diff --git a/apps/client/src/scenes/game/application/GameLoop.ts b/apps/client/src/scenes/game/application/GameLoop.ts index 77e5fd1..f9145b5 100644 --- a/apps/client/src/scenes/game/application/GameLoop.ts +++ b/apps/client/src/scenes/game/application/GameLoop.ts @@ -1,8 +1,9 @@ import { Application, Container, Ticker } from "pixi.js"; -import { config } from "@repo/shared"; -import { socketManager } from "@client/network/SocketManager"; -import { LocalPlayerController, RemotePlayerController } from "../entities/player/PlayerController"; +import { LocalPlayerController } from "../entities/player/PlayerController"; import type { GamePlayers } from "./game.types"; +import { InputStep } from "./loopSteps/InputStep"; +import { SimulationStep } from "./loopSteps/SimulationStep"; +import { CameraStep } from "./loopSteps/CameraStep"; type GameLoopOptions = { app: Application; @@ -17,16 +18,18 @@ private worldContainer: Container; private players: GamePlayers; private myId: string; - private getJoystickInput: () => { x: number; y: number }; - private lastPositionSentTime = 0; - private wasMoving = false; + private inputStep: InputStep; + private simulationStep: SimulationStep; + private cameraStep: CameraStep; constructor({ app, worldContainer, players, myId, getJoystickInput }: GameLoopOptions) { this.app = app; this.worldContainer = worldContainer; this.players = players; this.myId = myId; - this.getJoystickInput = getJoystickInput; + this.inputStep = new InputStep({ getJoystickInput }); + this.simulationStep = new SimulationStep(); + this.cameraStep = new CameraStep(); } public tick = (ticker: Ticker) => { @@ -34,36 +37,19 @@ if (!me || !(me instanceof LocalPlayerController)) return; const deltaSeconds = ticker.deltaMS / 1000; + const { isMoving } = this.inputStep.run({ me, deltaSeconds }); - const { x: dx, y: dy } = this.getJoystickInput(); - const isMoving = dx !== 0 || dy !== 0; - - if (isMoving) { - me.applyLocalInput({ axisX: dx, axisY: dy, deltaTime: deltaSeconds }); - me.tick(); - - const now = performance.now(); - if (now - this.lastPositionSentTime >= config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS) { - const position = me.getPosition(); - socketManager.game.sendMove(position.x, position.y); - this.lastPositionSentTime = now; - } - } else if (this.wasMoving) { - me.tick(); - const position = me.getPosition(); - socketManager.game.sendMove(position.x, position.y); - } else { - me.tick(); - } - this.wasMoving = isMoving; - - Object.values(this.players).forEach((player) => { - if (player instanceof RemotePlayerController) { - player.tick(deltaSeconds); - } + this.simulationStep.run({ + me, + players: this.players, + deltaSeconds, + isMoving, }); - const meDisplay = me.getDisplayObject(); - this.worldContainer.position.set(-(meDisplay.x - this.app.screen.width / 2), -(meDisplay.y - this.app.screen.height / 2)); + this.cameraStep.run({ + app: this.app, + worldContainer: this.worldContainer, + me, + }); }; } diff --git a/apps/client/src/scenes/game/application/loopSteps/CameraStep.ts b/apps/client/src/scenes/game/application/loopSteps/CameraStep.ts new file mode 100644 index 0000000..4871923 --- /dev/null +++ b/apps/client/src/scenes/game/application/loopSteps/CameraStep.ts @@ -0,0 +1,15 @@ +import { Application, Container } from "pixi.js"; +import { LocalPlayerController } from "../../entities/player/PlayerController"; + +type CameraStepParams = { + app: Application; + worldContainer: Container; + me: LocalPlayerController; +}; + +export class CameraStep { + public run({ app, worldContainer, me }: CameraStepParams) { + const meDisplay = me.getDisplayObject(); + worldContainer.position.set(-(meDisplay.x - app.screen.width / 2), -(meDisplay.y - app.screen.height / 2)); + } +} \ No newline at end of file diff --git a/apps/client/src/scenes/game/application/loopSteps/InputStep.ts b/apps/client/src/scenes/game/application/loopSteps/InputStep.ts new file mode 100644 index 0000000..6ca5551 --- /dev/null +++ b/apps/client/src/scenes/game/application/loopSteps/InputStep.ts @@ -0,0 +1,33 @@ +import { LocalPlayerController } from "../../entities/player/PlayerController"; + +type InputStepOptions = { + getJoystickInput: () => { x: number; y: number }; +}; + +type InputStepParams = { + me: LocalPlayerController; + deltaSeconds: number; +}; + +type InputStepResult = { + isMoving: boolean; +}; + +export class InputStep { + private getJoystickInput: () => { x: number; y: number }; + + constructor({ getJoystickInput }: InputStepOptions) { + this.getJoystickInput = getJoystickInput; + } + + public run({ me, deltaSeconds }: InputStepParams): InputStepResult { + const { x: axisX, y: axisY } = this.getJoystickInput(); + const isMoving = axisX !== 0 || axisY !== 0; + + if (isMoving) { + me.applyLocalInput({ axisX, axisY, deltaTime: deltaSeconds }); + } + + return { isMoving }; + } +} \ 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 new file mode 100644 index 0000000..adcdc27 --- /dev/null +++ b/apps/client/src/scenes/game/application/loopSteps/SimulationStep.ts @@ -0,0 +1,43 @@ +import { config } from "@repo/shared"; +import { socketManager } from "@client/network/SocketManager"; +import { LocalPlayerController, RemotePlayerController } from "../../entities/player/PlayerController"; +import type { GamePlayers } from "../game.types"; + +type SimulationStepParams = { + me: LocalPlayerController; + players: GamePlayers; + deltaSeconds: number; + isMoving: boolean; +}; + +export class SimulationStep { + private lastPositionSentTime = 0; + private wasMoving = false; + + public run({ me, players, deltaSeconds, isMoving }: SimulationStepParams) { + if (isMoving) { + me.tick(); + + const now = performance.now(); + if (now - this.lastPositionSentTime >= config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS) { + const position = me.getPosition(); + socketManager.game.sendMove(position.x, position.y); + this.lastPositionSentTime = now; + } + } else if (this.wasMoving) { + me.tick(); + const position = me.getPosition(); + socketManager.game.sendMove(position.x, position.y); + } else { + me.tick(); + } + + this.wasMoving = isMoving; + + Object.values(players).forEach((player) => { + if (player instanceof RemotePlayerController) { + player.tick(deltaSeconds); + } + }); + } +} \ No newline at end of file diff --git a/apps/client/src/scenes/game/hooks/useGameSceneController.ts b/apps/client/src/scenes/game/hooks/useGameSceneController.ts index 62eb640..a688a92 100644 --- a/apps/client/src/scenes/game/hooks/useGameSceneController.ts +++ b/apps/client/src/scenes/game/hooks/useGameSceneController.ts @@ -29,8 +29,9 @@ }); const timerInterval = setInterval(() => { - setTimeLeft(formatRemainingTime(manager.getRemainingTime())); - }, 100); + const nextDisplay = formatRemainingTime(manager.getRemainingTime()); + setTimeLeft((prev) => (prev === nextDisplay ? prev : nextDisplay)); + }, config.GAME_CONFIG.TIMER_DISPLAY_UPDATE_MS); return () => { manager.destroy(); diff --git a/packages/shared/src/config/gameConfig.ts b/packages/shared/src/config/gameConfig.ts index ada6e0b..32b91e2 100644 --- a/packages/shared/src/config/gameConfig.ts +++ b/packages/shared/src/config/gameConfig.ts @@ -3,6 +3,9 @@ MAX_PLAYERS_PER_ROOM: 4, // ルーム収容人数設定 GAME_DURATION_SEC: 180, // 1ゲームの制限時間(3分 = 180秒) + // UI表示更新設定 + TIMER_DISPLAY_UPDATE_MS: 250, // 残り時間表示の更新間隔(ms) + // ネットワーク・描画補間設定 PLAYER_POSITION_UPDATE_MS: 50, // 座標送信間隔(20Hz) PLAYER_LERP_SMOOTHNESS: 18, // 補間の滑らかさ(秒基準、目安: 12〜20)