UE5 Online Subsystem 기초
Online Subsystem이란
섹션 제목: “Online Subsystem이란”UE5의 Online Subsystem(OSS)은 Steam, Epic Online Services, Xbox Live, PlayStation Network 등 다양한 멀티플레이어 플랫폼 서비스를 통일된 인터페이스로 추상화합니다. 플랫폼을 교체해도 게임 코드는 변경하지 않아도 됩니다.
모듈 설정
섹션 제목: “모듈 설정”PublicDependencyModuleNames.AddRange(new string[]{ "OnlineSubsystem", "OnlineSubsystemUtils"});
DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam");; DefaultEngine.ini[OnlineSubsystem]DefaultPlatformService=Steam
[OnlineSubsystemSteam]bEnabled=trueSteamDevAppId=480 ; Spacewar 테스트 AppID세션 생성
섹션 제목: “세션 생성”#pragma once#include "OnlineSubsystem.h"#include "Interfaces/OnlineSessionInterface.h"#include "GameFramework/GameModeBase.h"#include "SessionManager.generated.h"
UCLASS()class ASessionManager : public AGameModeBase{ GENERATED_BODY()
public: void CreateSession(int32 MaxPlayers); void FindSessions(); void JoinSession(int32 SessionIndex); void DestroySession();
private: IOnlineSessionPtr _sessionInterface; TSharedPtr<FOnlineSessionSearch> _sessionSearch;
// 델리게이트 핸들 FDelegateHandle _createSessionHandle; FDelegateHandle _findSessionsHandle; FDelegateHandle _joinSessionHandle;
void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful); void OnFindSessionsComplete(bool bWasSuccessful); void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);};#include "SessionManager.h"#include "OnlineSubsystemUtils.h"
void ASessionManager::CreateSession(int32 MaxPlayers){ IOnlineSubsystem* OSS = IOnlineSubsystem::Get(); if (!OSS) return;
_sessionInterface = OSS->GetSessionInterface(); if (!_sessionInterface.IsValid()) return;
// 기존 세션 제거 if (_sessionInterface->GetNamedSession(NAME_GameSession)) _sessionInterface->DestroySession(NAME_GameSession);
// 세션 설정 FOnlineSessionSettings Settings; Settings.NumPublicConnections = MaxPlayers; Settings.bIsLANMatch = OSS->GetSubsystemName() == "NULL"; Settings.bUsesPresence = true; Settings.bAllowJoinInProgress = false; Settings.bShouldAdvertise = true; Settings.bUseLobbiesIfAvailable = true;
// 커스텀 데이터 추가 Settings.Set(FName("MapName"), FString("Desert_01"), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
// 델리게이트 등록 _createSessionHandle = _sessionInterface->OnCreateSessionCompleteDelegates.AddUObject( this, &ASessionManager::OnCreateSessionComplete);
_sessionInterface->CreateSession(0, NAME_GameSession, Settings);}
void ASessionManager::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful){ _sessionInterface->OnCreateSessionCompleteDelegates.Remove(_createSessionHandle);
if (bWasSuccessful) { UE_LOG(LogTemp, Log, TEXT("Session created: %s"), *SessionName.ToString()); // 맵 이동 (서버 트래블) GetWorld()->ServerTravel("/Game/Maps/GameMap?listen"); } else { UE_LOG(LogTemp, Error, TEXT("Failed to create session")); }}세션 검색
섹션 제목: “세션 검색”void ASessionManager::FindSessions(){ IOnlineSubsystem* OSS = IOnlineSubsystem::Get(); if (!OSS) return;
_sessionInterface = OSS->GetSessionInterface();
_sessionSearch = MakeShared<FOnlineSessionSearch>(); _sessionSearch->MaxSearchResults = 20; _sessionSearch->bIsLanQuery = OSS->GetSubsystemName() == "NULL"; _sessionSearch->QuerySettings.Set( SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
_findSessionsHandle = _sessionInterface->OnFindSessionsCompleteDelegates.AddUObject( this, &ASessionManager::OnFindSessionsComplete);
_sessionInterface->FindSessions(0, _sessionSearch.ToSharedRef());}
void ASessionManager::OnFindSessionsComplete(bool bWasSuccessful){ _sessionInterface->OnFindSessionsCompleteDelegates.Remove(_findSessionsHandle);
if (!bWasSuccessful || !_sessionSearch.IsValid()) return;
const auto& Results = _sessionSearch->SearchResults; UE_LOG(LogTemp, Log, TEXT("Found %d sessions"), Results.Num());
for (int32 i = 0; i < Results.Num(); ++i) { const auto& Result = Results[i]; FString SessionId = Result.GetSessionIdStr(); int32 Ping = Result.PingInMs; int32 OpenSlots = Result.Session.NumOpenPublicConnections;
// 커스텀 데이터 읽기 FString MapName; Result.Session.SessionSettings.Get(FName("MapName"), MapName);
UE_LOG(LogTemp, Log, TEXT("[%d] ID:%s Ping:%dms Slots:%d Map:%s"), i, *SessionId, Ping, OpenSlots, *MapName); }}세션 참가
섹션 제목: “세션 참가”void ASessionManager::JoinSession(int32 SessionIndex){ if (!_sessionInterface.IsValid() || !_sessionSearch.IsValid()) return;
const auto& Results = _sessionSearch->SearchResults; if (!Results.IsValidIndex(SessionIndex)) return;
_joinSessionHandle = _sessionInterface->OnJoinSessionCompleteDelegates.AddUObject( this, &ASessionManager::OnJoinSessionComplete);
_sessionInterface->JoinSession(0, NAME_GameSession, Results[SessionIndex]);}
void ASessionManager::OnJoinSessionComplete( FName SessionName, EOnJoinSessionCompleteResult::Type Result){ _sessionInterface->OnJoinSessionCompleteDelegates.Remove(_joinSessionHandle);
if (Result == EOnJoinSessionCompleteResult::Success) { // 서버 주소 가져와서 클라이언트 트래블 FString TravelURL; if (_sessionInterface->GetResolvedConnectString(NAME_GameSession, TravelURL)) { APlayerController* PC = GetWorld()->GetFirstPlayerController(); if (PC) { PC->ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute); } } } else { FString ErrorMsg; switch (Result) { case EOnJoinSessionCompleteResult::SessionIsFull: ErrorMsg = "세션이 가득 찼습니다"; break; case EOnJoinSessionCompleteResult::SessionDoesNotExist: ErrorMsg = "세션이 존재하지 않습니다"; break; default: ErrorMsg = "참가 실패"; break; } UE_LOG(LogTemp, Error, TEXT("JoinSession failed: %s"), *ErrorMsg); }}친구 목록 조회
섹션 제목: “친구 목록 조회”void ASessionManager::GetFriendsList(){ IOnlineSubsystem* OSS = IOnlineSubsystem::Get(); if (!OSS) return;
IOnlineFriendsPtr FriendsInterface = OSS->GetFriendsInterface(); if (!FriendsInterface.IsValid()) return;
// 친구 목록 읽기 요청 FriendsInterface->ReadFriendsList( 0, EFriendsLists::ToString(EFriendsLists::Default), FOnReadFriendsListComplete::CreateLambda( [FriendsInterface](int32 LocalUserNum, bool bWasSuccessful, const FString& ListName, const FString& Error) { if (!bWasSuccessful) return;
TArray<TSharedRef<FOnlineFriend>> Friends; FriendsInterface->GetFriendsList(LocalUserNum, ListName, Friends);
for (const auto& Friend : Friends) { FString Name = Friend->GetDisplayName(); bool Online = Friend->GetPresence().bIsOnline; FString Status = Friend->GetPresence().StatusStr;
UE_LOG(LogTemp, Log, TEXT("Friend: %s [%s] %s"), *Name, Online ? TEXT("Online") : TEXT("Offline"), *Status); } } ) );}플레이어 ID 처리
섹션 제목: “플레이어 ID 처리”// 로컬 플레이어 UniqueId 가져오기void GetLocalPlayerId(){ IOnlineSubsystem* OSS = IOnlineSubsystem::Get(); if (!OSS) return;
IOnlineIdentityPtr IdentityInterface = OSS->GetIdentityInterface(); if (!IdentityInterface.IsValid()) return;
// 로그인 상태 확인 ELoginStatus::Type Status = IdentityInterface->GetLoginStatus(0); if (Status != ELoginStatus::LoggedIn) { UE_LOG(LogTemp, Warning, TEXT("Player not logged in")); return; }
TSharedPtr<const FUniqueNetId> UserId = IdentityInterface->GetUniquePlayerId(0); if (UserId.IsValid()) { FString UserIdStr = UserId->ToString(); FString DisplayName = IdentityInterface->GetPlayerNickname(0);
UE_LOG(LogTemp, Log, TEXT("Player: %s (%s)"), *DisplayName, *UserIdStr); }}| 인터페이스 | 역할 |
|---|---|
IOnlineSession | 세션 생성·검색·참가·삭제 |
IOnlineFriends | 친구 목록, 친구 초대 |
IOnlineIdentity | 로그인, 플레이어 ID |
IOnlineLeaderboards | 리더보드 읽기·쓰기 |
IOnlineAchievements | 업적 조회·해제 |
OSS의 모든 작업은 비동기 델리게이트 기반입니다. 델리게이트 핸들을 저장했다가 콜백 완료 후 반드시 Remove해야 메모리 누수를 방지할 수 있습니다.