Session 생성·조회·참여 정리
Session
Session을 통한 멀티플레이어 정리
Session을 관리하는 클래스를 따로 만들 수 있겠지만 게임을 시작한 후 생성되고 종료되기 전까지 존재하는 Singleton객체인 GameInstance에서 관리하게 만들었다.
Session 생성·조회·참여 모두 델리게이트를 기반으로 동작한다.
따라서, 핸들을 통해 관리하면 편하다.
https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/Online/SessionInterface/
Session 생성
IOnlineSession::CreateSession()을 통해 Session을 생성할 수 있다.
생성 과정은 다음과 같다.
- CreateSession에서 Session에 대한 설정을 한 뒤, OnlineSessionInterface을 통해 생성 요청을 한다.
- OnlineSessionInterface는 전달받은 정보를 통해 Session 생성을 시도한다.
- 성공여부와 결과를 사용자가 설정해 놓은 델리게이트 함수에 전달한다.
Session 정보 설정과 기본적인 세팅을 알아보자.
우선, Session과 관련된 동작은 OnlineSessionInterface를 통해 이루어져야 한다.
필요할 때마다 받아올 수 있지만, 변수로 관리하는 게 편할 것이다.
#include "Interfaces/OnlineSessionInterface.h"
IOnlineSessionPtr OnlineSessionInterface;
OnlineSessionInterface를 받아오는 방법은 다음과 같다.
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
Session 생성을 위한 정보를 담는 구조체인 FOnlineSessionSettings SessionSettings을 생성하는 방법은 다음과 같다.
TSharedPtr<FOnlineSessionSettings> SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = false; // Lan을 통한 연결
SessionSettings->NumPublicConnections = 4; // 연결 가능 인원
SessionSettings->bAllowJoinInProgress = true; // 세션이 실행 중 참여가능 여부
SessionSettings->bAllowJoinViaPresence = true; // ???
SessionSettings->bShouldAdvertise = true; // Steam을 통해 세션을 알림(세션 조회 가능)
SessionSettings->bUsesPresence = true; // ???
SessionSettings->bUseLobbiesIfAvailable = true; // Lobby 사용 여부
SessionSettings->Set(FName("MatchType"), FString("FreeForAll"), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing); // 세션의 MatchType을 모두에게 열림, 온라인서비스와 핑을 통해 세션 홍보 옵션으로 설정
이제 OnlineSessionInterface에 SessionSettings를 넘겨 Session생성을 요청하면 된다.
// GameInstance에서 실행하기 때문(PlayerController를 얻어올 수 있거나 갖고 있다면 써도됨)
const ULocalPlayer* Localplayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->CreateSession(*Localplayer->GetPreferredUniqueNetId(), NAME_GameSession, *SessionSettings);
Session 생성 요청에 대한 결과를 돌려받을 델리게이트 함수가 필요하다.
이를 정의하고 등록해 주어야 한다.
이를 관리하기 위해 핸들이 필요하다.
즉, 핸들에 결과를 받을 함수를 등록하고 이 핸들을 Session 생성 시 사용하겠다고 등록하면 결과를 돌려받을 수 있다.
핸들을 설정하는 일은 생성자에서 한다.
// .h
FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
======================================================================================
// .cpp
USteamTestGameInstance::USteamTestGameInstance(): OnCreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete))
{
...
}
초기화 리스트를 통해 초기화해주면 효율이 좋아진다.
결과를 처리할 함수의 인터페이스는 다음과 같이 Session의 이름과 성공여부를 돌려받는다.
// .h
void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
================================================================================
// .cpp
void USteamTestGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
if (bWasSuccessful)
{
// 세션 생성 성공
}
else
{
// 세션 생성 실패
}
}
그렇다면 처리 함수가 등록된 핸들을 Session을 생성할 때 해당 Session을 처리하는 핸들로 설정해 주면 끝이다.
OnlineSessionInterface->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
전체코드는 다음과 같다.
// .h
UCLASS()
class STEAMTEST_API USteamTestGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
USteamTestGameInstance();
void CreateSession();
void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
private:
IOnlineSessionPtr OnlineSessionInterface;
FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
};
==============================================================================
//.cpp
void USteamTestGameInstance::CreateSession()
{
// OnlineSubsystem 받아오기
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
// OnlineSubsystem Interface 받아오기
if (!OnlineSessionInterface)
{
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
}
// 기존 Session 제거
const auto ExistingSession = OnlineSessionInterface->GetNamedSession(NAME_GameSession); //
if (ExistingSession != nullptr)
{
OnlineSessionInterface->DestroySession(NAME_GameSession);
}
// Session Setting
TSharedPtr<FOnlineSessionSettings> SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = false; // Lan을 통한 연결
SessionSettings->NumPublicConnections = 4; // 연결 가능 인원
SessionSettings->bAllowJoinInProgress = true; // 세션이 실행 중 참여가능 여부
SessionSettings->bAllowJoinViaPresence = true; // ???
SessionSettings->bShouldAdvertise = true; // Steam을 통해 세션을 알림(세션 조회 가능)
SessionSettings->bUsesPresence = true; // ???
SessionSettings->bUseLobbiesIfAvailable = true; // Lobby 사용 여부
SessionSettings->Set(FName("MatchType"), FString("FreeForAll"), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing); // 세션의 MatchType을 모두에게 열림, 온라인서비스와 핑을 통해 세션 홍보 옵션으로 설정
// Session 생성 요청
const ULocalPlayer* Localplayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->CreateSession(*Localplayer->GetPreferredUniqueNetId(), NAME_GameSession, *SessionSettings);
}
}
void USteamTestGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
if (bWasSuccessful)
{
// 세션 생성 성공
}
else
{
// 세션 생성 실패
}
}
Session 조회
Session을 조회하는 과정은 Session을 생성하는 과정과 동일하다.
IOnlineSession::FindSessions()을 통해 Session들을 조회하면 OnFindSessionsComplete가 호출된다.
우선, Session을 조회할 때 필요한 class인 FOnlineSessionSearch를 선언해야 한다.
TSharedPtr<class FOnlineSessionSearch> SessionSearch;
이 class는 Session 조회에 필요한 정보를 설정하고 결과를 받아 저장하는 class라고 생각하면 된다.
다음은 Session 조회를 요청하는 부분이다.
void USteamTestGameInstance::FindSession()
{
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
// OnlineSubsystem Interface 받아오기
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
if (OnlineSessionInterface)
{
// Find Session Complete Delegate 등록
OnlineSessionInterface->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionCompleteDelegate);
// Find Game Session
SessionSearch = MakeShareable(new FOnlineSessionSearch());
SessionSearch->MaxSearchResults = 10000; // 검색 결과로 나오는 세션 수 최대값
SessionSearch->bIsLanQuery = false; // LAN 사용 여부
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); // 찾을 세션 쿼리를 현재로 설정한다
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), SessionSearch.ToSharedRef());
}
}
}
Session 생성과 마찬가지로 OnlineSubsystem을 통해 OnlineSessionInterface를 받아 요청 시 사용한다.
또한, OnlineSessionInterface에 FindSessionDelegate 핸들을 등록한 후, SessionSearch에 정보를 설정하고 OnlineSessionInterface::FindSessions()을 호출하면 된다.
이후에는, FOnFindSessionsCompleteDelegate를 선언하고 생성자에서 결과 처리 함수를 등록한다.
// .h
FOnFindSessionsCompleteDelegate OnFindSessionCompleteDelegate;
=============================================================================
// .cpp
USteamTestGameInstance::USteamTestGameInstance(): OnFindSessionCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionComplete))
{
...
}
OnFindSessionsComplete에 등록할 함수의 인터페이스는 다음과 같다.
// .h
void OnFindSessionComplete(bool bWasSuccessful);
================================================================================
// .cpp
void USteamTestGameInstance::OnFindSessionComplete(bool bWasSuccessful)
{
if (bWasSuccessful)
{
// 세션 조회 성공
}
else
{
// 세션 조회 실패
}
}
OnFindSessionComplete에서는 SessionSearch->SearchResults를 통해 Session 조회 결과를 확인하면 된다.
전체코드는 다음과 같다.
// .h
UCLASS()
class STEAMTEST_API USteamTestGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
USteamTestGameInstance();
void FindSession();
void OnFindSessionComplete(bool bWasSuccessful);
private:
IOnlineSessionPtr OnlineSessionInterface;
FOnFindSessionsCompleteDelegate OnFindSessionCompleteDelegate;
};
==============================================================================
//.cpp
void USteamTestGameInstance::FindSession()
{
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
// OnlineSubsystem Interface 받아오기
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
if (OnlineSessionInterface)
{
// Find Session Complete Delegate 등록
OnlineSessionInterface->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionCompleteDelegate);
// Find Game Session
SessionSearch = MakeShareable(new FOnlineSessionSearch());
SessionSearch->MaxSearchResults = 10000; // 검색 결과로 나오는 세션 수 최대값
SessionSearch->bIsLanQuery = false; // LAN 사용 여부
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); // 찾을 세션 쿼리를 현재로 설정한다
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), SessionSearch.ToSharedRef());
}
}
}
void USteamTestGameInstance::OnFindSessionComplete(bool bWasSuccessful)
{
OnFindSessionCompleteDelegate.Unbind();
for (auto Result : SessionSearch->SearchResults)
{
// 결과 처리
}
}
Session 참여
Session 참여도 다르지 않다.
IOnlineSession::JoinSession()을 통해 요청을 한 후, 참여 요청이 완료되면 OnJoinSessionComplete
가 호출된다.
JoinSession을 호출하는 부분은 다음과 같다.
void USteamTestGameInstance::JoinSession()
{
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
if (OnlineSessionInterface)
{
// Set the Handle
OnlineSessionInterface->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
// idx: 원하는 Session의 index
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
auto SessionName = FName(SessionSearch->SearchResults[idx].Session.OwningUserName);
auto Result = SessionSearch->SearchResults[idx];
OnlineSessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), SessionName, Result);
}
}
}
JoinSession의 델리게이트를 관리할 핸들과 처리 함수를 등록한다.
// .h
FOnFindSessionsCompleteDelegate OnJoinSessionCompleteDelegate;
=============================================================================
// .cpp
USteamTestGameInstance::USteamTestGameInstance(): OnJoinSessionCompleteDelegate(FOnJoinSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnJoinSessionComplete))
{
...
}
OnJoinSessionsComplete에 등록할 함수의 인터페이스는 다음과 같다.
// .h
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
================================================================================
// .cpp
void USteamTestGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
FString TravelURL;
if (PlayerController && Sessions->GetResolvedConnectString(SessionName, TravelURL))
{
PlayerController->ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute);
}
}
OnJoinSessionComplete에서 GetResolvedConnectString()를 통해 넘겨받은 SessionName에서 URL을 받아 Travel을 하면 Map을 이동할 수 있다.
전체코드는 다음과 같다.
// .h
UCLASS()
class STEAMTEST_API USteamTestGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
USteamTestGameInstance();
void JoinSession();
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
private:
IOnlineSessionPtr OnlineSessionInterface;
FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate;
};
==============================================================================
//.cpp
void USteamTestGameInstance::JoinSession()
{
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
if (OnlineSessionInterface)
{
// Set the Handle
OnlineSessionInterface->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
// idx: 원하는 Session의 index
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
auto SessionName = FName(SessionSearch->SearchResults[idx].Session.OwningUserName);
auto Result = SessionSearch->SearchResults[idx];
OnlineSessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), SessionName, Result);
}
}
}
void USteamTestGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
APlayerController* const PlayerController = GetFirstLocalPlayerController();
FString TravelURL;
if (PlayerController && Sessions->GetResolvedConnectString(SessionName, TravelURL))
{
PlayerController->ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute);
}
}
}
}
전체 코드
// .h
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "SteamTestGameInstance.generated.h"
UCLASS()
class STEAMTEST_API USteamTestGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
USteamTestGameInstance();
void CreateSession();
void FindSession();
void JoinSession();
void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
void OnFindSessionComplete(bool bWasSuccessful);
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
private:
IOnlineSessionPtr OnlineSessionInterface;
FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
FOnFindSessionsCompleteDelegate OnFindSessionCompleteDelegate;
FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate;
TSharedPtr<class FOnlineSessionSearch> SessionSearch;
};
=======================================================================
// .cpp
#include "SteamTestGameInstance.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/GameUserSettings.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "OnlineSubsystem.h"
#include "OnlineSessionSettings.h"
USteamTestGameInstance::USteamTestGameInstance(): OnCreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete))
, OnFindSessionCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionComplete))
, OnJoinSessionCompleteDelegate(FOnJoinSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnJoinSessionComplete))
{
// Constructor
}
void USteamTestGameInstance::CreateSession()
{
// OnlineSubsystem 받아오기
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
// OnlineSubsystem Interface 받아오기
if (!OnlineSessionInterface)
{
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
}
// 기존 Session 제거
const auto ExistingSession = OnlineSessionInterface->GetNamedSession(NAME_GameSession); //
if (ExistingSession != nullptr)
{
OnlineSessionInterface->DestroySession(NAME_GameSession);
}
// Session Setting
TSharedPtr<FOnlineSessionSettings> SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = false; // Lan을 통한 연결
SessionSettings->NumPublicConnections = 4; // 연결 가능 인원
SessionSettings->bAllowJoinInProgress = true; // 세션이 실행 중 참여가능 여부
SessionSettings->bAllowJoinViaPresence = true; // ???
SessionSettings->bShouldAdvertise = true; // Steam을 통해 세션을 알림(세션 조회 가능)
SessionSettings->bUsesPresence = true; // ???
SessionSettings->bUseLobbiesIfAvailable = true; // Lobby 사용 여부
SessionSettings->Set(FName("MatchType"), FString("FreeForAll"), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing); // 세션의 MatchType을 모두에게 열림, 온라인서비스와 핑을 통해 세션 홍보 옵션으로 설정
// Session 생성 요청
const ULocalPlayer* Localplayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->CreateSession(*Localplayer->GetPreferredUniqueNetId(), NAME_GameSession, *SessionSettings);
}
}
void USteamTestGameInstance::FindSession()
{
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
// OnlineSubsystem Interface 받아오기
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
if (OnlineSessionInterface)
{
// Find Session Complete Delegate 등록
OnlineSessionInterface->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionCompleteDelegate);
// Find Game Session
SessionSearch = MakeShareable(new FOnlineSessionSearch());
SessionSearch->MaxSearchResults = 10000; // 검색 결과로 나오는 세션 수 최대값
SessionSearch->bIsLanQuery = false; // LAN 사용 여부
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); // 찾을 세션 쿼리를 현재로 설정한다
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
OnlineSessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), SessionSearch.ToSharedRef());
}
}
}
void USteamTestGameInstance::JoinSession()
{
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem)
{
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
if (OnlineSessionInterface)
{
// Set the Handle
OnlineSessionInterface->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
// idx: 원하는 Session의 index
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
auto SessionName = FName(SessionSearch->SearchResults[idx].Session.OwningUserName);
auto Result = SessionSearch->SearchResults[idx];
OnlineSessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), SessionName, Result);
}
}
}
void USteamTestGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
if (bWasSuccessful)
{
// 세션 생성 성공
}
else
{
// 세션 생성 실패
}
}
void USteamTestGameInstance::OnFindSessionComplete(bool bWasSuccessful)
{
OnFindSessionCompleteDelegate.Unbind();
for (auto Result : SessionSearch->SearchResults)
{
// 결과 처리
}
}
void USteamTestGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
APlayerController* const PlayerController = GetFirstLocalPlayerController();
FString TravelURL;
if (PlayerController && Sessions->GetResolvedConnectString(SessionName, TravelURL))
{
PlayerController->ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute);
}
}
}
}