diff --git a/apps/server/src/entities/Player.ts b/apps/server/src/entities/Player.ts index 33c3cfc..cac7401 100644 --- a/apps/server/src/entities/Player.ts +++ b/apps/server/src/entities/Player.ts @@ -1,4 +1,5 @@ import type { PlayerData } from "@repo/shared/src/types/player"; +import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; // サーバー側保持プレイヤー状態モデル export class Player implements PlayerData { @@ -10,7 +11,8 @@ constructor(id: string) { this.id = id; - // 0〜3 範囲ランダムチームID割り当て - this.teamId = Math.floor(Math.random() * 4); + // GAME_CONFIGからチーム数を動的に取得して割り当て + const teamCount = GAME_CONFIG.TEAM_COLORS.length; + this.teamId = Math.floor(Math.random() * teamCount); } } \ No newline at end of file diff --git a/apps/server/src/handlers/GameHandler.ts b/apps/server/src/handlers/GameHandler.ts new file mode 100644 index 0000000..5d25f82 --- /dev/null +++ b/apps/server/src/handlers/GameHandler.ts @@ -0,0 +1,52 @@ +import { Server, Socket } from "socket.io"; +import { GameManager } from "../managers/GameManager.js"; +import { RoomManager } from "../managers/RoomManager.js"; +import { SocketEvents } from "@repo/shared/src/protocol/events"; +import { RoomStatus } from "@repo/shared/src/types/room"; +import type { MovePayload } from "@repo/shared/src/types/payloads"; + +export const registerGameHandlers = (io: Server, socket: Socket, gameManager: GameManager, roomManager: RoomManager) => { + + // ゲーム開始要求処理 + socket.on(SocketEvents.START_GAME, () => { + const room = roomManager.getRoomByOwnerId(socket.id); + + if (room) { + room.status = RoomStatus.PLAYING; + + // 同ルーム全プレイヤーのゲーム管理登録 + room.players.forEach(p => { + gameManager.addPlayer(p.id); + }); + + // ルーム全員向けゲーム開始通知 + io.to(room.roomId).emit(SocketEvents.GAME_START); + } + }); + + // 画面準備完了通知受信時初期データ返却 + socket.on(SocketEvents.READY_FOR_GAME, () => { + const allPlayers = gameManager.getAllPlayers(); + socket.emit(SocketEvents.CURRENT_PLAYERS, allPlayers); + }); + + // ゲームプレイ中イベント群 + socket.on(SocketEvents.MOVE, (data: MovePayload) => { + 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 + }); + } + } + }); + +}; \ No newline at end of file diff --git a/apps/server/src/handlers/RoomHandler.ts b/apps/server/src/handlers/RoomHandler.ts new file mode 100644 index 0000000..c0fec72 --- /dev/null +++ b/apps/server/src/handlers/RoomHandler.ts @@ -0,0 +1,20 @@ +import { Server, Socket } from "socket.io"; +import { RoomManager } from "../managers/RoomManager.js"; +import { SocketEvents } from "@repo/shared/src/protocol/events"; +import type { JoinRoomPayload } from "@repo/shared/src/types/room"; + +export const registerRoomHandlers = (io: Server, socket: Socket, roomManager: RoomManager) => { + + socket.on(SocketEvents.JOIN_ROOM, (data: JoinRoomPayload) => { + const { roomId, playerName } = data; + + socket.join(roomId); + + // RoomManagerにデータ操作を依頼 + const room = roomManager.addPlayerToRoom(roomId, socket.id, playerName); + + // ルーム内全員向け最新状態配信 + io.to(roomId).emit(SocketEvents.ROOM_UPDATE, room); + }); + +}; \ No newline at end of file diff --git a/apps/server/src/managers/GameManager.ts b/apps/server/src/managers/GameManager.ts index c39ca32..73c7dd2 100644 --- a/apps/server/src/managers/GameManager.ts +++ b/apps/server/src/managers/GameManager.ts @@ -1,4 +1,5 @@ import { Player } from "../entities/Player.js"; +import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; // プレイヤー集合の生成・更新・参照管理クラス export class GameManager { @@ -13,8 +14,8 @@ const player = new Player(id); // 初期スポーン位置 - player.x = 1000; - player.y = 1000; + player.x = GAME_CONFIG.MAP_WIDTH / 2; + player.y = GAME_CONFIG.MAP_HEIGHT / 2; this.players.set(id, player); return player; diff --git a/apps/server/src/managers/RoomManager.ts b/apps/server/src/managers/RoomManager.ts new file mode 100644 index 0000000..3229016 --- /dev/null +++ b/apps/server/src/managers/RoomManager.ts @@ -0,0 +1,66 @@ +import { Room, RoomStatus, RoomMember } from "@repo/shared/src/types/room"; +import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; + +export class RoomManager { + private rooms: Map = new Map(); + + // ルームにプレイヤーを追加(なければ作成) + public addPlayerToRoom(roomId: string, socketId: string, playerName: string): Room { + let room = this.rooms.get(roomId); + if (!room) { + room = { + roomId: roomId, + ownerId: socketId, + players: [], + status: RoomStatus.WAITING, + maxPlayers: GAME_CONFIG.MAX_PLAYERS_PER_ROOM + }; + this.rooms.set(roomId, room); + } + + const newPlayer: RoomMember = { + id: socketId, + name: playerName, + isOwner: room.ownerId === socketId, + isReady: false + }; + room.players.push(newPlayer); + + return room; + } + + // プレイヤーをルームから削除し、更新があったルームの配列を返す + public removePlayer(socketId: string): Room[] { + const updatedRooms: Room[] = []; + + for (const [roomId, room] of this.rooms.entries()) { + const playerIndex = room.players.findIndex(p => p.id === socketId); + if (playerIndex !== -1) { + room.players.splice(playerIndex, 1); + + if (room.players.length === 0) { + // 空ルーム削除 + this.rooms.delete(roomId); + } else { + // オーナー切断時所有権移譲処理 + if (room.ownerId === socketId) { + room.ownerId = room.players[0].id; + room.players[0].isOwner = true; + } + updatedRooms.push(room); + } + } + } + return updatedRooms; + } + + // オーナーIDからルームを取得 + public getRoomByOwnerId(ownerId: string): Room | undefined { + for (const room of this.rooms.values()) { + if (room.ownerId === ownerId) { + return room; + } + } + return undefined; + } +} \ No newline at end of file diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index 725bf2f..ee6eb3e 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -1,139 +1,44 @@ import { Server, Socket } from "socket.io"; import { GameManager } from "../managers/GameManager.js"; -import { Room, RoomStatus } from "@repo/shared/src/types/room"; +import { RoomManager } from "../managers/RoomManager.js"; import { SocketEvents } from "@repo/shared/src/protocol/events"; -import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; - -type RoomPlayer = Room["players"][0]; +import { registerRoomHandlers } from "../handlers/RoomHandler.js"; +import { registerGameHandlers } from "../handlers/GameHandler.js"; export class SocketManager { private io: Server; private gameManager: GameManager; - - // サーバーメモリ上ルーム情報テーブル - private rooms: Map = new Map(); + private roomManager: RoomManager; constructor(io: Server, gameManager: GameManager) { this.io = io; this.gameManager = gameManager; + this.roomManager = new RoomManager(); // 新設したRoomManagerをインスタンス化 } public initialize() { this.io.on(SocketEvents.CONNECT, (socket: Socket) => { console.log(`✅ User connected: ${socket.id}`); - // ロビー・ルーム関連イベント群 - - socket.on(SocketEvents.JOIN_ROOM, (data: { roomId: string; playerName: string }) => { - const { roomId, playerName } = data; - - // Socket.io ルーム参加処理 - socket.join(roomId); - - // ルーム未作成時の新規作成分岐 - let room = this.rooms.get(roomId); - if (!room) { - room = { - roomId: roomId, - // 先着参加者のオーナー割り当て - ownerId: socket.id, - players: [], - status: RoomStatus.WAITING, - maxPlayers: GAME_CONFIG.MAX_PLAYERS_PER_ROOM - }; - this.rooms.set(roomId, room); - } - - // 参加プレイヤー情報ルーム追加 - const newPlayer: RoomPlayer = { - id: socket.id, - name: playerName, - isOwner: room.ownerId === socket.id, - isReady: false - }; - room.players.push(newPlayer); - - // ルーム内全員向け最新状態配信 - this.io.to(roomId).emit(SocketEvents.ROOM_UPDATE, room); - }); - - // ゲーム開始要求処理 - socket.on(SocketEvents.START_GAME, () => { - for (const [roomId, room] of this.rooms.entries()) { - if (room.ownerId === socket.id) { - room.status = RoomStatus.PLAYING; - - // 同ルーム全プレイヤーのゲーム管理登録 - room.players.forEach(p => { - this.gameManager.addPlayer(p.id); - }); - - // ルーム全員向けゲーム開始通知 - this.io.to(roomId).emit(SocketEvents.GAME_START); - - // 初期プレイヤー一覧送信タイミング分離方針 - break; - } - } - }); - - // 画面準備完了通知受信時初期データ返却 - socket.on(SocketEvents.READY_FOR_GAME, () => { - // ゲーム管理中全プレイヤー情報取得 - const allPlayers = this.gameManager.getAllPlayers(); - - // 通知元ソケット限定初期データ送信 - socket.emit(SocketEvents.CURRENT_PLAYERS, allPlayers); - }); - - // ゲームプレイ中イベント群 - - socket.on(SocketEvents.MOVE, (data: { x: number; y: number }) => { - // サーバー側プレイヤー座標更新 - this.gameManager.movePlayer(socket.id, data.x, data.y); - - const updatedPlayer = this.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) { - this.io.to(targetRoom).emit(SocketEvents.UPDATE_PLAYER, { - id: socket.id, - x: updatedPlayer.x, - y: updatedPlayer.y - }); - } - } - }); + // 各ハンドラに処理を委譲(ルーティング) + registerRoomHandlers(this.io, socket, this.roomManager); + registerGameHandlers(this.io, socket, this.gameManager, this.roomManager); // 切断時イベント群 - socket.on(SocketEvents.DISCONNECT, () => { console.log(`❌ User disconnected: ${socket.id}`); + + // 1. ゲームからの除外処理 this.gameManager.removePlayer(socket.id); this.io.emit(SocketEvents.REMOVE_PLAYER, socket.id); - // 参加中ルームからの切断プレイヤー除外処理 - for (const [roomId, room] of this.rooms.entries()) { - const playerIndex = room.players.findIndex(p => p.id === socket.id); - if (playerIndex !== -1) { - room.players.splice(playerIndex, 1); - - if (room.players.length === 0) { - // 空ルーム削除分岐 - this.rooms.delete(roomId); - } else { - // オーナー切断時所有権移譲処理 - if (room.ownerId === socket.id) { - room.ownerId = room.players[0].id; - room.players[0].isOwner = true; - } - this.io.to(roomId).emit(SocketEvents.ROOM_UPDATE, room); - } - } - } + // 2. ルームからの除外処理 + const updatedRooms = this.roomManager.removePlayer(socket.id); + + // 更新があったルーム(オーナー変更など)にのみ通知を飛ばす + updatedRooms.forEach(room => { + this.io.to(room.roomId).emit(SocketEvents.ROOM_UPDATE, room); + }); }); }); 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 fd36c35..2521b83 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" @@ -43,10 +43,11 @@ │ ├── client/ # 【演出】フロントエンド (Browser) │ │ ├── src/ │ │ │ ├── assets/ # 画像・音声リソース - │ │ │ ├── entities/ # クライアント側エンティティ (Player, GameMap など) - │ │ │ ├── input/ # 入力処理 (Joystick, Keyboard) - │ │ │ ├── network/ # 通信処理 (SocketClient, Reconciler/補正処理) - │ │ │ ├── scenes/ # 画面管理 (Title, Lobby, Game, Result) + │ │ │ ├── entities/ # ゲーム内オブジェクト(Player, GameMap) + │ │ │ ├── input/ # 入力処理 (VirtualJoystick) + │ │ │ ├── managers # 状態管理・エンジン制御(GameManager) + │ │ │ ├── network/ # 通信処理 (SocketClient) + │ │ │ ├── scenes/ # 画面管理 (Title, Lobby, GameScene) │ │ │ └── main.ts # エントリーポイント │ │ ├── index.html │ │ ├── vite.config.ts @@ -54,9 +55,9 @@ │ │ │ └── server/ # 【権限】バックエンド (Node.js) │ ├── src/ - │ │ ├── entities/ # サーバー側エンティティ (Player, Item - 状態管理のみ) - │ │ ├── managers/ # 管理クラス (RoomManager, ScoreManager) - │ │ ├── network/ # WebSocket処理 (Connection, PacketHandler) + │ │ ├── entities/ # サーバー側エンティティ (Player) + │ │ ├── managers/ # 管理クラス (GameManager) + │ │ ├── network/ # WebSocket処理 (SocketManager) │ │ └── index.ts # エントリーポイント │ ├── tsconfig.json │ └── package.json @@ -64,9 +65,9 @@ └── packages/ └── shared/ # 【最重要】「真実」の定義場所(型、定数、純粋ロジック) ├── src/ - │ ├── config/ # ゲーム定数 (マップサイズ, TickRate, 物理定数) - │ ├── protocol/ # 通信規約 (パケット構造, OpCode, バイナリ定義) - │ ├── types/ # 型定義 (PlayerState, InputData, RoomInfo) + │ ├── config/ # 共通定数(gameConfig.ts) + │ ├── protocol/ # 通信イベント定義(events.ts) + │ ├── types/ # 型定義(payloads.ts, player.ts, room.ts) │ └── index.ts # エントリーポイント (tsupビルド対象) ├── tsup.config.ts # ビルド設定 └── package.json diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 14cb0f9..67ad2b4 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,4 +1,6 @@ // shared パッケージ公開 API export * from './config/gameConfig'; export * from './types/room'; +export * from './types/payloads'; +export * from './types/player'; export * from "./protocol/events"; \ No newline at end of file diff --git a/packages/shared/src/types/payloads.ts b/packages/shared/src/types/payloads.ts new file mode 100644 index 0000000..543ba44 --- /dev/null +++ b/packages/shared/src/types/payloads.ts @@ -0,0 +1,5 @@ +// 移動イベント送信ペイロード型 +export interface MovePayload { + x: number; + y: number; +} \ No newline at end of file