2025년 2월 13일

김동환·2025년 2월 13일
0

TIL: NestJS 기반 웹소켓 마피아 게임 개발

오늘은 NestJS에서 WebSocket을 활용하여 마피아 게임을 개발하는 방식을 정리해보았습니다.


1. 마피아 게임에서 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)


🛠 2. NestJS에서 WebSocket을 설정하는 방법

1) WebSocket Gateway 생성 (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('이벤트', 데이터) → 특정 방에 이벤트 전달

3. 게임 로직을 서비스(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() → 현재 방의 플레이어 목록 조회

4. 게임 진행 예제: 투표 시스템 (vote.cast 이벤트 처리)

투표 기능을 추가하려면 웹소켓 이벤트를 활용하여 클라이언트에서 투표를 보내고, 서버에서 투표 결과를 계산해야 합니다.

1) WebSocket Gateway에서 이벤트 리스닝 (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);
}

2) 투표 로직을 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 맵을 사용해 투표 결과 저장

5. 클라이언트와 연동 (프론트엔드 예제)

클라이언트(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);
});

6. 결론 및 배운 점

NestJS의 WebSocket 모듈을 활용하면 실시간 마피아 게임 개발이 가능
Gateway(이벤트 처리)와 Service(게임 로직) 분리로 코드 유지보수성 향상
채팅, 투표, 게임 진행 이벤트를 웹소켓으로 처리 가능
클라이언트와 연결하여 실시간 상호작용을 구현

다음 목표

  • 역할(마피아, 시민) 자동 배정 기능 추가
  • 밤낮 시스템 구현 (마피아는 밤에만 공격 가능)
  • 투표 마감 후 결과 발표

이제 본격적으로 게임 기능을 확장해 나가야겠습니다!

profile
Node.js 7기

0개의 댓글

관련 채용 정보