メインコンテンツまでスキップ

クラウドセーブに OSS を使用する - クラウドセーブ - (Unreal Engine モジュール)

Last updated on May 30, 2024

Unwrap the Subsystem

In this tutorial, you will learn how to implement AccelByte Gaming Services (AGS) Cloud Save to save and fetch game options for music and sound effects (SFX) volumes. In Byte Wars, there is a Game Instance Subsystem defined in the CloudSaveSubsystem class. This subsystem acts as a wrapper to set and get any Player Record from AGS Cloud Save. Player Record is one of the record types that are available in AGS.

Here is the definition of record types supported by AGS:

  • Player Record: a record type to save player data, such as game options and preferences.
  • Game Record: a record type to save game data, such as game announcements, event configurations, etc.

You will set up a subsystem called CloudSaveSubsystem_Starter, which is the starter version of the CloudSaveSubsystem class.

What's in the Starter Pack

The CloudSaveSubsystem_Starter is available in the Resources section and consists of the following:

  • Header file: /Source/AccelByteWars/TutorialModules/Storage/CloudSaveEssentials/CloudSaveSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Storage/CloudSaveEssentials/CloudSaveSubsystem_Starter.cpp

The CloudSaveSubsystem_Starter class has several functionalities provided. If you open the CloudSaveSubsystem_Starter class CPP file, you will find a function called Initialize(). In this function is a defined variable called CloudSaveInterface.

void UCloudSaveSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);

// Get Online Subsystem and make sure it's valid.
const FOnlineSubsystemAccelByte* Subsystem = static_cast<const FOnlineSubsystemAccelByte*>(Online::GetSubsystem(GetWorld()));
if (!ensure(Subsystem))
{
UE_LOG_CLOUDSAVE_ESSENTIALS(Warning, TEXT("The online subsystem is invalid. Please make sure OnlineSubsystemAccelByte is enabled and DefaultPlatformService under [OnlineSubsystem] in the Engine.ini set to AccelByte."));
return;
}

// Grab the reference of the AccelByte Identity Interface and make sure it's valid.
CloudSaveInterface = StaticCastSharedPtr<FOnlineCloudSaveAccelByte>(Subsystem->GetCloudSaveInterface());
if (!ensure(CloudSaveInterface.IsValid()))
{
UE_LOG_CLOUDSAVE_ESSENTIALS(Warning, TEXT("Cloud Save interface is not valid."));
return;
}

UTutorialModuleDataAsset* TutorialModule = UTutorialModuleUtility::GetTutorialModuleDataAsset(FPrimaryAssetId{ "TutorialModule:CLOUDSAVEESSENTIALS" }, this, true);
if (TutorialModule && TutorialModule->IsActiveAndDependenciesChecked() && TutorialModule->IsStarterMode())
{
BindDelegates();
}
}

The CloudSaveInterface is a variable of type FOnlineCloudSaveAccelBytePtr interface. The FOnlineCloudSaveAccelBytePtr is part of AGS Online Subsystem (OSS), and you can use this interface to access AGS Cloud Save functionalities.

Implement Cloud Save with AGS OSS

In this section, you will implement Cloud Save using AGS OSS to store game options to and fetch game options from a Player Record.

  1. Open the CloudSaveSubsystem_Starter class Header file and declare the functions below. Each of these functions correspond to an action performed on a Player Record. SetPlayerRecord() will create or update an existing record. GetPlayerRecord() will retrieve the data associated with the record key provided. DeletePlayerRecord() will remove the stored record. You also declare corresponding response functions for each action.

    public:
    void SetPlayerRecord(const APlayerController* PC, const FString& RecordKey, const FJsonObject& RecordData, const FOnSetCloudSaveRecordComplete& OnSetRecordComplete);
    void GetPlayerRecord(const APlayerController* PC, const FString& RecordKey, const FOnGetCloudSaveRecordComplete& OnGetRecordComplete);
    void DeletePlayerRecord(const APlayerController* PC, const FString& RecordKey, const FOnDeleteCloudSaveRecordComplete& OnDeleteRecordComplete);

    private:
    void OnSetPlayerRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FOnSetCloudSaveRecordComplete OnSetRecordComplete);
    void OnGetPlayerRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FAccelByteModelsUserRecord& UserRecord, const FOnGetCloudSaveRecordComplete OnGetRecordComplete);
    void OnDeletePlayerRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FOnDeleteCloudSaveRecordComplete OnDeleteRecordComplete);
  2. Create the function definitions starting with SetPlayerRecord(). Open the CloudSaveSubsystem_Starter class CPP file and add the code below. This will send a request to set the Player Record data with the JSON data provided under the record key provided. When the request completes, the OnSetPlayerRecordComplete() method will be called.

    void UCloudSaveSubsystem_Starter::SetPlayerRecord(const APlayerController* PC, const FString& RecordKey, const FJsonObject& RecordData, const FOnSetCloudSaveRecordComplete& OnSetRecordComplete)
    {
    if (!ensure(CloudSaveInterface.IsValid()))
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Warning, TEXT("Cloud Save interface is not valid."));
    return;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    ensure(LocalPlayer != nullptr);
    int32 LocalUserNum = LocalPlayer->GetControllerId();

    // Create Player Record or update it if it already exists.
    OnSetPlayerRecordCompletedDelegateHandle = CloudSaveInterface->AddOnReplaceUserRecordCompletedDelegate_Handle(LocalUserNum, FOnReplaceUserRecordCompletedDelegate::CreateUObject(this, &ThisClass::OnSetPlayerRecordComplete, OnSetRecordComplete));
    CloudSaveInterface->ReplaceUserRecord(LocalUserNum, RecordKey, RecordData);
    }
  3. Define the OnSetPlayerRecordComplete() function. In the CPP file, add the code below. This will execute the OnSetRecordComplete delegate. This delegate is used to inform the player if the record was saved successfully, or if there was an error. You will use this delegate later.

    void UCloudSaveSubsystem_Starter::OnSetPlayerRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FOnSetCloudSaveRecordComplete OnSetRecordComplete)
    {
    if (Result.bSucceeded)
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Success to set player record."));
    }
    else
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Failed to set player record. Message: %s"), *Result.ErrorMessage.ToString());
    }

    CloudSaveInterface->ClearOnReplaceUserRecordCompletedDelegate_Handle(LocalUserNum, OnSetPlayerRecordCompletedDelegateHandle);
    OnSetRecordComplete.ExecuteIfBound(Result.bSucceeded);
    }
  4. Create the function definition for the GetPlayerRecord() with the code below. This will send a request to retrieve the stored JSON data for the Player Record under the record key provided. When the request completes, the OnGetPlayerRecordComplete() will be called.

    void UCloudSaveSubsystem_Starter::GetPlayerRecord(const APlayerController* PC, const FString& RecordKey, const FOnGetCloudSaveRecordComplete& OnGetRecordComplete)
    {
    if (!ensure(CloudSaveInterface.IsValid()))
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Warning, TEXT("Cloud Save interface is not valid."));
    return;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    ensure(LocalPlayer != nullptr);
    int32 LocalUserNum = LocalPlayer->GetControllerId();

    OnGetPlayerRecordCompletedDelegateHandle = CloudSaveInterface->AddOnGetUserRecordCompletedDelegate_Handle(LocalUserNum, FOnGetUserRecordCompletedDelegate::CreateUObject(this, &ThisClass::OnGetPlayerRecordComplete, OnGetRecordComplete));
    CloudSaveInterface->GetUserRecord(LocalUserNum, RecordKey);
    }
  5. Define the OnGetPlayerRecordComplete() function with the code below. Similar to the OnSetPlayerRecordComplete() function, this will notify the player if the record retrieval was successful or not. Additionally, it will send the record data through the delegate for the game code to read and use. You will use this delegate later.

    void UCloudSaveSubsystem_Starter::OnGetPlayerRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FAccelByteModelsUserRecord& UserRecord, const FOnGetCloudSaveRecordComplete OnGetRecordComplete)
    {
    FJsonObject RecordResult;

    if (Result.bSucceeded)
    {
    RecordResult = UserRecord.Value.JsonObject.ToSharedRef().Get();
    UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Success to get player record."));
    }
    else
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Failed to get player record. Message: %s"), *Result.ErrorMessage.ToString());
    }

    CloudSaveInterface->ClearOnGetUserRecordCompletedDelegate_Handle(LocalUserNum, OnGetPlayerRecordCompletedDelegateHandle);
    OnGetRecordComplete.ExecuteIfBound(Result.bSucceeded, RecordResult);
    }
  6. Define the DeletePlayerRecord() function with the code below. This will send a request to delete the Player Record associated with the record key provided. When the request completes, OnDeletePlayerRecordComplete() will be called.

    void UCloudSaveSubsystem_Starter::DeletePlayerRecord(const APlayerController* PC, const FString& RecordKey, const FOnDeleteCloudSaveRecordComplete& OnDeleteRecordComplete)
    {
    if (!ensure(CloudSaveInterface.IsValid()))
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Warning, TEXT("Cloud Save interface is not valid."));
    return;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    ensure(LocalPlayer != nullptr);
    int32 LocalUserNum = LocalPlayer->GetControllerId();

    OnDeletePlayerRecordCompletedDelegateHandle = CloudSaveInterface->AddOnDeleteUserRecordCompletedDelegate_Handle(LocalUserNum, FOnDeleteUserRecordCompletedDelegate::CreateUObject(this, &ThisClass::OnDeletePlayerRecordComplete, OnDeleteRecordComplete));
    CloudSaveInterface->DeleteUserRecord(LocalUserNum, RecordKey);
    }
  7. Define the OnDeletePlayerRecordComplete() function with the code below. This will execute the OnDeleteRecordComplete delegate to inform the player that the deletion was successful.

    void UCloudSaveSubsystem_Starter::OnDeletePlayerRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FOnDeleteCloudSaveRecordComplete OnDeleteRecordComplete)
    {
    if (Result.bSucceeded)
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Success to delete player record."));
    }
    else
    {
    UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Failed to delete player record. Message: %s"), *Result.ErrorMessage.ToString());
    }

    CloudSaveInterface->ClearOnDeleteUserRecordCompletedDelegate_Handle(LocalUserNum, OnDeletePlayerRecordCompletedDelegateHandle);
    OnDeleteRecordComplete.ExecuteIfBound(Result.bSucceeded);
    }
  8. Build the Byte Wars project and make sure there are no compile errors.

Resources