서버
실제 크레이지 아케이드를 플레이 해보았을 때 물풍선은 평균적으로 2.8초 후에 터지도록 되어 있었습니다. 그리고클라이언트에서 구현할 때는 코루틴을 사용하여 2.8초 후에 물풍선이 터지도록 구현했었습니다. 이것을 이제 서버 쪽으로 옮길 때 크게 고민했던 사항은 물풍선이 터지는 타이밍을 서버에서 계산해줄텐데 일정 시간 후에 실행되는 함수를 어떻게 구현할 것인가? 였습니다.
먼저, 구현되어 있는 네트워크 라이브러리 코드는 연결된 클라이언트가 패킷을 하나씩 처리하도록 구성되어 있습니다. 클라이언트 세션이 WSARecv() 함수를 호출하여 받을 준비를 하고, GetQueuedCompletionStatus()를 통해 패킷이 오면 처리한 후 다시 WSARecv()를 호출하여 받을 준비를 하는 과정으로 동작합니다. 정리 하면 연결된 클라이언트 하나당 아래와 같은 3단계의 반복으로 동작합니다.
- WSARecv() => 패킷을 받을 준비
- GetQueuedCompletionStatus() => 받은 패킷을 처리
- WSARecv() => 다음 패킷을 받을 준비
위 단계를 설명드린 이유는 제가 처음에 구현했던 방식의 문제점을 말씀드리기 위해서 입니다. 물풍선을 어떤 위치에 스폰하는 물풍선 패킷이 도착하면 2.8초 후에 물풍선이 터지는 함수를 해당 스레드가 this_thread::sleep_for와 같은 함수로 일정 시간 대기 후 실행하도록 했었는데 당연하게도 2번이 문제가되어 2.8초 동안 다음 패킷을 받을 수 없으니 물풍선을 놓고나서 2.8초 동안은 물풍선을 놓은 플레이어가 보내는 패킷을 처리할 수 없는 상태가 되고 2.8초 후에 밀려서 처리되는 결과를 낳았습니다. 따라서, 이렇게는 구현할 수 없다는 것을 깨닫고 다른 방법을 찾아보았습니다.
두 번째로 생각한 것은 물풍선 패킷을 보낸 클라이언트가 터지는 것 까지 담당하면 어떨까? 라는 생각을 했었습니다. 물풍선 패킷을 보낸 후 클라이언트가 2.8초후에 그 물풍선 터져야해! 라는 패킷을 보내주어 처리하는 것이었는데 물풍선은 서버가 판단하고 처리하는게 맞다는 생각이 들어 바로 폐기했습니다.
마지막 세 번째 방법으로 채택한 것은 Command 패턴을 사용하는 것 입니다. 일종의 주문서를 만들어서 넘기고 해당 부분을 처리하는 스레드는 따로 두는 것인데, 이 부분을 메인스레드가 담당하도록 구현하였습니다.
while (true)
{
ChannelManager::GetInstance()->Update();
this_thread::sleep_for(1ms);
}
Update()를 실행하면 각 채널별로 그리고 채널에 만들어진 방을 순회하면서 방마다 구현되어 있는 multimap을 체크합니다. multimap의 key값은 실행해야할 시간, value는 실행해야할 함수의 포인터입니다. 메인 스레드는 실행해야할 시간을 체크하여 시간이 지났으면 실행해야하는 함수를 실행한다. 라는 동작방식을 갖고 있습니다.
// Room Update
const auto& job = _jobMultiMap.begin();
if (job->first <= ::GetTickCount64())
{
// 해당 일을 해야하는 시간이 지났다면 실행.
wstringstream log;
log << L"ROOM ID : " << _roomId << L" HAS UPDATE THING";
Utils::Log(log);
job->second();
_jobMultiMap.erase(job);
}
multimap을 사용한 이유는 우선순위 큐를 사용할 수 없었기 때문이고, 희박한 확률로 동시에 물풍선을 놓는 경우가 있을 수 있다고 생각했기 때문입니다. 우선순위 큐를 사용할 수 없는 이유는 실행 시간과 함수포인터를 pair로 묶었을 때 함수 포인터의 비교연산자가 없기 때문입니다. 따라서 이와 같은 이유들로 multimap을 사용하여 구현했습니다. 그리고 2.8초 후에 물풍선이 터지면서 실행되는 함수는 아래의 작업들을 수행하게 됩니다.
- 물풍선의 범위 파악 (물풍선 길이와, 벽이 있는 곳까지의 거리)
- 연달아서 터질 물풍선의 범위
- 부딪치는 벽의 개수 (파괴되어야 할 오브젝트)
- 물풍선에 갇히게 될 플레이어들
- 사라질 아이템들
위의 5가지를 검사 후에 물풍선이 터졌다는 패킷을 broadcast하여 결과를 화면에 표시하도록 하였습니다.
클라이언트
클라이언트는 스페이스 바를 누르면 물풍선 패킷을 보내게 되는데, 0.2초의 쿨타임을 두어 연속으로 패킷을 보내는 것을 방지했고, 물풍선이 해당 위치에 놓일 수 있게 되어 물풍선이 스폰되면 애니메이션 재생 후 터졌다는 패킷이 서버로 부터 도착하면 물풍선을 삭제하고 파도의 애니메이션이 재생되도록 하였습니다.
크게 구현에 어려운 부분은 없었지만 애니메이션 타이밍을 맞추기(파도가 몇 초간 유지되는지...)를 위해 게임을 녹화하고 느리게 재생하는 등의 작업이 생각보다 오래 걸렸던 것 같습니다.
'모작 > 크레이지 아케이드' 카테고리의 다른 글
이동 동기화 (0) | 2023.05.10 |
---|---|
크레이지 아케이드 모작 (0) | 2023.05.10 |