온라인 서브시스템은 플랫폼 서비스와 인터페이스로 나뉜다.
인터페이스 내에서는 Session Interface를 비롯한 다양한 인터페이스가 존재한다. 이는 모두 IOnlineSubSystem을 통해 접근이 가능하다. 이러한 온라인 서브시스템은 델리게이트를 이용하여 이벤트를 처리한다.
이 중 세션은 서버에서 실행되는 게임의 인스턴스로, 게임을 플레이하려는 플레이어가 찾아서 참여하는 등 멀티플레이에 있어서 기초적인 역할을 수행한다.
그렇기 때문에 Session Interface에는 다양한 기능이 존재한다.
세션을 만들고(Creating Sessions), 업데이트 하고(Update Sessions) 소멸시키고(Destroy Sessions) 세션을 탐색하는(Find Sessions)등 다양한 기능을 인터페이스의 형태로 제공한다.
이번에는 이러한 세션을 생성하고, 다른 컴퓨터에서는 이를 탐색하고 세션에 들어가는 것을 구현해본다.
향후, 이를 플러그인으로 만들어 사용할 예정이지만, 지금은 기본 캐릭터의 C++ 클래스에 이를 구현하고 테스트해본다.
1. 함수 및 델리게이트 선언
먼저 OnlineSessionInterface라는 이름의 공유포인터로 IOnlineSession을 생성한다. 이는 공유포인터를 통해 메모리를 쉽게 관리하기 위함이다. 또한 TSharedPtr<> 형식으로 OnlineSubSystem에서 기본 제공한다.
다음으로 CreateGameSession이라는 블루프린트 호출이 가능한 함수를 만든다. 이를 통해 블루프린트에서 사용자가 특정 키를 누르면 해당 함수가 작동하도록 할 것이다.
그 뒤, 세션 생성에 성공할 경우 디버그 메시지를 화면에 띄워 이를 알릴 OnCreateSessionComplete 함수를 생성한다.
그리고 CreateGameSession을 호출하고, 세션이 생성되면(성공 여부에 관계 없이) OnCreateSessionComplete를 호출 할 FOnCreateSessionCompleteDelegate 형식의 CreateaSessionCompleteDelegate를 만들어준다. 이는 이름에서도 알 수 있지만, 세션 생성이 완료되면 호출되는 델리게이트이다.
2. CreateGameSession
CreateGameSession은 블루프린트를 통해, 1번키를 누르면 작동하도록 연결할 것이다.
우선 플레이어가 1번키를 누른다면, 앞선 헤더에서 설정한 온라인 세션 인터페이스가 유효한지 검사할 것이고 유효하지 않다면 return을 통해 더 이상 실행되지 않을 것이다.
만일 온라인 세션 인터페이스가 유효하다면, 해당 인터페이스를 통해 세션의 이름을 받아온다.
이때, auto로 받는 이유는 GetNameSession의 반환 값이 FString이 아닌 포인터 값이기 때문이다.
그 뒤, 다음과 같은 코드를 볼 수 있는데
OnlineSessionInterface->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegate);
일반적으로 Handle이 붙는 델리게이트는 바인딩된 작업의 완료 여부를 처리하는 데 주로 사용된다. 이런 델리게이트는 비동기 작업(예: 세션 생성, 로딩 완료 등)에 대한 결과 처리에 많이 사용된다. 이 Handle을 통해 델리게이트를 추가하거나 해제하는 작업을 보다 세밀하게 제어할 수 있다.
이 코드에서는 세션 생성이 완료되었을 때, 해당 델리게이트 CreateSessionCompleteDelegate를 호출하기 위해 사용된다. 그리고 Handle이 붙은 경우, 나중에 특정 이벤트에 대한 델리게이트를 해제하거나 수정하고자 할 때 핸들을 사용하여 관리할 수 있기 때문에 이와 같은 작업을 수행한다.
즉, 델리게이트 이름에 Handle이 붙는 이유는, 해당 델리게이트가 특정 작업(예: 세션 생성, 다운로드 완료 등)의 완료 상태를 추적하고, 그 바인딩을 제어할 수 있는 핸들(참조자 또는 식별자)을 사용하기 때문이다. 이를 통해 바인딩된 델리게이트를 추가적으로 관리하거나 해제할 수 있게 된다.
그 다음으로는 세션의 상태를 설정한다. API에 자세히 나와있으므로 읽어보기를 권장한다.
마지막으로 CreateSession을 위한 플레이어의 넷 아이디가 필요한데, 이는 ULocalPlayer를 통해 플레이어의 컨트롤러를 받아온, 플레이어 컨트롤러에서 참조 가능하다.
델리게이트에 바인딩 될 함수는 다음과 같이 구현하였고,
클래스 생성자에서 세션 생성 완료 델리게이트를 초기화하였다. CreateUObject()는 언리얼 엔진에서 UObject 기반 클래스의 멤버 함수를 델리게이트에 바인딩할 때 사용하는 함수다. 이 함수는 델리게이트를 설정하면서, UObject 클래스에 속한 멤버 함수를 바인딩하는 방식으로 동작한다.
즉 지금까지의 과정을 설명하면
1. 온라인 서브 시스템의 인터페이스 - 세션 인터페이스 사용을 위해 공유 포인터로 온라인 세션 인터페이스를 생성하였다.
TSharedPtr<class IOnlineSession, ESPMode::ThreadSafe> OnlineSessionInterface;
2. 세션 생성을 위해 플레이어의 입력을 받는 과정을 BP로 구현하고, 세션이 만들어지면 사용자에게 알림이 가도록 델리게이트와 그 델리게이트에 바인딩 될 함수를 선언하였다.
protected:
UFUNCTION(BlueprintCallable)
void CreateGameSession();
void OnCreateSessionComplete(FName SessionName, bool bWasSuccesful);
private:
FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate;
3. 생성자 초기화 과정에서, 이 델리게이트가 OnCreateSessionComplete 함수를 바인디앟고, 세션 생성이 완료되면 이 함수를 호출하게 하였다.
여기에서 CreateUObject를 사용해 UObject 기반 클래스인 AMenuSystemCharacter의 멤버 함수인 OnCreateSessionComplete를 바인딩한다.
AMenuSystemCharacter::AMenuSystemCharacter(): CreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete))
4. 세션의 생성 과정에서 세션의 기본적인 세팅까지 만든다. 여기에서 델리게이트 핸들을 통해 델리게이트를 동적 추적하여 메모리 관리 및 향후 델리게이트에 대한 추가적인 작업이 용이하게 만든다.
void AMenuSystemCharacter::CreateGameSession()
{
// Called when pressing 1 key
if (!OnlineSessionInterface.IsValid())
{
return;
}
auto ExistingSession = OnlineSessionInterface->GetNamedSession(NAME_GameSession);
if (ExistingSession != nullptr)
{
OnlineSessionInterface->DestroySession(NAME_GameSession);
}
OnlineSessionInterface->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegate);
TSharedPtr<FOnlineSessionSettings> SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = false;
SessionSettings->NumPublicConnections = 4;
SessionSettings->bAllowJoinInProgress = true;
SessionSettings->bAllowJoinViaPresence = true;
SessionSettings->bShouldAdvertise = true;
SessionSettings->bUsesPresence = true;
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->CreateSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, *SessionSettings);
}
5. 세션 생성 완료 이벤트가 발생하면, 이 델리게이트에 의해 바인딩 된 함수가 호출된다.
void AMenuSystemCharacter::OnCreateSessionComplete(FName SessionName, bool bWasSuccesful)
{
if (bWasSuccesful)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(
-1,
15.f,
FColor::Blue,
FString::Printf(TEXT("Created session: %s"), *SessionName.ToString())
);
}
}
else
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(
-1,
15.f,
FColor::Red,
FString(TEXT("Failed to Create Session!"))
);
}
}
}
그러면 다음과 같이 세션 생성 완료 로그를 확인할 수 있다.
이제 세션을 생성했다면, 접속까지 해봐야한다.
public:
// Pointer to the Online Session Interface
TSharedPtr<class IOnlineSession, ESPMode::ThreadSafe> OnlineSessionInterface;
protected:
UFUNCTION(BlueprintCallable)
void CreateGameSession();
UFUNCTION(BlueprintCallable)
void JoinGameSession();
void OnCreateSessionComplete(FName SessionName, bool bWasSuccesful);
void OnFindSessionComplete(bool bWasSuccesful);
private:
FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate;
FOnFindSessionsCompleteDelegate FindSessionsCompleteDelegate;
TSharedPtr<FOnlineSessionSearch> SessionSearch;
이전과 비슷한 작업이 반복된다. 세션에 들어가기 위한 JoinGameSession()을 만든다.
세션에 들어가기 위해서는 우선 세션을 찾아야한다. 따라서 세션 참가를 위한, 세션 찾기 델리게이트를 만든다. TSharedPtr<FOnlineSessionSearch>는 Unreal Engine에서 사용되는 템플릿 클래스인 TSharedPtr를 사용한 스마트 포인터로, Unreal Engine의 온라인 세션 시스템에서 게임 세션을 검색하기 위해 사용하는 FOnlineSessionSearch 객체를 가리킨다. 이를 통해 검색된 세션 정보를 처리하고 관리할 수 있다. 이 구조를 사용하면, 멀티플레이어 게임에서 세션 검색, 필터링 및 참여할 수 있는 시스템을 쉽게 구축할 수 있다.
다음으로 이전과 동일하게 델리게이트를 콜백 함수와 바인딩 해주고
FindSessionsCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionComplete))
CreateGameSession에 세션 참가를 위해 몇 가지 설정을 추가해준다.
void AMenuSystemCharacter::CreateGameSession()
{
// Called when pressing 1 key
if (!OnlineSessionInterface.IsValid())
{
return;
}
auto ExistingSession = OnlineSessionInterface->GetNamedSession(NAME_GameSession);
if (ExistingSession != nullptr)
{
OnlineSessionInterface->DestroySession(NAME_GameSession);
}
OnlineSessionInterface->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegate);
TSharedPtr<FOnlineSessionSettings> SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = false;
SessionSettings->NumPublicConnections = 4;
SessionSettings->bAllowJoinInProgress = true;
SessionSettings->bAllowJoinViaPresence = true;
SessionSettings->bShouldAdvertise = true;
SessionSettings->bUsesPresence = true;
// UE5에서 세션을 찾을 수 없다면 다음 줄을 추가해야 한다.
SessionSettings->bUseLobbiesIfAvailable = true;
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->CreateSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, *SessionSettings);
}
Join Game Session은 다음과 같이 구성하는데
void AMenuSystemCharacter::JoinGameSession()
{
// Find Game Session
if(!OnlineSessionInterface.IsValid())
{
return;
}
OnlineSessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);
SessionSearch = MakeShareable(new FOnlineSessionSearch());
// dev app ID는 공유 ID라 많은 세션 탐색을 통해 세션을 찾는게 유리
SessionSearch->MaxSearchResults = 1000;
SessionSearch->bIsLanQuery = false;
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), SessionSearch.ToSharedRef());
}
앞선 CreateGameSession과 유사하지만 몇 가지 다른 점이 있다.
Handle의 함수 이름이 바뀌었고, 앞서 생성산 공유 포인터에 온라인 세션 기능을 사용하여 세션 검색을 수행하기 위한 객체를 생성한다. 이 코드는 온라인 세션을 찾기 위한 검색 객체(FOnlineSessionSearch)를 동적으로 생성하고, 이를 스마트 포인터(TSharedPtr)로 래핑하여 메모리 관리를 자동화한다. 이후 이 SessionSearch 객체는 세션 검색을 설정하거나 실행하는 데 사용된다.
그 뒤, 공유 ID 특성 상 많은 사람이 사용하기 때문에 더욱 많은 세션 탐색을 할당하고, LAN 접속을 막는다. 그리고 온라인 세션 검색에서 검색 조건을 설정하는데, 이 코드는 SessionSearch의 QuerySettings에 SEARCH_PRESENCE라는 조건을 추가하여, 온라인 상태에 있는 세션만 검색할 수 있도록 설정한다. 검색 조건에서 SEARCH_PRESENCE가 true로 설정되었고, EOnlineComparisonOp::Equals 연산자를 사용하여 온라인 상태에 있는 세션을 찾도록 필터링한다.
마지막으로 플레이어 컨트롤러를 가져와서, 세션 탐색을 시작한다.
FindSessions는 플레이어의 IP와 SearchSetting에 맞추어 세션을 탐색하게 된다. 이는 결과를 bool 값으로 반환시키고, 플레이어는 세션에 참가하게 된다.
/**
* Searches for sessions matching the settings specified
*
* @param SearchingPlayerId the id of the player searching for a match
* @param SearchSettings the desired settings that the returned sessions will have
*
* @return true if successful searching for sessions, false otherwise
*/
virtual bool FindSessions(const FUniqueNetId& SearchingPlayerId, const TSharedRef<FOnlineSessionSearch>& SearchSettings) = 0;
이렇게 탐색된 결과를 바탕으로, 로그를 찍게 된다.
void AMenuSystemCharacter::OnFindSessionComplete(bool bWasSuccesful)
{
if(SessionSearch->SearchResults.IsEmpty())
{
}
for(auto Result : SessionSearch->SearchResults)
{
FString Id = Result.GetSessionIdStr();
FString User = Result.Session.OwningUserName;
if(GEngine)
{
GEngine->AddOnScreenDebugMessage(
-1,
15.f,
FColor::Green,
FString::Printf(TEXT("Id: %s, User: %s"), *Id, *User)
);
}
}
}
다음과 같이 정상적으로 작동한다.
'Study > Unreal Engine' 카테고리의 다른 글
[Tips] Delegate (0) | 2024.10.20 |
---|---|
[Tips] TSharedPtr (0) | 2024.10.20 |
[UE5 C++ Multiplayer Shooter] #03 Online Subsystem 과 Online Subsystem Steam (0) | 2024.10.20 |
[UE5 C++ Multiplayer Shooter] #02 LAN 접속 (0) | 2024.10.19 |
[UE5 C++ Multiplayer Shooter] #01 언리얼 엔진의 프레임 워크와 기초 그리고 네트워크 (0) | 2024.10.19 |