クラウドセーブを使用して機能フラグを実装する
注釈:本資料はAI技術を用いて翻訳されています。
概要
AccelByte Gaming Services (AGS) のクラウドセーブを使用すると、ゲームのアップデートを必要とせずに、特定の機能をリモートで有効または無効にすることができます。このガイドでは、これらの機能フラグをゲームに実装し、AGS 管理ポータルでオン/オフを切り替える方法を説明します。
前提条件
AGS Online Subsystem (OSS) バージョン 0.12.0 および AGS Game SDK Unreal 24.7.0 以降がインストールされている必要があります。
また、ゲームレコードでのゲーム内設定をセットアップする必要があります。
このガイドでは、Unreal Engine v5.1 で Byte Wars を使用しますので、一緒に進めたい場合は必要なファイルをクローンしてインストールしてください。Unreal Engine Byte Wars の GitHub はこちらにあり、Byte Wars Unreal でクラウドセーブを起動して実行するためのチュートリアルはこちらにあります。
ゲームレコードのクラウドセーブを実装する
このセクションでは、ゲームレコードのクラウドセーブを実装する手順を説明します。
プロジェクトの .ini ファイルにゲーム設定フラグを作成します。この例では、EnableMatchmaking と EnableStore フラグを作成し、DefaultEngine.ini ファイルで true に設定します。
[GameConfig]
EnableMatchmaking=true
EnableStore=true
Byte Wars モジュールを使用している場合、ゲームレコードはまだ実装されていません。実装するには、次の手順に従ってください。
CloudSaveSubsystem.h に次のコードを追加します:
public:
void GetGameRecord(const APlayerController* PlayerController, const FString& RecordKey, const FOnGetCloudSaveRecordComplete& OnGetRecordComplete);
private:
void OnGetGameRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FAccelByteModelsGameRecord& GameRecord, const FOnGetCloudSaveRecordComplete OnGetRecordComplete);
FDelegateHandle OnGetGameRecordCompletedDelegateHandle;
このコードを CloudSaveSubsystem.cpp の UCloudSaveSubsystem::GetGameRecord 宣言に追加します:
void UCloudSaveSubsystem::GetGameRecord(const APlayerController* PlayerController, 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 = PlayerController->GetLocalPlayer();
ensure(LocalPlayer != nullptr);
int32 LocalUserNum = LocalPlayer->GetControllerId();
OnGetGameRecordCompletedDelegateHandle = CloudSaveInterface->AddOnGetGameRecordCompletedDelegate_Handle(LocalUserNum, FOnGetGameRecordCompletedDelegate::CreateUObject(this, &ThisClass::OnGetGameRecordComplete, OnGetRecordComplete));
CloudSaveInterface->GetGameRecord(LocalUserNum, RecordKey);
}
このコードを CloudSaveSubsystem.cpp の UCloudSaveSubsystem::OnGetGameRecordComplete 宣言に追加します:
void UCloudSaveSubsystem::OnGetGameRecordComplete(int32 LocalUserNum, const FOnlineError& Result, const FString& Key, const FAccelByteModelsGameRecord& GameRecord, const FOnGetCloudSaveRecordComplete OnGetRecordComplete)
{
FJsonObject RecordResult;
if (Result.bSucceeded)
{
RecordResult = GameRecord.Value.JsonObject.ToSharedRef().Get();
UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Success to get game record."));
}
else
{
UE_LOG_CLOUDSAVE_ESSENTIALS(Log, TEXT("Failed to get game record. Message: %s"), *Result.ErrorMessage.ToString());
}
CloudSaveInterface->ClearOnGetGameRecordCompletedDelegate_Handle(LocalUserNum, OnGetGameRecordCompletedDelegateHandle);
OnGetRecordComplete.ExecuteIfBound(Result.bSucceeded, RecordResult);
}
後でクラウドセーブから取得する際に使用するゲーム設定フラグ用に、以下の定義文字列を作成します。
#define GAME_CONFIG_KEY FString(TEXT("GameConfig"))
#define ENABLE_MATCHMAKING_KEY FString(TEXT("enableMatchmaking"))
#define ENABLE_STORE_KEY FString(TEXT("enableStore"))
ゲーム設定を実装する
次に、ゲーム設定を実装します。先ほど作成したゲーム設定値を DefaultEngine.ini から次のコードを使用して取得できます:
bool bEnableMatchmaking;
GConfig->GetBool(TEXT("GameConfig"), TEXT("EnableMatchmaking"), bEnableMatchmaking, GEngineIni);
bool bEnableStore;
GConfig->GetBool(TEXT("GameConfig"), TEXT("EnableStore"), bEnableStore, GEngineIni);
また、常に DefaultEngine.ini ファイルから読み取る代わりに、ゲーム設定値を変数に保存することもできます。
このコードを AccelByteWarsGameInstance.h に追加します:
public:
void SetEnableMatchmaking(bool InValue);
void SetEnableStore(bool InValue);
bool GetEnableMatchmaking();
bool GetEnableStore();
private:
bool bEnableMatchmaking = false;
bool bEnableStore = false;
次に、このコードを AccelByteWarsGameInstance.cpp に追加します:
void UAccelByteWarsGameInstance::SetEnableMatchmaking(bool InValue)
{
bEnableMatchmaking = InValue;
}
void UAccelByteWarsGameInstance::SetEnableStore(bool InValue)
{
bEnableStore = InValue;
}
bool UAccelByteWarsGameInstance::GetEnableMatchmaking()
{
return bEnableMatchmaking;
}
bool UAccelByteWarsGameInstance::GetEnableStore()
{
return bEnableStore;
}
これで、DefaultEngine.ini ファイルからゲーム設定値を、ゲームインスタンスで作成した新しい値に保存できます。Byte Wars を使用している場合は、LoginWidget.cpp の ULoginWidget::NativeOnActivated() に追加できます。
bool bEnableMatchmaking;
GConfig->GetBool(TEXT("GameConfig"), TEXT("EnableMatchmaking"), bEnableMatchmaking, GEngineIni);
GameInstance->SetEnableMatchmaking(bEnableMatchmaking);
bool bEnableStore;
GConfig->GetBool(TEXT("GameConfig"), TEXT("EnableStore"), bEnableStore, GEngineIni);
GameInstance->SetEnableStore(bEnableStore);
AGS 管理ポータルで作成したゲームレコードを取得するには、UCloudSaveSubsystem::GetGameRecord を呼び出すことができますが、プレイヤーが AGS にログインした後に呼び出すようにしてください。
Byte Wars では、これを LoginWidget クラスに追加できます。LoginWidget.h に次を追加します。
#include "Storage/CloudSaveEssentials/CloudSaveSubsystem.h"
protected:
void OnLoadGameConfigFromCloud(const APlayerController* PlayerController, FSimpleDelegate OnComplete);
private:
UCloudSaveSubsystem* CloudSaveSubsystem;
LoginWidget.cpp で、先ほど作成した CloudSaveSubsystem から GetGameRecord を呼び出します:
void ULoginWidget::OnLoadGameConfigFromCloud(const APlayerController* PlayerController, FSimpleDelegate OnComplete)
{
if (!PlayerController)
{
UE_LOG_CLOUDSAVE_ESSENTIALS(Warning, TEXT("Cannot get game config from Cloud Save. Player Controller is null."));
return;
}
// Get game config from Cloud Save.
CloudSaveSubsystem = GameInstance->GetSubsystem<UCloudSaveSubsystem>();
CloudSaveSubsystem->GetGameRecord(
PlayerController,
GAME_CONFIG_KEY, //FString with value of GameConfig
FOnGetCloudSaveRecordComplete::CreateWeakLambda(this, [this, OnComplete](bool bWasSuccessful, FJsonObject& Result)
{
UE_LOG_CLOUDSAVE_ESSENTIALS(Warning, TEXT("Get game config from Cloud Save was successful: %s"), bWasSuccessful ? TEXT("True") : TEXT("False"));
// Update the local game options based on the Cloud Save record.
if (bWasSuccessful)
{
// Get game config from DefaultEngine.ini
bool bEnableMatchmaking = Result.GetBoolField(ENABLE_MATCHMAKING_KEY); // FString with value of enableMatchmaking
GConfig->SetBool(TEXT("GameConfig"), TEXT("EnableMatchmaking"), bEnableMatchmaking, GEngineIni);
GameInstance->SetEnableMatchmaking(bEnableMatchmaking);
bool bEnableStore = Result.GetBoolField(ENABLE_STORE_KEY); //FString with value of enableStore
GConfig->SetBool(TEXT("GameConfig"), TEXT("EnableStore"), bEnableStore, GEngineIni);
GameInstance->SetEnableStore(bEnableStore);
}
OnComplete.ExecuteIfBound();
})
);
}
次のコードを使用してゲーム設定値を取得できます:
bool bEnableMatchmaking = Result.GetBoolField(ENABLE_MATCHMAKING_KEY);
bool bEnableStore = Result.GetBoolField(ENABLE_STORE_KEY);
次に、DefaultEngine.ini に保存できます。これはランタイムキャッシュのみであるため、実際のファイルは変更されません。
GConfig->SetBool(TEXT("GameConfig"), TEXT("EnableMatchmaking"), bEnableMatchmaking, GEngineIni);
GConfig->SetBool(TEXT("GameConfig"), TEXT("EnableStore"), bEnableStore, GEngineIni);
後で使用するために、ゲームインスタンスに保存します。
GameInstance->SetEnableMatchmaking(bEnableMatchmaking);
GameInstance->SetEnableStore(bEnableStore);
フリーフォームプッシュ通知を追加する
ゲーム設定の変更を通知するロビー通知のリスナーを作成する必要があります。まず、AGS 管理ポータルでトピックを作成します。この例では、GAMECONFIG_CHANGE トピックを作成します。AGS 管理ポータルでゲームのネームスペースを開き、ソーシャル > プッシュ通知 > トピックに移動します。+ 新しいトピックをクリックします。トピック名を GAMECONFIG_CHANGE として入力し、説明を追加します。
ゲームプロジェクトでは、通知のリスナーを追加する必要があります。まず、ApiClient を取得する必要があります。このコードを AuthEssentialsSubsystem.h に追加します:
public:
AccelByte::FApiClientPtr GetApiClient();
private:
AccelByte::FApiClientPtr ApiClient;
このコードを AuthEssentialsSubsystem.cpp に追加します:
AccelByte::FApiClientPtr UAuthEssentialsSubsystem::GetApiClient()
{
if (!ApiClient.IsValid())
{
UE_LOG_AUTH_ESSENTIALS(Warning, TEXT("Api Client not valid"));
return nullptr;
}
return ApiClient;
}
ログインが成功したときに、UAuthEssentialsSubsystem::OnLoginComplete で ApiClient を設定します:
if (bLoginWasSuccessful)
{
UE_LOG_AUTH_ESSENTIALS(Log, TEXT("Login user successful."));
ApiClient = IdentityInterface->GetApiClient(LocalUserNum);
if (!ApiClient.IsValid())
{
UE_LOG_AUTH_ESSENTIALS(Warning, TEXT("Cannot get the Api Client"));
}
}
else
次に、LoginWidget.h で通知用の関数デリゲートを作成します:
void OnMessageNotif(const FAccelByteModelsNotificationMessage& InMessage);
そして、LoginWidget.cpp にその宣言を追加します。
void ULoginWidget::OnMessageNotif(const FAccelByteModelsNotificationMessage& InMessage)
{
if (InMessage.Topic == "GAMECONFIG_CHANGE")
{
// do hide the corresponding button
}
}
ユーザーログインが完了した後、LoginWidget.cpp の ULoginWidget::OnLoginComplete でデリゲート関数を設定します:
const AccelByte::FApiClientPtr ApiClient = AuthSubsystem->GetApiClient();
// listen to Message Notif Lobby
const AccelByte::Api::Lobby::FMessageNotif Delegate = AccelByte::Api::Lobby::FMessageNotif::CreateUObject(this, &ULoginWidget::OnMessageNotif);
if (!ApiClient.IsValid())
{
UE_LOG_AUTH_ESSENTIALS(Warning, TEXT("Cannot get the Api Client"));
return;
}
ApiClient->Lobby.SetMessageNotifDelegate(Delegate);
プッシュ通知を送信するには、AGS 管理ポータルのソーシャル > プッシュ通知 > テンプレートに移動し、フリーフォームを送信をクリックします。

UI ボタンを処理する
Byte Wars プロジェクトでは、メインメニューの UI ボタンを処理するものがありません。まず追加する必要があるので、AccelByteWarsGameInstance.h に戻り、このコードを追加します:
public:
void SetPlayOnlineButton(UUserWidget* InButton);
void SetStoreButton(UUserWidget* InButton);
void SetPlayOnlineButtonVisibility(ESlateVisibility InValue);
void SetStoreButtonVisibility(ESlateVisibility InValue);
private:
UUserWidget* ButtonPlayOnline;
UUserWidget* ButtonStore;
次に、AccelByteWarsGameInstance.cpp でその関数の宣言を作成します。
void UAccelByteWarsGameInstance::SetPlayOnlineButton(UUserWidget* InButton)
{
ButtonPlayOnline = InButton;
}
void UAccelByteWarsGameInstance::SetStoreButton(UUserWidget* InButton)
{
ButtonStore = InButton;
}
void UAccelByteWarsGameInstance::SetPlayOnlineButtonVisibility(ESlateVisibility InValue)
{
if (!ButtonPlayOnline)
{
GAMEINSTANCE_LOG("Failed to Set Play Online Button Visibility, the button is not set");
return;
}
ButtonPlayOnline->SetVisibility(InValue);
}
void UAccelByteWarsGameInstance::SetStoreButtonVisibility(ESlateVisibility InValue)
{
if (!ButtonPlayOnline)
{
GAMEINSTANCE_LOG("Failed to Set Store Button Visibility, the button is not set");
return;
}
ButtonStore->SetVisibility(InValue);
}
その後、AccelByteWarsActivatableWidget.cpp の UAccelByteWarsActivatableWidget::GenerateEntryButton にこのコードを追加して、Play Online と Store ボタンを設定します:
if (EntryWidgetClassName == "W_PlayOnline_C")
{
GameInstance->SetPlayOnlineButton(Button.Get());
}
if (EntryWidgetClassName == "W_Store_C")
{
GameInstance->SetStoreButton(Button.Get());
}
LoginWidget.h で、設定に基づいて対応するボタンを表示または非表示にするゲーム設定更新を処理する関数デリゲートを作成します:
public:
void UpdateConfig();
private:
FSimpleDelegate OnGetGameRecordComplete;
LoginWidget.cpp にその宣言を追加します。
void ULoginWidget::UpdateConfig()
{
if (GameInstance->GetEnableMatchmaking())
{
GameInstance->SetPlayOnlineButtonVisibility(ESlateVisibility::Visible);
}
else
{
GameInstance->SetPlayOnlineButtonVisibility(ESlateVisibility::Collapsed);
}
if (GameInstance->GetEnableStore())
{
GameInstance->SetStoreButtonVisibility(ESlateVisibility::Visible);
}
else
{
GameInstance->SetStoreButtonVisibility(ESlateVisibility::Collapsed);
}
}
これで、OnGetGameRecordComplete を UpdateConfig にバインドできます。ULoginWidget::NativeOnActivated にこのコードを追加します:
OnGetGameRecordComplete.BindUObject(this, &ULoginWidget::UpdateConfig);
次に、先ほど追加した OnLoadGameConfigFromCloud 関数を LoginWidget.cpp の ULoginWidget::OnLoginComplete に呼び出して、すべてをまとめます。プレイヤーが正常にログインしたときのために、このコードを追加します:
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
ensure(PlayerController);
OnLoadGameConfigFromCloud(PlayerController, OnGetGameRecordComplete);
そして、ULoginWidget::OnMessageNotif にこのコードを追加します:
if (InMessage.Topic == "GAMECONFIG_CHANGE")
{
OnLoadGameConfigFromCloud(GetOwningPlayer(), OnGetGameRecordComplete);
}
結果を確認する
ゲームを実行して結果を確認します。AGS 管理ポータルで作成したゲーム設定を変更して、Play Online および/または Store ボタンを有効または無効にできます。DefaultEngine.ini を介してゲーム設定のデフォルト値を指定できます。
[GameConfig]
EnableMatchmaking=true
EnableStore=true
または、ゲームレコードのクラウドセーブとフリーフォームプッシュ通知を使用して、その場で値を変更できます。

これは、マッチメイキングとストアが有効になっている Byte Wars のメインメニューです:

これは、ゲーム設定からマッチメイキングが無効になっている Byte Wars のメインメニューです:

これは、ゲーム設定からストアが無効になっている Byte Wars のメインメニューです。

そして、これは、ゲーム設定からマッチメイキングとストアが無効になっている Byte Wars のメインメニューです。
