UI 파트의 코드는 내 담당이었지만, 서버 담당 팀원분이 고친 코드도 있고, 어떻게 서버와 연계되어서 동기화하도록 작성되었는지 궁금해서 가장 먼저 분석해보기로 했다.
메인메뉴 UI
기획 흐름
메인 메뉴에서 GameStart 버튼을 누름 -> 존재하는 방 검색 후 참가 / 없을 경우 직접 생성
GameStart 버튼 클릭 시 콜백 함수는 다음과 같다.
void USGMainMenuWidget::OnGameStartButtonClicked()
{
UGameInstance* GameInstance = GetGameInstance();
USGMultiplayGameInstance* SGGameInstance = Cast<USGMultiplayGameInstance>(GameInstance);
if (SGGameInstance)
{
//SGGameInstance->CreateServer();
SGGameInstance->FindServers();
}
else
{
UE_LOG(LogTemp,Warning,TEXT(" SGGameInstance : None "));
}
}
기존에 내가 방 열기를 구현할 때 CreateServer()를 바로 시켰다가, 존재하는 서버를 찾는 FindServers() 함수로 바꿨었다.
게임 인스턴스의 FindServers() 함수를 확인해보자.
SGMultiplayGameInstance
FindServers()
전체 코드는 다음과 같다.
void USGMultiplayGameInstance::FindServers()
{
if (SessionInterface.IsValid())
{
ULocalPlayer* LocalPlayer = GetFirstGamePlayer();
if (LocalPlayer == nullptr)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("[Multiplay] 에러: LocalPlayer를 찾을 수 없어 검색을 중단합니다."));
}
return;
}
FindSessionsCompleteDelegateHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);
// 검색 바구니 세팅
SessionSearch = MakeShareable(new FOnlineSessionSearch());
SessionSearch->MaxSearchResults = 10000;
IOnlineSubsystem* OSS = IOnlineSubsystem::Get();
bool bIsLAN = true;
if (OSS)
{
bIsLAN = (OSS->GetSubsystemName()=="NULL");
UE_LOG(
LogTemp,
Warning,
TEXT("[Multiplay] OnlineSubsystem: %s"),
*OSS->GetSubsystemName().ToString()
);
}
else
{
UE_LOG(
LogTemp,
Error,
TEXT("[Multiplay] OnlineSubsystem == nullptr")
);
return;
}
SessionSearch->bIsLanQuery = bIsLAN;
if (!bIsLAN)
{
SessionSearch->MaxSearchResults = 100; // 스팀 검색 개수 제한
SessionSearch->QuerySettings.Set(SEARCH_LOBBIES,true,EOnlineComparisonOp::Equals);
}
else
{
SessionSearch->MaxSearchResults = 10000; // LAN 환경은 그대로 높게 유지
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
}
SessionSearch->QuerySettings.Set(SETTING_MAPNAME,FString(TEXT("SG_LobbyLevel")),
EOnlineComparisonOp::Equals);
const bool bFindStarted =SessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(),
SessionSearch.ToSharedRef());
UE_LOG(LogTemp,Warning,TEXT("[Multiplay] FindSessions Return: %s"),
bFindStarted? TEXT("TRUE"): TEXT("FALSE"));
if (!bFindStarted)
{
SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(
FindSessionsCompleteDelegateHandle);
UE_LOG(LogTemp,Error,TEXT("[Multiplay] FindSessions 시작 실패"));
}
}
else
{
UE_LOG(
LogTemp,
Error,
TEXT("[Multiplay] FindServers 실패: SessionInterface Invalid")
);
}
}
흐름을 확인해보겠다.
1. SessionInterface가 유효한지 확인
if (SessionInterface.IsValid())
SessionInterface란?
SessionInterface는 헤더에서
IOnlineSessionPtr Sessioninterface;
로 정의되어있다.
SessionInterface의 기능: 멀티플레이 방을 만들기 위해 해야하는 방 만들기, 검색하기, 참가하기, 나가기, 세션 종료하기 등의 기능을 직접 전부 구현하지 않고, 언리얼의 Online Subsystem이 제공하는 기능들을 호출하기 위한 창구
SessionInterface
-> Online Subsystem
-> Steam / EOS / Null 등 실제 온라인 서비스
이런 식으로 구성되어 있다.
IOnlineSession는 세션 관련 기능들을 제공하는 인터페이스.
// 제공 함수 예시
CreateSession()
FindSessions()
JoinSession()
DestroySession()
StartSession()
EndSession()
인터페이스는 이런 함수들이 구현되어있어야한다는 약속.
IOnlineSessionPtr은 IOnlineSession을 가리키는 스마트 포인터 타입.
Unreal Online Subsystem에서는 세션 인터페이스를 직접 객체로 받아오지 않고 포인터로 받아서 사용한다.
이유: 여러 종류의 온라인 서비스에 대응하기 위해서.
내 코드에서 CreateSession을 호출하면->OnlineSubSystem에서 현재 설정된 플랫폼에 맞게 처리하는 식이다.
각 플랫폼에 맞는 OnlineSubSystem을 가져와서, 그 안의 Session기능 담당자를 가져와 실행한다.
결과적으로 동일한 코드로 세션 생성, 검색, 참가, 삭제를 처리할 수 있게 해준다.
2. 로컬 플레이어를 가져와서 유효성을 확인.
3. FindSessionsCompleteDelegateHandle에 세션 검색 완료 델리게이트 바인딩
FindSessionsCompleteDelegateHandle =
SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);
FindSessionsCompleteDelegateHandle: 나중에 델리게이트 바인딩 등록을 해제하기 위한 핸들.
AddOnFindSessionsCompleteDelegate_Handle(): 검색이 끝나면 이 함수를 불러달라고 등록하는 함수. 델리게이트를 추가하고 핸들을 반환한다.
FindSessionsCompleteDelegate: 검색이 끝나면 실행할 콜백 델리게이트 객체
보통 콜백함수 (OnFindSessionsComplete() 등) 에서 연결한 델리게이트를 해제한다.
[??] FOnFindSessionsCompleteDelegate는 뭐고 FindSessionsCompleteDelegate는 뭐지...?
FOnFindSessionsCompleteDelegate
FindSessionsCompleteDelegate;
FDelegateHandle
FindSessionsCompleteDelegateHandle;
이것들의 차이
기본 델리게이트 (FOnFindSessionsCompleteDelegate)
실행할 콜백 정보를 담는다.
// 예시
FindSessionsCompleteDelegate =
FOnFindSessionsCompleteDelegate::CreateObject(this, &UMyGameInstance::OnFindSessionsComplete)
어떤 함수를 부를지를 담고 있다.
델리게이트 핸들 (FindSessionsCompleteDelegateHandle)
등록한 콜백을 나중에 지우기 위한 식별코드
// 예시
FindSessionsCompleteDelegateHandle =
SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);
[??] 또다른 의문: 이미 FindSessionsCompleteDelegate가 있는데 왜 또 델리게이트를 바인딩하는가?
A. FindSessionCompleteDelegate를 만든 것과, SessionInterface에 등록한 것은 다른 단계임.
FindSessionCompleteDelegate는, 검색이 끝나면 OnFindSessionsComplete 함수를 호출하는 것만 만들어뒀을 뿐이고, 이걸 SessionInterface에 연결해야 해당 SessionInterface가 검색이 끝난 뒤 해당 델리게이트를 호출해 OnfindSessionsComplete 함수를 호출하게 된다.
4. 검색 바구니 세팅
// SGMultiplayGameInstance.h
TSharedPtr<FOnlineSessionSearch> SessionSearch;
// SGMultiplayGameInstance.cpp
// 세션 검색 조건/결과를 담을 검색 객체를 생성
// FonlineSessionSearch 객체를 동적으로 만들고, 그 포인터를 TSharedPtr이 관리하게 만드는 코드
SessionSearch = MakeShareable(new FOnlineSessionSearch());
// 최대 몇 개까지 방을 찾을지 설정
SessionSearch->MaxSearchResults = 10000;
SessionSearch는 포인터임을 잊지말자.
5. 온라인서브시스템을 가져와서, 해당 서브시스템이 Lan인지 아닌지 확인
IOnlineSubsystem* OSS = IOnlineSubsystem::Get();
bool bIsLAN = true;
if (OSS)
{
bIsLAN = (OSS->GetSubsystemName()=="NULL");
UE_LOG(
LogTemp,
Warning,
TEXT("[Multiplay] OnlineSubsystem: %s"),
*OSS->GetSubsystemName().ToString()
);
}
else
{
UE_LOG(
LogTemp,
Error,
TEXT("[Multiplay] OnlineSubsystem == nullptr")
);
return;
}
6. 만약 OnlineSubsystem이 Lan 환경이라면 서치 개수를 그대로 두고, 아니라면 개수를 100개로 줄인다.
(이후에 계속...)