글에 사용된 모든 그림과 내용은 직접 만들고 작성한 것입니다.
서로 다른 Public IP 주소를 가지는 디바이스끼리 게임을 할 수 있도록 만들기 위해 데디케이티드 서버를 공부하고 구현한 내용을 정리하기 위함
- 데디케이티드 서버는 서버 컴퓨터에서 실행되는 하나의 프로세스이다.
- 데디케이티드 서버에서의 로직은 RPC로 만들어야한다.
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/setting-up-dedicated-servers-in-unreal-engine
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/sessions-interface-in-unreal-engine
https://dev.epicgames.com/community/learning/tutorials/eK7q/unreal-engine-setting-up-a-dedicated-server-to-host-eos-sessions
https://www.youtube.com/watch?app=desktop&v=09yWANtKmC8
https://www.youtube.com/watch?v=tfC7HtP1oKQ
https://forums.unrealengine.com/t/creating-a-session-on-dedicated-server-launch/326004/3
https://eoshelp.epicgames.com/s/question/0D54z00009JFxNYCA1/dedicated-server-session-on-join-delegates?language=en_US
https://velog.io/@kih0976/UE5-Steam-Join-Session-Issue
https://forums.unrealengine.com/t/ue-5-5-online-subsystem-join-session-always-results-in-on-failure/2125579/18
다음의 포스팅 글을 참고해 진행했습니다. 구현에 많은 도움이 된 글입니다.
https://velog.io/@singery00/UE5-Dedicate-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95
공식 홈페이지에서는 데디케이티드 서버를 다음처럼 소개하고 있습니다.
"데디케이티드 서버(dedicated server) 는 헤드리스로 실행되는 서버로 구성됩니다. 즉 데디케이티드 서버 게임 인스턴스에서 직접 플레이하는 클라이언트가 없습니다. 각 플레이어는 연결된 원격 클라이언트로 참여합니다. 헤드리스 서버는 비주얼을 렌더링하지 않으며 로컬로 플레이하는 사람이 없습니다."
제가 이해한 내용으로 풀어서 설명을 드리자면, 플레이어가 없는 깡통 클라이언트를 하나 만든다고 생각하시면 이해가 빠릅니다. 그리고 그 깡통 클라이언트가 이제부터 서버를 담당하게 되어 액션에 대한 Validation 체크나 다른 클라이언트들에게 정보를 넘겨주는 역할을 수행합니다. 즉 이 데디서버는 클라이언트들끼리 통신을 위한 것입니다.
''' 즉 클라들은 데디서버의 레벨에 참여하는 개념입니다. 사람이 없는 게임 월드에 우리가 참여한다고 생각하면 편합니다. '''
항상 궁금했던 저 서버 기본맵이라고 되어있는 부분이 바로 데디서버가 처음 실행 했을 때 열릴 레벨 입니다. 지금 스크린샷의 경우에 데디서버는 프로세스를 실행하면 Lobby 레벨을 오픈하고 대기하고 있습니다.
그리고 언리얼 엔진은 서버와 클라간의 통신을 위해 PlayerController와 PlayerState는 서버 클라 모두에게 생깁니다. GameMode는 서버에서만 존재합니다. 서버에서는 캐릭터가 없습니다. 즉 RPC에 대한 로직을 PlayerController 혹은 PlayerState에 만들어야 합니다. GAS 프레임워크도 PlayerState에서 Server, Client에 따라 AcknowledgePossession, PossesedBy, OnRep_PlayerState 등에서 InitAbilityActorInfo 함수를 사용하고 Ability System Component에서 항상 Owner Actor가 누군지 확인하는 이유가 이 때문입니다.
데디케이티드 서버를 A, 클라이언트 1을 B, 클라이언트 2를 C라고 하겠습니다. 편의상 나는 B라고 해봅시다. 이렇게 나타낼 수 있습니다.
이제 B에서 채팅 명령어를 통해 A에게 색을 바꾸고 싶다고 요청합니다. 이때 나의 색상을 미리 바꾸고 서버에서 Validation을 false로 주면 다시 원래 색상인 검은색으로 돌리게 할 수 있습니다.
그 다음 A에서는 Validation 체크를 합니다. 혹은 현재 월드에서 적용이 가능한지, B가 바꿀 수 있는 상태인지 등을 확인합니다.
모든 검사를 통과하고 A가 연결된 모든 클라들에게 색상을 바꾸라고 요청합니다.
B와 C는 B 캐릭터의 색상을 바꿉니다.
💡여기서 생각해 볼만한 문제입니다. A에서 B,C에게 색상을 바꾸라는 명령을 내릴때 다음처럼 코드를 작성 할 수 있습니다. 매개변수인 Character는 요청한 B의 캐릭터이고 Color는 색상입니다. 지금 코드는 B 클라에서 B 캐릭터의 색상만 바뀝니다. C 클라에서는 B 캐릭터의 색상이 바뀌지 않습니다. 왜 그럴까요? RPC의 개념을 잘 공부했다면 뭐가 문제인지 금방 알 수 있습니다. A 서버에 있는 B, C 캐릭터는 C클라에 있는 B, C 캐릭터와 그 오브젝트 정보가 같지 않습니다. 서로 다른 pc의 프로세스에서 메모리를 할당받아 만들어진 오브젝트이기 때문입니다. 이를 어떻게 해결할지는 잘 생각해보시기 바랍니다.
UFUNCTION(NetMulticast, Reliable, WithValidation)
virtual void Client_ChangeChatracterColor(ACharacter* Character, const FLinearColor Color);
void AMyPlayerState::Client_ChangeChatracterColor_Implementation(ACharacter* Character, const FLinearColor Color)
{
for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator) // find all controllers
{
APlayerController* TargetController = Cast<APlayerController>(*Iterator);
if (Character == TargetController->GetCharacter())
{
if (AMyCharacter* ch = Cast<AMyCharacter>(Character))
{
ch->ChangeMaterialColor(Color);
}
}
}
}