[Packet 통신] 디펜스 게임

서버 중심 데이터 관리

호지98 2024. 7. 15. 22:09

GameData 객체를 사용하여 서버가 클라이언트에 정보를 전달하는 과정을 중점적으로 설명하겠습니다. 코드는 매칭 요청을 처리하여 두 명의 플레이어를 매칭하고, 게임 데이터를 생성하여 클라이언트에 전송합니다.

코드 개요

이 코드의 주요 기능은 다음과 같습니다:

  1. 매칭 요청 처리 (handleMatchRequest 함수)
  2. 랜덤 몬스터 경로 생성 (generateRandomMonsterPath 함수)
  3. 경로 근처의 랜덤 위치 생성 (getRandomPositionNearPath 함수)
  4. GameData 객체 생성 및 클라이언트로 전송

매칭 요청 처리

handleMatchRequest 함수는 클라이언트로부터 매칭 요청을 받으면 대기열에 추가하고, 대기열에 두 명 이상의 플레이어가 있을 때 매칭을 성사시킵니다.

function handleMatchRequest(socket, data) {
  const { userId } = data;
  console.log(`매치 요청을 보낸 유저 ID: ${userId}`);
  queue.push({ socket, userId });
  console.log(`현재 대기열 상태: ${queue.map((user) => user.userId).join(', ')}`);
  
  if (queue.length >= 2) {
    const player1 = queue.shift();
    const player2 = queue.shift();
    console.log(`매칭 성공: ${player1.userId} vs ${player2.userId}`);

 

랜덤 몬스터 경로 생성

generateRandomMonsterPath 함수는 게임에서 몬스터가 이동할 랜덤 경로를 생성합니다. 경로는 캔버스 크기 내에서 랜덤한 x, y 좌표를 가지며, 경로가 캔버스 너비를 초과하지 않도록 합니다.

 

function generateRandomMonsterPath() {
  const canvasHeight = RESOLUTION_HEIGHT;
  const canvasWidth = RESOLUTION_WIDTH;
  const path = [];
  let currentX = 0;
  let currentY = Math.floor(Math.random() * 21) + 500; // 500 ~ 520 범위의 y 시작
  path.push({ x: currentX, y: currentY });

  while (currentX < canvasWidth) {
    currentX += Math.floor(Math.random() * 100) + 50; // 50 ~ 150 범위의 x 증가
    if (currentX > canvasWidth) {
      currentX = canvasWidth;
    }
    currentY += Math.floor(Math.random() * 200) - 100; // -100 ~ 100 범위의 y 변경
    if (currentY < 0) {
      currentY = 0;
    }
    if (currentY > canvasHeight) {
      currentY = canvasHeight;
    }
    path.push({ x: currentX, y: currentY });
  }

  return path;
}

 

경로 근처의 랜덤 위치 생성

getRandomPositionNearPath 함수는 주어진 경로 근처의 랜덤 위치를 생성합니다. 이는 플레이어의 초기 타워 위치를 설정하는 데 사용됩니다.

function getRandomPositionNearPath(maxDistance, monsterPath) {
  const segmentIndex = Math.floor(Math.random() * (monsterPath.length - 1));
  const startX = monsterPath[segmentIndex].x;
  const startY = monsterPath[segmentIndex].y;
  const endX = monsterPath[segmentIndex + 1].x;
  const endY = monsterPath[segmentIndex + 1].y;

  const t = Math.random();
  const posX = startX + t * (endX - startX);
  const posY = startY + t * (endY - startY);

  const offsetX = (Math.random() - 0.5) * 2 * maxDistance;
  const offsetY = (Math.random() - 0.5) * 2 * maxDistance;

  return {
    x: posX + offsetX,
    y: posY + offsetY,
  };
}

 

GameData 객체 생성 및 클라이언트로 전송

매칭이 성사되면, 두 플레이어의 몬스터 경로와 초기 타워 위치를 생성합니다. 그런 다음 GameData 객체를 생성하고, createPlayData 함수로 데이터베이스에 저장합니다. 마지막으로, 각 클라이언트에 매칭 성공 패킷과 게임 데이터를 전송합니다.

 

const player1MonsterPath = generateRandomMonsterPath();
const player2MonsterPath = generateRandomMonsterPath();
let player1InitialTowerCoords = [];
let player2InitialTowerCoords = [];

for (let i = 0; i < 5; i++) {
  player1InitialTowerCoords.push(getRandomPositionNearPath(200, player1MonsterPath));
  player2InitialTowerCoords.push(getRandomPositionNearPath(200, player2MonsterPath));
}

createPlayData(
  player1.userId,
  new GameData(
    player1MonsterPath,
    player1InitialTowerCoords,
    player1MonsterPath[player1MonsterPath.length - 1],
    player2MonsterPath,
    player2InitialTowerCoords,
    player2MonsterPath[player2MonsterPath.length - 1],
  ),
);
createPlayData(
  player2.userId,
  new GameData(
    player2MonsterPath,
    player2InitialTowerCoords,
    player2MonsterPath[player2MonsterPath.length - 1],
    player1MonsterPath,
    player1InitialTowerCoords,
    player1MonsterPath[player1MonsterPath.length - 1],
  ),
);

const player1Payload = getPlayData(player1.userId);
const player2Payload = getPlayData(player2.userId);

player1.socket.emit('event', packet, player1Payload);
player2.socket.emit('event', { ...packet, opponentId: player1.userId }, player2Payload);

 

결론

이 코드는 클라이언트로부터 매칭 요청을 받아 두 명의 플레이어를 매칭하고, GameData 객체를 생성하여 각 클라이언트에 전송합니다. 이를 통해 게임 시작 시 필요한 초기 데이터(몬스터 경로, 타워 위치 등)를 클라이언트에 제공하게 됩니다. 이 과정은 실시간 게임에서 플레이어 간의 공정한 시작을 보장하고, 게임 서버와 클라이언트 간의 원활한 데이터 동기화를 가능하게 합니다.