오늘은 NestJS에서 WebSocket을 활용하여 마피아 게임을 개발하는 방식을 정리해보았습니다.
마피아 게임은 실시간 상호작용이 핵심이므로, 웹소켓을 활용한 실시간 통신이 필수적입니다.
다음과 같은 기능에서 웹소켓이 활용됩니다.
✅ 게임 진행 관련 이벤트 (game.start
, game.end
, day.start
, night.start
)
✅ 플레이어 행동 이벤트 (vote.cast
, vote.result
, mafia.kill
, doctor.save
)
✅ 채팅 시스템 (chat.send
, chat.receive
, chat.toggleMute
)
✅ 접속/퇴장 관리 (player.join
, player.leave
)
game.gateway.ts
)NestJS에서는 @WebSocketGateway()
데코레이터를 사용하여 웹소켓 게이트웨이를 생성합니다.
import { WebSocketGateway, WebSocketServer, SubscribeMessage } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({ cors: { origin: '*' } }) // CORS 설정
export class GameGateway {
@WebSocketServer()
server: Server;
// 플레이어가 방에 입장할 때
@SubscribeMessage('player.join')
handlePlayerJoin(client: Socket, payload: { roomId: string; username: string }) {
client.join(payload.roomId);
this.server.to(payload.roomId).emit('player.update', { message: `${payload.username} 님이 입장했습니다.` });
}
// 플레이어가 채팅을 보낼 때
@SubscribeMessage('chat.send')
handleChatMessage(client: Socket, payload: { roomId: string; message: string }) {
this.server.to(payload.roomId).emit('chat.receive', payload);
}
}
설명:
@WebSocketGateway()
→ 웹소켓 게이트웨이 생성 @SubscribeMessage('이벤트이름')
→ 클라이언트가 해당 이벤트를 보낼 때 실행되는 리스너 this.server.to(roomId).emit('이벤트', 데이터)
→ 특정 방에 이벤트 전달 game.service.ts
)로 분리웹소켓 게이트웨이에서 게임 로직을 직접 처리하면 복잡해지므로, 별도의 서비스(GameService)를 만들어 관리합니다.
import { Injectable } from '@nestjs/common';
@Injectable()
export class GameService {
private rooms = new Map<string, { players: string[] }>();
createRoom(roomId: string) {
this.rooms.set(roomId, { players: [] });
}
joinRoom(roomId: string, username: string) {
if (!this.rooms.has(roomId)) this.createRoom(roomId);
this.rooms.get(roomId).players.push(username);
}
getPlayers(roomId: string) {
return this.rooms.get(roomId)?.players || [];
}
}
설명:
rooms
맵을 활용하여 방 관리 createRoom()
→ 방 생성 joinRoom()
→ 플레이어 입장 getPlayers()
→ 현재 방의 플레이어 목록 조회 vote.cast
이벤트 처리)투표 기능을 추가하려면 웹소켓 이벤트를 활용하여 클라이언트에서 투표를 보내고, 서버에서 투표 결과를 계산해야 합니다.
game.gateway.ts
)@SubscribeMessage('vote.cast')
handleVote(client: Socket, payload: { roomId: string; voter: string; target: string }) {
const result = this.gameService.castVote(payload.roomId, payload.voter, payload.target);
this.server.to(payload.roomId).emit('vote.update', result);
}
GameService
에서 처리 (game.service.ts
)private votes = new Map<string, Map<string, string>>();
castVote(roomId: string, voter: string, target: string) {
if (!this.votes.has(roomId)) this.votes.set(roomId, new Map());
this.votes.get(roomId).set(voter, target);
return { voter, target, votes: this.votes.get(roomId) };
}
설명:
vote.cast
이벤트를 감지하면 castVote()
호출 castVote()
에서 votes
맵을 사용해 투표 결과 저장 클라이언트(React, Vue, etc.)에서는 socket.io-client
를 사용해 웹소켓과 연결합니다.
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000'); // 서버 주소
// 방 입장
socket.emit('player.join', { roomId: '123', username: '홍길동' });
// 채팅 보내기
socket.emit('chat.send', { roomId: '123', message: '안녕하세요!' });
// 서버에서 채팅 메시지 수신
socket.on('chat.receive', (data) => {
console.log('채팅 메시지:', data.message);
});
✅ NestJS의 WebSocket 모듈을 활용하면 실시간 마피아 게임 개발이 가능
✅ Gateway(이벤트 처리)와 Service(게임 로직) 분리로 코드 유지보수성 향상
✅ 채팅, 투표, 게임 진행 이벤트를 웹소켓으로 처리 가능
✅ 클라이언트와 연결하여 실시간 상호작용을 구현
다음 목표
이제 본격적으로 게임 기능을 확장해 나가야겠습니다!