1. 코드의 개요

이 코드는 온라인 게임 서버에서 몬스터 AI가 플레이어를 추적하고 공격하는 길찾기 기능을 포함하고 있으며, 클라이언트와 서버 간 동기화를 수행하는 기능을 가지고 있다.
또한, 게임의 주요 오브젝트(Monster, Player)를 관리하는 GameRoom이 존재하며, 여기서 몬스터와 플레이어의 상태를 지속적으로 갱신하는 역할을 수행한다.


2. 사용된 주요 기술 및 개념

  1. 서버-클라이언트 동기화

    • 서버에서 몬스터의 이동을 연산하고 이를 클라이언트에 전달하여 동기화함.
    • 클라이언트는 서버에서 보낸 데이터를 기반으로 몬스터의 움직임을 화면에 렌더링.
    • Broadcast() 메서드를 사용하여 서버에서 모든 클라이언트에게 몬스터의 이동 정보를 전송.
  2. 길찾기 알고리즘 (A* 기반 휴리스틱 탐색)

    • FindPath() 함수에서 길찾기를 수행하며, 우선순위 큐(priority_queue) 를 사용해 최적 경로를 탐색함.
    • 맨해튼 거리(Manhattan Distance) 를 기반으로 휴리스틱을 설정하여 최단 거리를 우선 탐색.
    • CanGo() 함수를 활용해 몬스터가 이동할 수 있는지 체크.
  3. 게임 오브젝트 관리

    • GameRoom에서 PlayerMonster를 관리.
    • FindClosestPlayer()를 통해 가장 가까운 플레이어를 탐색하여 타겟으로 설정.
    • UpdateIdle(), UpdateMove(), UpdateSkill()을 통해 몬스터의 상태 전이(State Transition) 관리.

3. 코드 분석

1) Monster.h : 몬스터 클래스 선언

#pragma once
#include "GameObject.h"

class Monster : public GameObject
{
	using Super = GameObject;

public:
	Monster();
	virtual ~Monster() override;

	virtual void Update();

private:
	virtual void UpdateIdle();
	virtual void UpdateMove();
	virtual void UpdateSkill();

private:
	uint64 _waitUntil = 0;  // 대기 시간 (밀리초 단위)
	weak_ptr<Player> _target; // 목표 플레이어 (스마트 포인터 사용)
};

🔹 분석

  • Monster 클래스는 GameObject 를 상속받는다.
  • _waitUntil: 몬스터가 특정 행동 후 대기해야 하는 시간 (GetTickCount64() 를 통해 체크)
  • _target: 몬스터가 추적하는 플레이어를 weak_ptr<Player> 로 저장 (메모리 관리 최적화)
  • Update(): 몬스터의 상태에 따라 UpdateIdle(), UpdateMove(), UpdateSkill() 을 실행.

2) Monster.cpp : 몬스터 로직 구현

Monster::Monster()
{
	info.set_name("MonsterName");
	info.set_hp(50);
	info.set_maxhp(50);
	info.set_attack(5);
	info.set_defence(0);
}

Monster::~Monster()
{
}

🔹 분석

  • 몬스터의 기본 정보(이름, 체력, 공격력, 방어력)를 초기화.

3) Monster::Update() : 상태 업데이트

void Monster::Update()
{
	switch (info.state())
	{
		case IDLE:
			UpdateIdle();
			break;
		case MOVE:
			UpdateMove();
			break;
		case SKILL:
			UpdateSkill();
			break;
	}
}

🔹 분석

  • 몬스터는 IDLE, MOVE, SKILL 상태에 따라 각각 다른 행동을 수행.

4) Monster::UpdateIdle() : 타겟 탐색 및 길찾기

void Monster::UpdateIdle()
{
	if (room == nullptr)
		return;

	// 가장 가까운 플레이어 찾기
	if (_target.lock() == nullptr)
		_target = room->FindClosestPlayer(GetCellPos());

	PlayerRef target = _target.lock();
	if (target)
	{
		Vec2Int dir = target->GetCellPos() - GetCellPos();
		int32 dist = abs(dir.x) + abs(dir.y);
		if (dist == 1)
		{
			SetDir(GetLookAtDir(target->GetCellPos()));
			SetState(SKILL, true);
			_waitUntil = GetTickCount64() + 1000; // 1초 대기 후 공격
		}
		else
		{
			vector<Vec2Int> path;
			if (room->FindPath(GetCellPos(), target->GetCellPos(), OUT path))
			{
				if (path.size() > 1)
				{
					Vec2Int nextPos = path[1];
					if (room->CanGo(nextPos))
					{
						SetDir(GetLookAtDir(nextPos));
						SetCellPos(nextPos);
						_waitUntil = GetTickCount64() + 1000;
						SetState(MOVE, true);
					}
				}
				else
					SetCellPos(path[0]);
			}
		}
	}
}

🔹 분석

  • FindClosestPlayer(): 가장 가까운 플레이어를 찾음.
  • FindPath(): 길찾기 알고리즘을 수행하여 최적 경로를 계산.
  • SetState(SKILL): 근접하면 공격 상태로 변경.
  • SetCellPos(): 이동 가능한 경우 다음 위치로 이동.

5) FindPath() : 길찾기 알고리즘 구현

bool GameRoom::FindPath(Vec2Int src, Vec2Int dest, vector<Vec2Int>& path, int32 maxDepth)
{
	priority_queue<PQNode, vector<PQNode>, greater<PQNode>> pq;
	map<Vec2Int, int32> best;
	map<Vec2Int, Vec2Int> parent;

	int32 cost = abs(dest.y - src.y) + abs(dest.x - src.x);
	pq.push(PQNode(cost, src));
	best[src] = cost;
	parent[src] = src;

	Vec2Int front[4] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};

	while (!pq.empty())
	{
		PQNode node = pq.top();
		pq.pop();

		if (best[node.pos] < node.cost)
			continue;

		if (node.pos == dest)
			break;

		for (int32 dir = 0; dir < 4; dir++)
		{
			Vec2Int nextPos = node.pos + front[dir];

			if (!CanGo(nextPos))
				continue;

			int32 newCost = abs(dest.y - nextPos.y) + abs(dest.x - nextPos.x);
			if (best.find(nextPos) != best.end() && best[nextPos] <= newCost)
				continue;

			best[nextPos] = newCost;
			pq.push(PQNode(newCost, nextPos));
			parent[nextPos] = node.pos;
		}
	}

	path.clear();
	Vec2Int pos = dest;
	while (pos != parent[pos])
	{
		path.push_back(pos);
		pos = parent[pos];
	}
	std::reverse(path.begin(), path.end());
	return true;
}

🔹 분석

  • priority_queue 를 사용하여 우선 탐색.
  • 맨해튼 거리(Manhattan Distance) 를 기반으로 최단 경로 계산.
  • CanGo() 를 통해 이동 가능 여부 확인.

6) GameRoom::Broadcast() : 서버-클라이언트 동기화

void GameRoom::Broadcast(SendBufferRef& sendBuffer)
{
	for (auto& item : _players)
	{
		item.second->session->Send(sendBuffer);
	}
}

🔹 분석

  • SendBufferRef 를 사용하여 모든 클라이언트에게 몬스터의 상태를 동기화.

profile
李家네_공부방

0개의 댓글