diff --git a/apps/server/src/handlers/GameHandler.ts b/apps/server/src/handlers/GameHandler.ts index c26cf65..e2a524a 100644 --- a/apps/server/src/handlers/GameHandler.ts +++ b/apps/server/src/handlers/GameHandler.ts @@ -13,6 +13,8 @@ if (room) { room.status = RoomStatus.PLAYING; + + const playerIds = room.players.map((p: { id: string }) => p.id); // 同ルーム全プレイヤーのゲーム管理登録 room.players.forEach((p: { id: string }) => { @@ -21,6 +23,9 @@ // ルーム全員向けゲーム開始通知 io.to(room.roomId).emit(SocketEvents.GAME_START); + + // 20Hzのゲームループを開始 + gameManager.startGameLoop(room.roomId, io, playerIds); } }); @@ -32,29 +37,8 @@ // ゲームプレイ中イベント群 socket.on(SocketEvents.MOVE, (data: MovePayload) => { + // 【変更】座標の更新のみ行い、即時の送信(io.to...emit)は全て削除 gameManager.movePlayer(socket.id, data.x, data.y); - - const updatedPlayer = gameManager.getPlayer(socket.id); - if (updatedPlayer) { - const myRooms = Array.from(socket.rooms).filter(r => r !== socket.id); - const targetRoom = myRooms.length > 0 ? myRooms[0] : null; - - if (targetRoom) { - io.to(targetRoom).emit(SocketEvents.UPDATE_PLAYER, { - id: socket.id, - x: updatedPlayer.x, - y: updatedPlayer.y - }); - - // ② 【新規】マス目の更新と差分送信のテスト - const cellUpdates = gameManager.paintAndGetUpdates(socket.id); - if (cellUpdates.length > 0) { - io.to(targetRoom).emit(SocketEvents.UPDATE_MAP_CELLS, cellUpdates); - // ログを出してサーバー側で送信されているか確認すると便利です - console.log(`[MAP_UPDATE] Sent ${cellUpdates.length} updates to room ${targetRoom}`); - } - } - } }); }; diff --git a/apps/server/src/managers/GameManager.ts b/apps/server/src/managers/GameManager.ts index 152cbe0..c3b8bed 100644 --- a/apps/server/src/managers/GameManager.ts +++ b/apps/server/src/managers/GameManager.ts @@ -3,15 +3,19 @@ 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"; // プレイヤー集合の生成・更新・参照管理クラス export class GameManager { private players: Map; private mapStore: MapStore; + private gameLoops: Map; constructor() { this.players = new Map(); this.mapStore = new MapStore(); + this.gameLoops = new Map(); } // 新規プレイヤー登録と初期位置設定処理 @@ -56,6 +60,69 @@ } } + /** + * 20Hz固定のゲームループを開始する + * @param roomId ルームID + * @param io WebSocketサーバーインスタンス + * @param playerIds このルームに参加しているプレイヤーのIDリスト + */ + startGameLoop(roomId: string, io: Server, playerIds: string[]) { + // 既にループが回っている場合は何もしない + if (this.gameLoops.has(roomId)) return; + + // gameConfigから20Hz(50ms)の定数を取得 + const tickRate = GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; + + 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`); + } + + /** + * ゲームループを停止する + */ + stopGameLoop(roomId: string) { + const loopId = this.gameLoops.get(roomId); + if (loopId) { + clearInterval(loopId); + this.gameLoops.delete(roomId); + console.log(`[GameLoop] Stopped for room: ${roomId}`); + } + } + // 登録中全プレイヤー配列取得 getAllPlayers() { return Array.from(this.players.values());