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개로 줄인다. 

 

(이후에 계속...)

 

 

 

 

 

+ Recent posts