diff --git a/apps/server/src/domains/game/GameHandler.ts b/apps/server/src/domains/game/GameHandler.ts index 7340835..3d67bd4 100644 --- a/apps/server/src/domains/game/GameHandler.ts +++ b/apps/server/src/domains/game/GameHandler.ts @@ -24,8 +24,20 @@ // ルーム全員向けゲーム開始通知 io.to(room.roomId).emit(SocketEvents.GAME_START); - // 20Hzのゲームループを開始 - gameManager.startGameLoop(room.roomId, io, playerIds); + // 20Hzのゲームループを開始し、毎フレームの送信処理を定義 + gameManager.startGameLoop(room.roomId, playerIds, (tickData) => { + + // 1. 各プレイヤーの最新座標をクライアントに送信 + tickData.players.forEach((playerData) => { + io.to(room.roomId).emit(SocketEvents.UPDATE_PLAYER, playerData); + }); + + // 2. 差分があれば、ルーム内の全員に一斉送信 + if (tickData.cellUpdates.length > 0) { + io.to(room.roomId).emit(SocketEvents.UPDATE_MAP_CELLS, tickData.cellUpdates); + } + + }); } }); @@ -37,7 +49,6 @@ // ゲームプレイ中イベント群 socket.on(SocketEvents.MOVE, (data: MovePayload) => { - // 【変更】座標の更新のみ行い、即時の送信(io.to...emit)は全て削除 gameManager.movePlayer(socket.id, data.x, data.y); }); diff --git a/apps/server/src/domains/game/GameLoop.ts b/apps/server/src/domains/game/GameLoop.ts new file mode 100644 index 0000000..50c81f8 --- /dev/null +++ b/apps/server/src/domains/game/GameLoop.ts @@ -0,0 +1,76 @@ +import { Player } from "./entities/Player.js"; +import { MapStore } from "./states/MapStore"; +import { getGridIndexFromPosition } from "@repo/shared/src/domains/gridMap/gridMap.logic"; +import type { CellUpdate } from "@repo/shared/src/domains/gridMap/gridMap.type"; + +// コールバックで渡すデータの型定義 +export interface TickData { + players: { + id: string; + x: number; + y: number; + teamId: number; + }[]; + cellUpdates: CellUpdate[]; +} + +export class GameLoop { + private loopId: NodeJS.Timeout | null = null; + + constructor( + private roomId: string, + private tickRate: number, + private playerIds: string[], + private players: Map, + private mapStore: MapStore, + private onTick: (data: TickData) => void + ) {} + + start() { + // 既にループが回っている場合は何もしない + if (this.loopId) return; + + this.loopId = setInterval(() => { + const playersData: TickData["players"] = []; + + // 1. 各プレイヤーの座標処理とマス塗りの判定 + this.playerIds.forEach(id => { + const player = this.players.get(id); + if (!player) return; + + const gridIndex = getGridIndexFromPosition(player.x, player.y); + if (gridIndex !== null) { + this.mapStore.paintCell(gridIndex, player.teamId); + } + + // 送信用のプレイヤーデータを構築 + playersData.push({ + id: player.id, + x: player.x, + y: player.y, + teamId: player.teamId, + }); + }); + + // 2. マスの差分(Diff)を取得 + const cellUpdates = this.mapStore.getAndClearUpdates(); + + // 3. 通信層(GameHandler)へデータを渡す + this.onTick({ + players: playersData, + cellUpdates: cellUpdates, + }); + + }, this.tickRate); + + console.log(`[GameLoop] Started for room: ${this.roomId} at ${this.tickRate}ms`); + } + + stop() { + if (this.loopId) { + clearInterval(this.loopId); + this.loopId = null; + console.log(`[GameLoop] Stopped for room: ${this.roomId}`); + } + } +} \ No newline at end of file diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index dc71ff2..7b010e1 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -3,14 +3,13 @@ import { MapStore } from "./states/MapStore"; import { getGridIndexFromPosition } from "@repo/shared/src/domains/gridMap/gridMap.logic"; import type { CellUpdate } from "@repo/shared/src/domains/gridMap/gridMap.type"; -import { SocketEvents } from "@repo/shared/src/protocol/events" -import { Server } from "socket.io"; +import { GameLoop, type TickData } from "./GameLoop"; // プレイヤー集合の生成・更新・参照管理クラス export class GameManager { private players: Map; private mapStore: MapStore; - private gameLoops: Map; + private gameLoops: Map; // NodeJS.Timeout から変更 constructor() { this.players = new Map(); @@ -21,11 +20,8 @@ // 新規プレイヤー登録と初期位置設定処理 addPlayer(id: string): Player { const player = new Player(id); - - // 初期スポーン位置 player.x = GAME_CONFIG.MAP_WIDTH / 2; player.y = GAME_CONFIG.MAP_HEIGHT / 2; - this.players.set(id, player); return player; } @@ -44,17 +40,11 @@ movePlayer(id: string, x: number, y: number) { const player = this.players.get(id); if (player) { - - // 受信移動要求ログ console.log(`Move Request -> ID:${id.slice(0,4)} x:${Math.round(x)} y:${Math.round(y)}`); - - // 無効座標データ防御チェック if (typeof x !== "number" || typeof y !== "number" || isNaN(x) || isNaN(y)) { console.log("⚠️ 無効なデータなので無視しました"); return; } - - // クライアント送信絶対座標の直接反映 player.x = x; player.y = y; } @@ -63,63 +53,36 @@ /** * 20Hz固定のゲームループを開始する * @param roomId ルームID - * @param io WebSocketサーバーインスタンス * @param playerIds このルームに参加しているプレイヤーのIDリスト + * @param onTick 毎フレーム実行される送信用のコールバック関数 */ - startGameLoop(roomId: string, io: Server, playerIds: string[]) { - // 既にループが回っている場合は何もしない + startGameLoop(roomId: string, playerIds: string[], onTick: (data: TickData) => void) { if (this.gameLoops.has(roomId)) return; - // gameConfigから20Hz(50ms)の定数を取得 const tickRate = GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; + + // GameLoopインスタンスを生成し、参照を渡す + const loop = new GameLoop( + roomId, + tickRate, + playerIds, + this.players, + this.mapStore, + onTick + ); - const loopId = setInterval(() => { - // 1. 各プレイヤーの処理 - playerIds.forEach(id => { - const player = this.players.get(id); - if (!player) return; - - // マス塗りの判定 - const gridIndex = getGridIndexFromPosition(player.x, player.y); - if (gridIndex !== null) { - this.mapStore.paintCell(gridIndex, player.teamId); - } - - // 【追加】ここで各プレイヤーの最新座標をクライアントに送信する! - io.to(roomId).emit(SocketEvents.UPDATE_PLAYER, { - id: player.id, - x: player.x, - y: player.y, - teamId: player.teamId - }); - }); - - // 2. マスの差分(Diff)を取得 - const cellUpdates = this.mapStore.getAndClearUpdates(); - - // 3. 差分があれば、ルーム内の全員に一斉送信 - if (cellUpdates.length > 0) { - io.to(roomId).emit(SocketEvents.UPDATE_MAP_CELLS, cellUpdates); - } - - // 4. 今後、プレイヤーの座標データ(UPDATE_PLAYER)もここで一括送信するように変更できます - - }, tickRate); - - // ループIDを保存 - this.gameLoops.set(roomId, loopId); - console.log(`[GameLoop] Started for room: ${roomId} at ${tickRate}ms`); + loop.start(); + this.gameLoops.set(roomId, loop); } /** * ゲームループを停止する */ stopGameLoop(roomId: string) { - const loopId = this.gameLoops.get(roomId); - if (loopId) { - clearInterval(loopId); + const loop = this.gameLoops.get(roomId); + if (loop) { + loop.stop(); this.gameLoops.delete(roomId); - console.log(`[GameLoop] Stopped for room: ${roomId}`); } } diff --git "a/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" "b/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" index f00455c..cf5888b 100644 --- "a/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" +++ "b/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" @@ -55,12 +55,13 @@ │ │ │ └── server/ # 【権限】バックエンド (Node.js) │ ├── src/ - │ │ ├── entities/ # サーバー側エンティティ (Player) - │ │ ├── handlers/ # 処理ハンドラ (GameHandler, RoomHandler) - │ │ ├── managers/ # 管理クラス (GameManager, RoomManager) - │ │ ├── network/ # WebSocket処理 (SocketManager) - │ │ ├── states/ # 状態ストア (MapStore) - │ │ └── index.ts # エントリーポイント + │ │ ├── domains/ # ドメイン別実装 + │ │ │ ├── game/ # ゲーム進行 (GameHandler, GameLoop, GameManager) + │ │ │ │ ├── entities/ # ゲーム内エンティティ (Player) + │ │ │ │ └── states/ # ゲーム状態ストア (MapStore) + │ │ │ └── room/ # ルーム管理 (RoomHandler, RoomManager) + │ │ ├── network/ # WebSocket処理 (SocketManager) + │ │ └── index.ts # エントリーポイント │ ├── tsconfig.json │ └── package.json │