개요
이 페이지에서 다루는 내용
이 페이지에서는 게임에서 일부 온라인 서비스 함수 기능을 구성하고 구현하는 방법을 안내합니다. 이 튜토리얼에서는 온라인 서비스 플러그인이 지원하는 모든 인터페이스 구현에 대해서가 아니라, 온라인 서비스 플러그인의 일반적인 프로그래밍 패턴을 보여주는 보다 간단한 인터페이스인 온라인 서비스 타이틀 파일 인터페이스에 대해 안내합니다. 이 페이지에서는 다음과 같은 방법에 대해 안내합니다.
- 온라인 서비스에서 로컬 플레이어에 대한 정보를 가져오는 방법
- 백엔드 서비스에서 타이틀 파일을 얻는 방법
- 게임에서 타이틀 파일 콘텐츠를 표시하는 방법
이 가이드에서는 OnlineSample 이라는 프로젝트와 Online Services Null 플러그인을 사용합니다.
시작하기 전에
게임에서 사용하기 위한 온라인 서비스 플러그인을 활성화하고 환경설정했는지 확인합니다. 아직 하지 않았다면, 온라인 서비스 플러그인 구성 및 환경설정 문서 페이지를 참조하세요.
할 일
먼저, 현재 로그인되어 있는 로컬 플레이어에 대한 정보를 얻습니다. 이 로컬 플레이어의 계정 ID는 모든 다른 온라인 서비스 플러그인 작업에 대한 파라미터로 사용됩니다. 이 정보를 가져오는 방법을 알게 되면 모든 다른 온라인 서비스 함수를 수행할 수 있습니다. 이 가이드에서는 Online Services Null 플러그인을 사용합니다. 사용자는 자동으로 Null 서비스에 등록되므로 로그인 함수를 호출할 필요가 없습니다. 따라서, 명시적인 로그인 호출은 필요 없지만, 온라인 서비스에서 로컬 사용자에 대한 온라인 정보는 가져와야 합니다.
다음으로, 백엔드 온라인 서비스에서 타이틀 파일을 얻습니다. 이 튜토리얼에서는 Online Services Null 플러그인을 사용하므로 타이틀 파일과 해당 콘텐츠가 엔진 환경설정에 추가됩니다. 타이틀 파일 인터페이스는 백엔드 온라인 서비스에서 타이틀 파일을 쿼리하고 읽는 작업을 처리합니다.
마지막으로, 화면에 타이틀 파일을 표시합니다. 이를 통해 파일을 제대로 가져왔다는 것을 시각적으로 확인할 수 있습니다.
환경설정
앞서 언급했듯이, 이 가이드에서는 Online Services Null 플러그인을 사용합니다. 이 플러그인은 온라인 서비스 구현을 테스트하고 디버깅하는 용도로 설계되었습니다. Null 서비스는 타이틀 파일을 저장하기 위한 백엔드 서비스를 제공하지 않습니다. 따라서 이 스토리지는 엔진 환경설정을 사용하여 시뮬레이션됩니다.
Null 플러그인에 타이틀 파일을 추가하는 절차는 다음과 같습니다.
- Visual Studio에서 프로젝트를 엽니다. 언리얼 에디터 내에서 툴(Tools) > Visual Studio 열기(Open Visual Studio) 로 이동하면 됩니다.
- Visual Studio 솔루션 탐색기에서 Games > [YOUR_GAME] > Config > DefaultEngine.ini 로 이동하여 프로젝트의
DefaultEngine.ini
파일을 엽니다. -
프로젝트의
DefaultEngine.ini
파일에 다음 내용을 추가합니다.DefaultEngine.ini
; Null Platform Configuration [OnlineServices.Null.TitleFile] +Files=(Name=StatusFile, Contents="Explore this virtual world with me!")
구성
게임 인스턴스 추가하기
온라인 서비스 플러그인 기능을 사용하려면 C++ 클래스를 생성하여 사용하려는 온라인 서비스를 구현해야 합니다. 이 튜토리얼에서는 게임 인스턴스(Game Instance) 클래스를 사용합니다. 대부분의 게임 프레임워크 클래스는 레벨 또는 맵 간에 다시 인스턴스화됩니다. 즉, 게임 프레임워크 클래스에 포함된 정보는 레벨이나 맵이 바뀌면 리셋되거나 사라집니다. 게임 인스턴스와 해당 서브시스템은 초기화부터 종료까지 게임의 전체 수명에 걸쳐 지속됩니다. 따라서 한 맵이나 레벨에서 다른 맵이나 레벨로 정보를 전달하는 데 도움이 되는 영구적인 구조체 역할을 할 수 있습니다. 이 길잡이에서 게임 인스턴스 클래스 이름은 OnlineSampleGameInstance 입니다.
게임 인스턴스 클래스를 추가하려면 언리얼 에디터에서 C++ 클래스 마법사를 사용하여 다음 정보로 새 C++ 클래스를 생성합니다.
- 클래스: Game Instance
- 이름: OnlineSampleGameInstance
- 경로: ../OnlineSample/Source/OnlineSample/GameInstance
언리얼 에디터는 언리얼 엔진 프로젝트 코드에 새 클래스를 추가하고 라이브 코딩 을 초기화합니다. 그러면 언리얼 에디터가 열려 있는 동안 새 클래스가 표시되도록 Visual Studio 코드가 리컴파일됩니다. 새 .cpp
및 .h
파일도 Visual Studio에서 열려야 새 클래스 파일에 코드를 추가할 준비가 된 것입니다.
Visual Studio에 솔루션 파일이 업데이트되었으며 프로젝트를 리로드해야 한다는 팝업 창이 표시될 수 있습니다. '모두 리로드(Reload All)'를 선택하여 Visual Studio에서 프로젝트를 리로드합니다. 시스템에서 'GameInstance/OnlineSampleGameInstance.h' include 파일을 열지 못해 빌드에 실패한 경우, 'OnlineSampleGameInstance.cpp'로 이동해 다음 줄을 편집합니다.
#include "GameInstance/OnlineSampleGameInstance.h"
다음으로 변경:
#include "OnlineSample/GameInstance/OnlineSampleGameInstance.h"
여기서 OnlineSample
은 프로젝트 이름입니다. 이 줄을 변경한 다음에는 언리얼 에디터로 돌아가 라이브 코딩으로 프로젝트를 리컴파일합니다. 이제 프로젝트에서 헤더 파일을 제대로 찾아 빌드에 성공할 것입니다.
프로젝트의 게임 인스턴스 클래스 지정하기
프로젝트의 게임 인스턴스 클래스를 생성한 다음에는 디폴트 클래스 대신 새로 생성한 게임 인스턴스 클래스를 사용하도록 엔진에 알려줘야 합니다.
프로젝트의 게임 인스턴스 클래스를 지정하는 절차는 다음과 같습니다.
- 언리얼 에디터로 이동합니다.
- 메뉴 바(Menu Bar)에서 편집(Edit) > 프로젝트 세팅(Project Settings) 을 선택합니다.
- 왼쪽에서 프로젝트(Project) > 맵 & 모드(Maps & Modes) 를 선택합니다.
- 게임 인스턴스 섹션을 찾아 위의 게임 인스턴스 추가하기 섹션에서 생성한 게임 인스턴스 클래스 를 선택합니다. 이 가이드의 클래스와 같은 이름을 사용하는 경우, OnlineSampleGameInstance 를 선택합니다.
이제 프로젝트에서 커스텀 게임 인스턴스 클래스를 기본적으로 사용합니다.
게임 인스턴스 서브시스템 추가하기
이 길잡이에서는 게임 인스턴스 서브시스템(Game Instance Subsystem) 을 사용하여 게임 인스턴스 구조체 내에 온라인 코드를 구성합니다. 프로그래밍 서브시스템을 사용하면 각각 특정 부분에 초점을 맞춘 모듈형 시스템으로 코드를 구성할 수 있습니다.
게임 인스턴스 서브시스템 클래스를 추가하려면 언리얼 에디터에서 C++ 클래스 마법사를 사용하여 다음 정보로 새 C++ 클래스를 생성합니다.
- 클래스: Game Instance Subsystem
- 이름: OnlineSampleOnlineSubsystem
- 경로: ../OnlineSample/Source/OnlineSample/GameInstance
언리얼 에디터는 언리얼 엔진 프로젝트 코드에 새 클래스를 추가하고 라이브 코딩을 초기화하고 프로젝트를 리컴파일합니다.
플레이어 컨트롤러 추가하기
플레이어 컨트롤러(Player Controller) 클래스는 게임 코드에서 게임을 플레이하는 실제 사람을 추상화한 것입니다. 이 클래스는 온라인 사용자 등록 및 타이틀 파일 읽기를 게임에서 호출하는 BeginPlay
함수를 제공합니다.
플레이어 컨트롤러 클래스를 추가하려면 언리얼 에디터에서 C++ 클래스 마법사를 사용하여 다음 정보로 새 C++ 클래스를 생성합니다.
- 클래스: Player Controller
- 이름: OnlineSamplePlayerController
- 경로: ../OnlineSample/Source/OnlineSample/Player
앞에서와 마찬가지로 언리얼 에디터는 언리얼 엔진 프로젝트 코드에 새 클래스를 추가하고 라이브 코딩 을 초기화하고 프로젝트를 리컴파일합니다.
코드 추가하기
이제 프로젝트의 구조체와 필요한 다양한 파일을 구성했으니, 다음 단계는 해당 함수 기능을 구현하는 것입니다. 이 섹션에는 프로젝트 구성 섹션에서 생성한 각 C++ 클래스의 헤더 파일과 소스 파일에 대한 샘플 코드가 있는 서브섹션과 기존 프로젝트 파일 편집을 위한 서브섹션이 포함되어 있습니다.
이러한 파일에는 관련된 오브젝트에 대해 자세히 알아보고, 로그에서 활동 및 문제를 추적하고, 오류를 진단하는 데 도움이 되는 코드 주석, 로깅 및 오류 처리가 포함되어 있습니다.
라이브 코딩을 사용하다가 실패 이유를 진단할 수 없는 '빌드 실패(Build failed)' 로그 메시지가 라이브 코딩 콘솔에 표시되는 경우, 언리얼 에디터를 닫고 대신 Visual Studio로 빌드해 보시기 바랍니다.
게임 인스턴스
게임 인스턴스와 해당 서브시스템은 초기화부터 종료까지 게임의 전체 수명에 걸쳐 지속됩니다. 즉, 같은 게임 인스턴스 오브젝트와 서브시스템 오브젝트뿐 아니라 해당 함수와 필드까지 게임 초기화부터 종료까지 존재합니다. 그래서 UI 메뉴, 싱글 플레이어 모드에서 친구와 채팅하든 멀티플레이어 모드에서 다른 사용자와 온라인 세션에 참여하든 온라인 기능에 액세스해야 할 때 온라인 서비스 플러그인 함수 기능을 저장하기 좋습니다. 또한, 게임 인스턴스는 게임 인스턴스 서브시스템의 매니저 역할도 합니다. 특히, 게임 인스턴스는 이 튜토리얼에서 구현되는 온라인 서비스 게임 인스턴스 서브시스템(OnlineSampleOnlineSubsystem
)의 매니저 역할을 합니다.
OnlineSampleGameInstance.h
OnlineSampleGameInstance.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "OnlineSampleGameInstance.generated.h"
// 포워드 선언 클래스
class AOnlineSamplePlayerController;
class UObject;
DECLARE_LOG_CATEGORY_EXTERN(LogGameInstance, Log, All);
/**
* 게임 인스턴스 서브시스템을 관리할 커스텀 게임 인스턴스 클래스
*/
UCLASS()
class ONLINESAMPLE_API UOnlineSampleGameInstance : public UGameInstance
{
GENERATED_BODY()
protected:
/** 게임 시작 시 게임 인스턴스를 초기화하기 위해 호출됨 */
virtual void Init() override;
/** 게임 종료 시 게임 인스턴스를 종료하기 위해 호출됨 */
virtual void Shutdown() override;
public:
/** 게임 인스턴스 오브젝트를 초기화하기 위해 호출됨 */
UOnlineSampleGameInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/** 기본 플레이어 컨트롤러를 얻기 위해 호출됨 */
AOnlineSamplePlayerController* GetPrimaryPlayerController() const;
};
OnlineSampleGameInstance.cpp
OnlineSampleGameInstance.cpp
#include "OnlineSampleGameInstance.h"
#include "OnlineSample/Player/OnlineSamplePlayerController.h"
DEFINE_LOG_CATEGORY(LogGameInstance);
/// <summary>
/// 게임 인스턴스 오브젝트를 초기화합니다
/// </summary>
void UOnlineSampleGameInstance::Init()
{
UE_LOG(LogGameInstance, Log, TEXT("OnlineSampleGameInstance initialized."));
Super::Init();
}
/// <summary>
/// 게임 인스턴스 오브젝트를 종료합니다
/// </summary>
void UOnlineSampleGameInstance::Shutdown()
{
UE_LOG(LogGameInstance, Log, TEXT("OnlineSampleGameInstance shutdown."));
Super::Shutdown();
}
/// <summary>
/// 게임 인스턴스 오브젝트를 초기화합니다
/// </summary>
/// <param name="ObjectInitializer"></param>
UOnlineSampleGameInstance::UOnlineSampleGameInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
/// <summary>
/// 기본 플레이어 컨트롤러에 대한 레퍼런스를 얻습니다
/// </summary>
/// <returns>AOnlineSamplePlayerController pointer</returns>
AOnlineSamplePlayerController* UOnlineSampleGameInstance::GetPrimaryPlayerController() const
{
return Cast<AOnlineSamplePlayerController>(Super::GetPrimaryPlayerController(false));
}
게임 인스턴스 서브시스템
온라인 서비스 플러그인 함수 기능에 대한 모든 구현 디테일은 게임 인스턴스 서브시스템 안에 있습니다. 앞서 언급했듯이, 게임 인스턴스는 초기화부터 종료까지 게임의 전체 수명 동안 지속됩니다. 게임 인스턴스 서브시스템은 게임 인스턴스의 수명 전체에 걸쳐 액세스해야 하는 코드를 별도의 시스템으로 구성하는 데 도움이 됩니다. 이 프로젝트에서는 온라인 서비스 플러그인 관련 코드를 'OnlineSampleOnlineSubsystem'이라는 게임 인스턴스 서브시스템으로 구성합니다. 이 예시에서는 타이틀 파일 인터페이스 함수 기능을 구현합니다.
구현 디테일
온라인 서비스 플러그인에는 이 페이지에서 OnlineSampleOnlineSubsystem
으로 구현하는 일반 패턴이 포함되어 있습니다.
쿼리 및 얻기
온라인 서비스 플러그인에서 일반 패턴은 먼저 인터페이스에 정보를 쿼리 하는 것입니다. 그런 다음 이 정보를 인터페이스를 통해 캐시했다가 이 정보를 얻어야 할 때 캐시에서 정보를 얻습니다 . 쿼리는 해당 쿼리 및 얻기 작업의 비동기 부분으로 구성됩니다. OnlineSampleOnlineSubsystem
샘플 코드에 이 패턴에 대한 몇 가지 예시가 있습니다. 특히, 다음 예시를 살펴볼 수 있습니다.
RetrieveTitleFile
및 해당HandleEnumerateFiles
호출
OnlineSampleOnlineSubsystem.h
OnlineSampleOnlineSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Online/OnlineServices.h"
#include "Online/OnlineAsyncOpHandle.h"
#include "Online/TitleFile.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "OnlineSampleOnlineSubsystem.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogOnlineSampleOnlineSubsystem, Log, All);
/**
*
*/
UCLASS()
class ONLINESAMPLE_API UOnlineSampleOnlineSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
////////////////////////////////////////////////////////
/// OnlineSampleOnlineSubsystem Init/Deinit
/** 서브시스템 생성 여부를 결정하기 위해 호출됨 */
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
/** 게임 인스턴스 서브시스템을 초기화하기 위해 호출됨 */
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
/** 게임 인스턴스 서브시스템의 초기화를 해제하기 위해 호출됨 */
virtual void Deinitialize() override;
/** 게임 인스턴스 서브시스템에 로컬 온라인 사용자를 등록하기 위해 호출됨 */
void RegisterLocalOnlineUser(FPlatformUserId PlatformUserId);
/** 이 플랫폼 사용자 ID에 대한 온라인 사용자 정보를 얻기 위해 호출됨 */
TObjectPtr<UOnlineUserInfo> GetOnlineUserInfo(FPlatformUserId PlatformUserId);
/** 백엔드 서비스에서 게임의 타이틀 파일을 읽고 콘텐츠를 반환하기 위해 호출됨 */
FString ReadTitleFile(FString Filename, FPlatformUserId PlatformUserId);
protected:
struct FOnlineServicesInfo
{
/** 온라인 서비스 포인터 - 이 포인터를 통해 인터페이스에 액세스합니다 */
UE::Online::IOnlineServicesPtr OnlineServices = nullptr;
/** 인터페이스 포인터입니다 */
UE::Online::IAuthPtr AuthInterface = nullptr;
UE::Online::ITitleFilePtr TitleFileInterface = nullptr;
/** 온라인 서비스 구현입니다 */
UE::Online::EOnlineServices OnlineServicesType = UE::Online::EOnlineServices::None;
/** 타이틀 파일 콘텐츠입니다 */
UE::Online::FTitleFileContents TitleFileContent;
/** 구조체를 초기 세팅으로 리셋합니다 */
void Reset()
{
OnlineServices.Reset();
AuthInterface.Reset();
TitleFileInterface.Reset();
OnlineServicesType = UE::Online::EOnlineServices::None;
}
};
////////////////////////////////////////////////////////
/// 온라인 서비스 초기화
/** 관련 온라인 서비스 포인터가 포함된 내부 구조체에 대한 포인터입니다 */
FOnlineServicesInfo* OnlineServicesInfoInternal = nullptr;
/** 온라인 서비스 및 인터페이스 포인터를 초기화하기 위해 호출됨 */
void InitializeOnlineServices();
////////////////////////////////////////////////////////
/// 타이틀 파일
/** 온라인 서비스에서 타이틀 파일을 얻기 위해 호출됨 */
void RetrieveTitleFile(FString Filename, FPlatformUserId PlatformUserId);
////////////////////////////////////////////////////////
/// 이벤트
/** EnumerateFiles 비동기 이벤트를 처리하기 위해 호출됨 */
void HandleEnumerateFiles(const UE::Online::TOnlineResult<UE::Online::FTitleFileEnumerateFiles>& EnumerateFilesResult, TObjectPtr<UOnlineUserInfo> OnlineUser, FString Filename);
/** ReadFile 비동기 이벤트를 처리하기 위해 호출됨 */
void HandleReadFile(const UE::Online::TOnlineResult<UE::Online::FTitleFileReadFile>& ReadFileResult, FString Filename);
////////////////////////////////////////////////////////
/// 온라인 사용자 정보
/** 이 사용자에 대한 UOnlineUserInfo 오브젝트를 생성하기 위해 호출됨 */
TObjectPtr<UOnlineUserInfo> CreateOnlineUserInfo(int32 LocalUserIndex, FPlatformUserId PlatformUserId, UE::Online::FAccountId AccountId, UE::Online::EOnlineServices Services);
/** CreateOnlineUserInfo로 생성 후 OnlineUserInfos 맵에 사용자를 등록하고 사용자를 추가하기 위해 호출됨 */
TObjectPtr<UOnlineUserInfo> CreateAndRegisterUserInfo(int32 LocalUserIndex, FPlatformUserId PlatformUserId, UE::Online::FAccountId AccountId, UE::Online::EOnlineServices Services);
/** 각 로컬 사용자에 대한 정보입니다 */
TMap<FPlatformUserId, TObjectPtr<UOnlineUserInfo>> OnlineUserInfos;
/** 액세스하기 위해 UOnlineUserInfo 클래스를 friend 선언합니다 */
friend UOnlineUserInfo;
};
UCLASS()
class ONLINESAMPLE_API UOnlineUserInfo : public UObject
{
GENERATED_BODY()
public:
UOnlineUserInfo();
////////////////////////////////////////////////////////
/// 온라인 사용자 필드
int32 LocalUserIndex = -1;
FPlatformUserId PlatformUserId;
UE::Online::FAccountId AccountId;
UE::Online::EOnlineServices Services = UE::Online::EOnlineServices::None;
////////////////////////////////////////////////////////
/// 온라인 사용자 로깅/디버깅 함수
/** OnlineUserInfo를 스트링으로 가져오기 위해 호출됨 */
const FString DebugInfoToString();
friend UOnlineSampleOnlineSubsystem;
};
OnlineSampleOnlineSubsystem.cpp
OnlineSampleOnlineSubsystem.cpp
#include "OnlineSampleOnlineSubsystem.h"
#include "Online/CoreOnline.h"
#include "Online/OnlineResult.h"
#include "Online/OnlineAsyncOpHandle.h"
#include "Online/OnlineError.h"
#include "Online/OnlineServices.h"
#include "Online/Auth.h"
#include "Online/TitleFile.h"
DEFINE_LOG_CATEGORY(LogOnlineSampleOnlineSubsystem);
/// <summary>
///이 서브시스템을 생성할지 여부입니다. 단순화를 위해 서브시스템은 오직
/// 서버가 아닌 클라이언트 및 독립형 게임에서만 생성됩니다. 이 함수는
/// 종종 서버 또는 클라이언트로 서브시스템 생성을 제한하는 데 사용됩니다.
/// 사용 전에 서브시스템 null 체크를 해야 합니다!
/// </summary>
/// <param name="Outer"></param>
/// <returns>이 서브시스템을 생성할지 여부를 정하는 boolean입니다</returns>
bool UOnlineSampleOnlineSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
#if UE_SERVER
return false;
#else
return Super::ShouldCreateSubsystem(Outer);
#endif
}
/// <summary>
/// 게임 인스턴스가 초기화된 뒤에 호출되는 초기화입니다
/// </summary>
/// <param name="Collection">게임 인스턴스에 의해 초기화되는 서브시스템의 컬렉션입니다</param>
void UOnlineSampleOnlineSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
UE_LOG(LogTemp, Log, TEXT("OnlineSampleOnlineSubsystem initialized."));
Super::Initialize(Collection);
// 온라인 서비스를 초기화합니다
InitializeOnlineServices();
}
/// <summary>
/// 게임 인스턴스가 초기화 해제되거나 종료되기 전에 호출되는 초기화 해제입니다
/// </summary>
void UOnlineSampleOnlineSubsystem::Deinitialize()
{
UE_LOG(LogTemp, Log, TEXT("OnlineSampleOnlineSubsystem deinitialized."));
// 이벤트 핸들 바인딩을 해제하고 구조체 정보를 리셋합니다
OnlineServicesInfoInternal->Reset();
// 부모 클래스 초기화를 해제합니다
Super::Deinitialize();
}
/// <summary>
/// 비동기 EnumerateFiles 함수를 처리합니다. 실패하면 로그를 기록합니다.
/// 성공하면 HandleReadFile이 처리하는 비동기 ReadFile 함수를 호출합니다.
/// </summary>
/// <param name="EnumerateFilesResult">EnumerateFiles 시도의 결과입니다</param>
/// <param name="OnlineUser">타이틀 파일을 쿼리한 사용자입니다</param>
/// <param name="Filename">사용자가 쿼리한 파일 이름입니다</param>
void UOnlineSampleOnlineSubsystem::HandleEnumerateFiles(const UE::Online::TOnlineResult<UE::Online::FTitleFileEnumerateFiles>& EnumerateFilesResult, TObjectPtr<UOnlineUserInfo> OnlineUser, FString Filename)
{
using namespace UE::Online;
if (EnumerateFilesResult.IsOk())
{
FTitleFileGetEnumeratedFiles::Params GetParams;
GetParams.LocalAccountId = OnlineUser->AccountId;
TOnlineResult<FTitleFileGetEnumeratedFiles> GetResult = OnlineServicesInfoInternal->TitleFileInterface->GetEnumeratedFiles(MoveTemp(GetParams));
if (GetResult.IsOk())
{
FTitleFileGetEnumeratedFiles::Result& CachedFiles = GetResult.GetOkValue();
int32 FileIndex = CachedFiles.Filenames.Find(Filename);
if (FileIndex == INDEX_NONE)
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Title File \"%s\" not found!"), *Filename);
}
else
{
FTitleFileReadFile::Params ReadParams;
ReadParams.LocalAccountId = OnlineUser->AccountId;
ReadParams.Filename = Filename;
OnlineServicesInfoInternal->TitleFileInterface->ReadFile(MoveTemp(ReadParams)).OnComplete(this, &ThisClass::HandleReadFile, Filename);
}
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Get Title File Error: %s"), *GetResult.GetErrorValue().GetLogString());
}
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Enum Title File Error: %s"), *EnumerateFilesResult.GetErrorValue().GetLogString());
}
}
/// <summary>
/// 비동기 ReadFile 함수를 처리합니다. 실패하면 로그를 기록합니다.
/// 성공하면 사용자의 TitleFileContent를 채웁니다.
/// </summary>
/// <param name="ReadFileResult">ReadFile 시도의 결과입니다</param>
/// <param name="Filename">읽을 파일 이름입니다</param>
void UOnlineSampleOnlineSubsystem::HandleReadFile(const UE::Online::TOnlineResult<UE::Online::FTitleFileReadFile>& ReadFileResult, FString Filename)
{
using namespace UE::Online;
if (ReadFileResult.IsOk())
{
const FTitleFileReadFile::Result& ReadFileResultValue = ReadFileResult.GetOkValue();
OnlineServicesInfoInternal->TitleFileContent = *ReadFileResultValue.FileContents;
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Read Title File Error: %s"), *ReadFileResult.GetErrorValue().GetLogString());
}
}
/// <summary>
/// 온라인 서비스를 초기화합니다.
/// 서비스에 대한 포인터를 가져옵니다
/// 인터페이스에 대한 포인터를 가져옵니다
/// 이벤트 핸들을 추가합니다
/// 포인터 유효성을 확인합니다
/// </summary>
void UOnlineSampleOnlineSubsystem::InitializeOnlineServices()
{
OnlineServicesInfoInternal = new FOnlineServicesInfo();
// 서비스 포인터를 초기화합니다
OnlineServicesInfoInternal->OnlineServices = UE::Online::GetServices();
check(OnlineServicesInfoInternal->OnlineServices.IsValid());
// 서비스 타입을 검증합니다
OnlineServicesInfoInternal->OnlineServicesType = OnlineServicesInfoInternal->OnlineServices->GetServicesProvider();
if (OnlineServicesInfoInternal->OnlineServices.IsValid())
{
// 인터페이스 포인터를 초기화합니다
OnlineServicesInfoInternal->AuthInterface = OnlineServicesInfoInternal->OnlineServices->GetAuthInterface();
check(OnlineServicesInfoInternal->AuthInterface.IsValid());
OnlineServicesInfoInternal->TitleFileInterface = OnlineServicesInfoInternal->OnlineServices->GetTitleFileInterface();
check(OnlineServicesInfoInternal->TitleFileInterface.IsValid());
}
else {
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Error: Failed to initialize services."));
}
}
/// <summary>
/// EnumerateFiles, GetEnumeratedFiles 및 ReadFile. 이 구현은 람다 함수를 사용하여
/// OnComplete 콜백을 처리합니다.
/// </summary>
/// <param name="Filename">읽을 파일입니다</param>
/// <param name="PlatformUserId">파일을 가져올 대상 사용자입니다</param>
void UOnlineSampleOnlineSubsystem::RetrieveTitleFile(FString Filename, FPlatformUserId PlatformUserId)
{
using namespace UE::Online;
FTitleFileEnumerateFiles::Params EnumParams;
FAccountId LocalAccountId;
TObjectPtr<UOnlineUserInfo> OnlineUser;
if (OnlineUserInfos.Contains(PlatformUserId))
{
OnlineUser = *OnlineUserInfos.Find(PlatformUserId);
LocalAccountId = OnlineUser->AccountId;
EnumParams.LocalAccountId = LocalAccountId;
if (OnlineServicesInfoInternal->TitleFileInterface.IsValid())
{
(OnlineServicesInfoInternal->TitleFileInterface)->EnumerateFiles(MoveTemp(EnumParams)).OnComplete(this, &ThisClass::HandleEnumerateFiles, OnlineUser, Filename);
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Title File Interface pointer invalid."));
}
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Could not find user with Platform User Id: %d"), PlatformUserId.GetInternalId());
}
}
/// <summary>
/// 로컬 레지스트리 OnlineUserInfos에 온라인 사용자를 등록합니다
/// </summary>
/// <param name="PlatformUserId">등록할 사용자의 플랫폼 사용자 ID입니다</param>
void UOnlineSampleOnlineSubsystem::RegisterLocalOnlineUser(FPlatformUserId PlatformUserId)
{
using namespace UE::Online;
FAuthGetLocalOnlineUserByPlatformUserId::Params GetUserParams;
GetUserParams.PlatformUserId = PlatformUserId;
if (OnlineServicesInfoInternal->AuthInterface.IsValid())
{
TOnlineResult<FAuthGetLocalOnlineUserByPlatformUserId> AuthGetResult = OnlineServicesInfoInternal->AuthInterface->GetLocalOnlineUserByPlatformUserId(MoveTemp(GetUserParams));
if (AuthGetResult.IsOk())
{
FAuthGetLocalOnlineUserByPlatformUserId::Result& LocalOnlineUser = AuthGetResult.GetOkValue();
TSharedRef<FAccountInfo> UserAccountInfo = LocalOnlineUser.AccountInfo;
FAccountInfo UserAccountInfoContent = *UserAccountInfo;
if (!OnlineUserInfos.Contains(UserAccountInfoContent.PlatformUserId))
{
UOnlineUserInfo* NewUser = CreateAndRegisterUserInfo(UserAccountInfoContent.AccountId.GetHandle(), PlatformUserId, UserAccountInfoContent.AccountId, UserAccountInfoContent.AccountId.GetOnlineServicesType());
UE_LOG(LogOnlineSampleOnlineSubsystem, Log, TEXT("Local User Registered: %s"), *(NewUser->DebugInfoToString()));
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Log, TEXT("Local User with platform user id %d already registered."), PlatformUserId.GetInternalId());
}
}
else
{
FOnlineError ErrorResult = AuthGetResult.GetErrorValue();
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Get Local Online User Error: %s"), *ErrorResult.GetLogString());
}
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Auth Interface pointer invalid."));
}
}
/// <summary>
/// 타이틀 파일을 가져와 파일의 콘텐츠를 읽습니다.
/// </summary>
/// <param name="Filename">읽을 파일입니다</param>
/// <param name="PlatformUserId">파일을 읽을 대상 사용자입니다</param>
/// <returns>파일 이름 콘텐츠가 있는 FileString입니다</returns>
FString UOnlineSampleOnlineSubsystem::ReadTitleFile(FString Filename, FPlatformUserId PlatformUserId)
{
using namespace UE::Online;
RetrieveTitleFile(Filename, PlatformUserId);
FTitleFileContents FileContents = OnlineServicesInfoInternal->TitleFileContent;
FString FileString = FString(FileContents.Num(), UTF8_TO_TCHAR(FileContents.GetData()));
UE_LOG(LogOnlineSampleOnlineSubsystem, Log, TEXT("Reading Title File: %s"), *Filename);
return FileString;
}
/// <summary>
/// 주어진 정보로 UOnlineUserInfo 오브젝트를 생성합니다.
/// </summary>
/// <param name="LocalUserIndex"></param>
/// <param name="PlatformUserId"></param>
/// <param name="AccountId"></param>
/// <param name="Services">사용자가 등록되는 온라인 서비스입니다.</param>
/// <returns>NewUser에 대한 오브젝트 포인터입니다</returns>
TObjectPtr<UOnlineUserInfo> UOnlineSampleOnlineSubsystem::CreateOnlineUserInfo(int32 LocalUserIndex, FPlatformUserId PlatformUserId, UE::Online::FAccountId AccountId, UE::Online::EOnlineServices Services)
{
TObjectPtr<UOnlineUserInfo> NewUser = NewObject<UOnlineUserInfo>(this);
NewUser->LocalUserIndex = LocalUserIndex;
NewUser->PlatformUserId = PlatformUserId;
NewUser->AccountId = AccountId;
NewUser->Services = Services;
return NewUser;
}
/// <summary>
/// CreateOnlineUserInfo를 호출하여 UOnlineUserInfo 오브젝트를 생성한 다음
/// 로컬 레지스트리 OnlineUserInfos에 사용자를 등록합니다
/// </summary>
/// <param name="LocalUserIndex"></param>
/// <param name="PlatformUserId"></param>
/// <param name="AccountId"></param>
/// <param name="Services">사용자가 등록되는 온라인 서비스입니다.</param>
/// <returns>NewUser에 대한 오브젝트 포인터입니다</returns>
TObjectPtr<UOnlineUserInfo> UOnlineSampleOnlineSubsystem::CreateAndRegisterUserInfo(int32 LocalUserIndex, FPlatformUserId PlatformUserId, UE::Online::FAccountId AccountId, UE::Online::EOnlineServices Services)
{
TObjectPtr<UOnlineUserInfo> NewUser = CreateOnlineUserInfo(LocalUserIndex, PlatformUserId, AccountId, Services);
OnlineUserInfos.Add(PlatformUserId, NewUser);
return NewUser;
}
/// <summary>
/// 제공된 플랫폼 사용자 ID에 대한 UOnlineUserInfo를 얻습니다.
/// </summary>
/// <param name="PlatformUserId">가져올 사용자 ID입니다</param>
/// <returns>OnlineUser에 대한 오브젝트 포인터입니다</returns>
TObjectPtr<UOnlineUserInfo> UOnlineSampleOnlineSubsystem::GetOnlineUserInfo(FPlatformUserId PlatformUserId)
{
TObjectPtr<UOnlineUserInfo> OnlineUser;
if (OnlineUserInfos.Contains(PlatformUserId))
{
OnlineUser = *OnlineUserInfos.Find(PlatformUserId);
}
else
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Error, TEXT("Could not find user with Platform User Id: %d"), PlatformUserId.GetInternalId());
OnlineUser = nullptr;
}
return OnlineUser;
}
/// <summary>
/// UOnlineUserInfo 오브젝트에 대한 생성자입니다
/// </summary>
UOnlineUserInfo::UOnlineUserInfo()
{
}
/// <summary>
/// UOnlineUserInfo에 대한 디버그 스트링을 반환합니다
/// </summary>
/// <returns>UOnlineUserInfo의 스트링 표현입니다</returns>
const FString UOnlineUserInfo::DebugInfoToString()
{
int32 UserIndex = this->LocalUserIndex;
int32 PlatformId = this->PlatformUserId;
TArray<FStringFormatArg> FormatArgs;
FormatArgs.Add(FStringFormatArg(UserIndex));
FormatArgs.Add(FStringFormatArg(PlatformId));
return FString::Format(TEXT("LocalUserNumber: {0}, PlatformUserId: {1}"), FormatArgs);
}
플레이어 컨트롤러
플레이어 컨트롤러 클래스를 통해 온라인 서비스에 플레이어를 등록하고 백엔드 서비스에서 타이틀 파일을 읽습니다.
OnlineSamplePlayerController.h
OnlineSamplePlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "OnlineSamplePlayerController.generated.h"
/**
*
*/
UCLASS()
class ONLINESAMPLE_API AOnlineSamplePlayerController : public APlayerController
{
GENERATED_BODY()
public:
AOnlineSamplePlayerController();
protected:
/** 플레이가 시작되면 호출됨 */
virtual void BeginPlay();
/** 플레이가 종료되면 호출됨 */
virtual void EndPlay(EEndPlayReason::Type EndReason);
};
OnlineSamplePlayerController.cpp
OnlineSamplePlayerController.cpp
#include "OnlineSamplePlayerController.h"
#include "OnlineSample/GameInstance/OnlineSampleGameInstance.h"
#include "OnlineSample/GameInstance/OnlineSampleOnlineSubsystem.h"
AOnlineSamplePlayerController::AOnlineSamplePlayerController()
{
}
void AOnlineSamplePlayerController::BeginPlay()
{
Super::BeginPlay();
////////////////////////////////////////////////
/// 온라인 서비스
// 온라인 서비스에 이 플레이어를 등록합니다
UOnlineSampleGameInstance* GameInstance = Cast<UOnlineSampleGameInstance>(GetWorld()->GetGameInstance());
UOnlineSampleOnlineSubsystem* OnlineSubsystem = GameInstance->GetSubsystem<UOnlineSampleOnlineSubsystem>();
ULocalPlayer* LocalPlayer = Super::GetLocalPlayer();
if (LocalPlayer)
{
FPlatformUserId LocalPlayerPlatformUserId = LocalPlayer->GetPlatformUserId();
if (OnlineSubsystem) // null-check subsystem before access
{
UE_LOG(LogOnlineSampleOnlineSubsystem, Log, TEXT("Registering PlatformUserId: %d"), LocalPlayerPlatformUserId.GetInternalId());
OnlineSubsystem->RegisterLocalOnlineUser(LocalPlayerPlatformUserId);
// 타이틀 파일을 읽고 화면에 콘텐츠를 표시합니다
FString TitleFileContent = OnlineSubsystem->ReadTitleFile(FString("StatusFile"), LocalPlayerPlatformUserId);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Black, TitleFileContent);
}
}
}
///////////////////////////////////////////////
/// 다음 섹션...
}
void AOnlineSamplePlayerController::EndPlay(EEndPlayReason::Type EndReason)
{
Super::EndPlay(EndReason);
}
게임 모드 편집하기
이전 섹션에서 생성한 플레이어 컨트롤러 클래스를 사용하려면 <PROJECT_NAME>GameMode.cpp
라는 이름의 게임 모드 소스 파일도 편집해야 합니다.
OnlineSampleGameMode.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OnlineSampleGameMode.h"
#include "Player/OnlineSamplePlayerController.h"
#include "OnlineSampleCharacter.h"
#include "UObject/ConstructorHelpers.h"
AOnlineSampleGameMode::AOnlineSampleGameMode()
{
// 기본 폰 클래스를 에픽의 블루프린트 캐릭터로 설정합니다
static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"));
if (PlayerPawnBPClass.Class != NULL)
{
DefaultPawnClass = PlayerPawnBPClass.Class;
}
// 새 플레이어 컨트롤러 클래스를 할당합니다
PlayerControllerClass = AOnlineSamplePlayerController::StaticClass();
}
빌드
이제 프로젝트를 빌드할 준비가 됐습니다. 언리얼 에디터 내에서 Visual Studio를 열면, 언리얼 에디터에서 라이브 코딩을 사용하여 프로젝트의 C++을 컴파일하고 즉시 게임을 시작할 수 있습니다. 언리얼 에디터를 닫은 경우, Visual Studio를 사용하여 프로젝트를 빌드합니다. 자세한 내용은 언리얼 엔진에서 게임 프로젝트 컴파일하기 문서를 참조하세요.
테스트
현재 다음 작업을 완료했습니다.
- 온라인 서비스 플러그인을 활성화하고 환경설정했습니다.
- 프로젝트를 적절하게 구성했습니다.
- 온라인 서비스 플러그인 함수 기능을 구현하는 코드를 추가했습니다.
- 프로젝트 코드를 컴파일했습니다.
프로젝트를 성공적으로 컴파일했다면 프로젝트를 테스트할 준비가 된 것입니다. 프로젝트를 테스트하는 절차는 다음과 같습니다.
- 언리얼 에디터에서 내 프로젝트를 엽니다.
- 에디터에서 플레이(Play In Editor)를 시작하여 프로젝트를 테스트합니다.
프로젝트 테스트에 대한 자세한 내용은 언리얼 엔진에서의 플레이 및 시뮬레이션 문서를 참조하세요.
플레이 시작
플레이를 시작하면 다음과 비슷한 화면이 표시될 것입니다.

콘솔 명령
콘솔 명령으로도 현재 온라인 서비스 구현을 디버깅하고 테스트할 수 있습니다.

온라인 서비스 플러그인과 함께 콘솔 명령을 사용하는 방법에 대한 자세한 내용은 온라인 서비스 콘솔 명령 문서 페이지를 참조하세요.