diff --git a/apps/client/src/network/SocketClient.ts b/apps/client/src/network/SocketClient.ts index 6338614..a14d709 100644 --- a/apps/client/src/network/SocketClient.ts +++ b/apps/client/src/network/SocketClient.ts @@ -1,6 +1,7 @@ import { io, Socket } from "socket.io-client"; +import { SocketEvents } from "@repo/shared/src/protocol/events"; -// app.tsx で定義されている型と同じものをこちらでも定義しておきます +// プレイヤーの基本情報を定義する型 export type PlayerData = { id: string; x: number; @@ -8,72 +9,109 @@ color: string; }; +/** + * サーバーとのWebSocket通信を管理するクライアントクラス + */ export class SocketClient { public socket: Socket; constructor() { - // app.tsx に合わせて引数なしの io() で接続します + // サーバーへの接続を開始 this.socket = io(); } - // 1. 接続完了 + /** + * 1. 接続完了イベントの購読 + * @param callback 接続成功時に自身のソケットIDを受け取る関数 + */ onConnect(callback: (id: string) => void) { - this.socket.on("connect", () => { + this.socket.on(SocketEvents.CONNECT, () => { callback(this.socket.id || ""); }); } - // 2. 初期プレイヤー一覧の受信 + /** + * 2. 初期プレイヤー一覧の受信 + * ゲーム参加時、既に存在する全プレイヤーのデータを受け取る + */ onCurrentPlayers(callback: (players: any) => void) { - this.socket.on("current_players", callback); + this.socket.on(SocketEvents.CURRENT_PLAYERS, callback); } - // 3. 新規プレイヤーの参加 + /** + * 3. 新規プレイヤー参加イベントの購読 + * 他の誰かが新しく入室したときに通知される + */ onNewPlayer(callback: (player: PlayerData) => void) { - this.socket.on("new_player", callback); + this.socket.on(SocketEvents.NEW_PLAYER, callback); } - // 4. 他プレイヤーの移動・更新 + /** + * 4. 他プレイヤーの状態更新の受信 + * 他のプレイヤーの移動などの座標データを受け取る + */ onUpdatePlayer(callback: (data: any) => void) { - this.socket.on("update_player", callback); + this.socket.on(SocketEvents.UPDATE_PLAYER, callback); } - // 5. プレイヤーの退出 + /** + * 5. プレイヤー退出イベントの購読 + * 他のプレイヤーが切断したときに通知される + */ onRemovePlayer(callback: (id: string) => void) { - this.socket.on("remove_player", callback); + this.socket.on(SocketEvents.REMOVE_PLAYER, callback); } - // 6. 自分の移動を送信 + /** + * 6. 自身の移動データを送信 + * @param x 現在のX座標 + * @param y 現在のY座標 + */ sendMove(x: number, y: number) { - this.socket.emit("move", { x, y }); + this.socket.emit(SocketEvents.MOVE, { x, y }); } - // 👇==== ここからロビー・ルーム用の機能を追加 ====👇 - - // 7. ルーム入室 + /** + * 7. 特定のルームへの入室リクエスト + * @param roomId 入室先のID + * @param playerName 表示名 + */ joinRoom(roomId: string, playerName: string) { - this.socket.emit("join-room", { roomId, playerName }); + this.socket.emit(SocketEvents.JOIN_ROOM, { roomId, playerName }); } - // 8. ルーム情報が更新されたとき + /** + * 8. ルーム情報の更新を受信 + * ルーム内の人数や準備状況が変わるたびにサーバーから送られてくる + */ onRoomUpdate(callback: (room: any) => void) { - this.socket.on("room-update", callback); + this.socket.on(SocketEvents.ROOM_UPDATE, callback); } - // 9. ゲーム開始の合図を受け取ったとき + /** + * 9. ゲーム開始通知の受信 + * サーバー側でゲーム開始が確定したときに呼ばれる + */ onGameStart(callback: () => void) { - this.socket.on("game-start", callback); + this.socket.on(SocketEvents.GAME_START, callback); } - // 10. ゲーム開始をサーバーにリクエストする + /** + * 10. ゲーム開始リクエスト + * ルームオーナーがゲームを開始させる際に送信する + */ startGame() { - this.socket.emit("start-game"); + this.socket.emit(SocketEvents.START_GAME); } - // 👇 11. 【新規追加】ゲーム画面の準備完了をサーバーに伝える + /** + * 11. ゲーム画面準備完了の通知 + * シーン遷移が完了し、データを受け取る準備ができたことをサーバーに伝える + */ readyForGame() { - this.socket.emit("ready-for-game"); + this.socket.emit(SocketEvents.READY_FOR_GAME); } } +// シングルトンインスタンスとしてエクスポート export const socketClient = new SocketClient(); \ No newline at end of file diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index 5139c82..5cc23da 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -3,6 +3,8 @@ import { GameManager } from "../managers/GameManager.js"; // shared側の型をインポート(※パスは実際の環境に合わせて修正してください) import type { Room } from "@repo/shared/src/types/room"; +import { SocketEvents } from "@repo/shared/src/protocol/events"; + type RoomPlayer = Room["players"][0]; export class SocketManager { @@ -18,14 +20,14 @@ } public initialize() { - this.io.on("connection", (socket: Socket) => { + this.io.on(SocketEvents.CONNECT, (socket: Socket) => { console.log(`✅ User connected: ${socket.id}`); // ========================================== // 🚪 ロビー・ルーム関連のイベント // ========================================== - socket.on("join-room", (data: { roomId: string; playerName: string }) => { + socket.on(SocketEvents.JOIN_ROOM, (data: { roomId: string; playerName: string }) => { const { roomId, playerName } = data; // socket.io の機能でグループ(ルーム)に参加 @@ -54,11 +56,11 @@ room.players.push(newPlayer); // ルームの全員に最新情報を送信 - this.io.to(roomId).emit("room-update", room); + this.io.to(roomId).emit(SocketEvents.ROOM_UPDATE, room); }); // 🚀 ゲーム開始イベント - socket.on("start-game", () => { + socket.on(SocketEvents.START_GAME, () => { for (const [roomId, room] of this.rooms.entries()) { if (room.ownerId === socket.id) { room.status = 'playing'; @@ -69,7 +71,7 @@ }); // 📢 全員に「ゲーム画面に切り替えて!」と指示 - this.io.to(roomId).emit("game-start"); + this.io.to(roomId).emit(SocketEvents.GAME_START); // 🚨 【削除】ここでは current_players を送らない!(すれ違い防止) break; @@ -78,19 +80,19 @@ }); // 🌟 【新規追加】 クライアントから「画面の準備完了」が通知されたらデータを送る - socket.on("ready-for-game", () => { + socket.on(SocketEvents.READY_FOR_GAME, () => { // 全プレイヤー情報を取得 const allPlayers = this.gameManager.getAllPlayers(); // 準備が完了した「この通信主(socket)」に対してのみ初期データを送る - socket.emit("current_players", allPlayers); + socket.emit(SocketEvents.CURRENT_PLAYERS, allPlayers); }); // ========================================== // 🎮 ゲームプレイ中のイベント // ========================================== - socket.on("move", (data: { x: number; y: number }) => { + socket.on(SocketEvents.MOVE, (data: { x: number; y: number }) => { // マネージャーの状態を更新 this.gameManager.movePlayer(socket.id, data.x, data.y); @@ -101,7 +103,7 @@ const targetRoom = myRooms.length > 0 ? myRooms[0] : null; if (targetRoom) { - this.io.to(targetRoom).emit("update_player", { + this.io.to(targetRoom).emit(SocketEvents.UPDATE_PLAYER, { id: socket.id, x: updatedPlayer.x, y: updatedPlayer.y @@ -114,10 +116,10 @@ // ❌ 切断時のイベント // ========================================== - socket.on("disconnect", () => { + socket.on(SocketEvents.DISCONNECT, () => { console.log(`❌ User disconnected: ${socket.id}`); this.gameManager.removePlayer(socket.id); - this.io.emit("remove_player", socket.id); + this.io.emit(SocketEvents.REMOVE_PLAYER, socket.id); // ルームからも削除する for (const [roomId, room] of this.rooms.entries()) { @@ -134,7 +136,7 @@ room.ownerId = room.players[0].id; room.players[0].isOwner = true; } - this.io.to(roomId).emit("room-update", room); + this.io.to(roomId).emit(SocketEvents.ROOM_UPDATE, room); } } } diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 9797972..d551a19 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1 +1,3 @@ -export * from './config/gameConfig'; \ No newline at end of file +export * from './config/gameConfig'; +export * from './types/room'; +export * from "./protocol/events"; \ No newline at end of file diff --git a/packages/shared/src/protocol/events.ts b/packages/shared/src/protocol/events.ts new file mode 100644 index 0000000..a01fae4 --- /dev/null +++ b/packages/shared/src/protocol/events.ts @@ -0,0 +1,19 @@ +export const SocketEvents = { + // 接続・切断 + CONNECT: "connect", + DISCONNECT: "disconnect", + + // ロビー・ルーム系 + JOIN_ROOM: "join-room", + ROOM_UPDATE: "room-update", + START_GAME: "start-game", + GAME_START: "game-start", + READY_FOR_GAME: "ready-for-game", + + // ゲームプレイ系 + CURRENT_PLAYERS: "current_players", + NEW_PLAYER: "new_player", + UPDATE_PLAYER: "update_player", + REMOVE_PLAYER: "remove_player", + MOVE: "move" +} as const;