Skip to content
UCIK Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Google Play services

Use Play Games, saved games, Integrity, reviews, and updates.

Complete Android setup and sign-in first. Create achievement and leaderboard IDs in Play Console and enable Saved Games if you use snapshots.

Achievements and leaderboards

Unlock an achievement
Unlock an achievement
Preparing your graph…

Loading the interactive viewer.

Add achievement steps
Add achievement steps
Preparing your graph…

Loading the interactive viewer.

Submit a score
Submit a score
Preparing your graph…

Loading the interactive viewer.

Read top scores
Read top scores
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: PlayGamesServices, BetideCore.

#include "Nodes/Achievements/GPIK_Achievements_AsyncFunction.h"
#include "Nodes/Leaderboards/GPIK_Leaderboards_AsyncFunction.h"

void UnlockGoogleAchievement(UObject *WorldContext, const FString &AchievementId,
                             UGPIK_UnlockAchievement::FNativeCallback Completion)
{
    auto *Request = UGPIK_UnlockAchievement::UnlockAchievement(WorldContext, AchievementId);
    if (!Request)
        return;
    Request->SetNativeCallback(MoveTemp(Completion));
    Request->Activate();
}

void SubmitGoogleScore(UObject *WorldContext, const FString &LeaderboardId, int64 Score,
                       UGPIK_SubmitScore::FNativeCallback Completion)
{
    auto *Request = UGPIK_SubmitScore::SubmitScore(WorldContext, LeaderboardId, Score);
    if (!Request)
        return;
    Request->SetNativeCallback(MoveTemp(Completion));
    Request->Activate();
}

void IncrementGoogleAchievement(UObject *WorldContext, const FString &AchievementId, int32 Steps,
                                const FScriptDelegate &OnIncremented,
                                const FScriptDelegate &OnFailed)
{
    auto *Request =
        UGPIK_IncrementAchievement::IncrementAchievement(WorldContext, AchievementId, Steps);
    if (!Request)
        return;
    Request->OnSuccess.Add(OnIncremented);
    Request->OnFailure.Add(OnFailed);
    Request->Activate();
}

void ReadGoogleTopScores(UObject *WorldContext, const FString &LeaderboardId,
                         UGPIK_LoadTopScores::FNativeCallback Completion)
{
    auto *Request = UGPIK_LoadTopScores::LoadTopScores(WorldContext, LeaderboardId,
                                                       EGPIK_LeaderboardTimeSpan::AllTime,
                                                       EGPIK_LeaderboardCollection::Public, 10);
    if (!Request)
        return;
    Request->SetNativeCallback(MoveTemp(Completion));
    Request->Activate();
}

Unlock and submit callbacks receive (bool bSuccess, const FString& Id, const FBetideError& Error). Top-score callbacks receive (bool bSuccess, const TArray<FGPIK_LeaderboardScore>& Scores, const FBetideError& Error). Bind increment delegates to UFUNCTION handlers with (const FGPIK_AchievementIncrementResult& Result, const FBetideError& Error). Use weak UObject captures when updating game objects.

Incremental achievements take a positive number of steps, not a percentage. Score ordering comes from the leaderboard configuration.

Need Node / C++ entry point
Current progress Load Achievements / UGPIK_LoadAchievements::LoadAchievements
Increase progress Increment Achievement / UGPIK_IncrementAchievement::IncrementAchievement
Top entries Load Top Scores / UGPIK_LoadTopScores::LoadTopScores
Player’s rank Load Player Score / UGPIK_LoadPlayerScore::LoadPlayerScore
Native UI Show Achievements UI / UGPIK_AchievementsLibrary::ShowAchievementsUI

The Google Crossplay progression provider supports achievements and leaderboards, not generic stats.

Saved games

Upload a SaveGame
Upload a SaveGame
Preparing your graph…

Loading the interactive viewer.

Download a SaveGame
Download a SaveGame
Preparing your graph…

Loading the interactive viewer.

#include "Nodes/SavedGames/GPIK_CloudSave_AsyncFunction.h"

bool SaveGoogleGame(UObject *WorldContext, USaveGame *SaveGame, const FScriptDelegate &OnSaved,
                    const FScriptDelegate &OnFailed)
{
    const TArray<uint8> Data = UGPIK_CloudSaveLibrary::SaveGameObjectToData(SaveGame);
    if (Data.IsEmpty())
        return false;
    auto *Request =
        UGPIK_SaveGame::SaveGame(WorldContext, TEXT("PlayerSave"), Data, TEXT("Player progress"));
    if (!Request)
        return false;
    Request->OnSuccess.Add(OnSaved);
    Request->OnFailure.Add(OnFailed);
    Request->Activate();
    return true;
}

void LoadGoogleGame(UObject *WorldContext, const FScriptDelegate &OnLoaded,
                    const FScriptDelegate &OnFailed)
{
    auto *Request = UGPIK_LoadGame::LoadGame(WorldContext, TEXT("PlayerSave"));
    if (!Request)
        return;
    Request->OnSuccess.Add(OnLoaded);
    Request->OnFailure.Add(OnFailed);
    Request->Activate();
}

USaveGame *DeserializeGoogleSave(const FGPIK_SavedGame &SavedGame)
{
    return UGPIK_CloudSaveLibrary::DataToSaveGameObject(SavedGame.Data);
}

Save handlers use (const FString& SnapshotName, const FBetideError& Error); load handlers use (const FGPIK_SavedGame& SavedGame, const FBetideError& Error). Bind them as UFUNCTION delegates before activation. SaveGoogleGame returning true means the request started. On load success, deserialize, check for nullptr, and cast to your SaveGame class; retain it in a UPROPERTY if needed beyond the callback.

Pass your populated SaveGame object to upload. Cast the downloaded object to your SaveGame class before applying its fields. Use a stable slot name and version your payload. Load Saved Games List lists snapshot metadata; Load Game from Cloud reads the bytes. In C++, use UGPIK_LoadSavedGamesList::LoadSavedGamesList and UGPIK_LoadGame::LoadGame.

The wrapper requests Google’s most recently modified conflict policy. It exposes no manual conflict-selection callback; do not rely on it to merge simultaneous progress. These snapshots stay in Play Games and are separate from Crossplay cloud saves.

Integrity, reviews, and updates

These Android services use separate modules and do not require a Play Games achievement or leaderboard setup.

Request a review
Request a review
Preparing your graph…

Loading the interactive viewer.

Start a flexible update
Start a flexible update
Preparing your graph…

Loading the interactive viewer.

Install a downloaded update
Install a downloaded update
Preparing your graph…

Loading the interactive viewer.

Add PlayInAppReviews, PlayInAppUpdates, BetideCore, and Engine for these examples.

Request a review

GoogleReviewGameInstance.h:

#pragma once
#include "Engine/GameInstance.h"
#include "InAppReviewsManager.h"
#include "PlayInAppReviews.h"
#include "GoogleReviewGameInstance.generated.h"

UCLASS()
class UGoogleReviewGameInstance : public UGameInstance
{
    GENERATED_BODY()
  public:
    void RequestPlayerReview()
    {
        if (auto *Manager = GetSubsystem<UInAppReviewsManager>())
        {
            FReviewOperationCompletedDelegate Completion;
            Completion.BindDynamic(this, &ThisClass::OnPrepared);
            Manager->RequestReviewFlow(Completion);
        }
    }

  private:
    UFUNCTION()
    void OnPrepared(EInAppReviewErrorCode Code, const FBetideError &Error)
    {
        if (Error.IsError())
        {
            UE_LOG(LogPlayInAppReviews, Warning, TEXT("%s"), *Error.Message);
            return;
        }
        if (auto *Manager = GetSubsystem<UInAppReviewsManager>())
        {
            FReviewOperationCompletedDelegate Completion;
            Completion.BindDynamic(this, &ThisClass::OnCompleted);
            Manager->LaunchReviewFlow(Completion);
        }
    }
    UFUNCTION()
    void OnCompleted(EInAppReviewErrorCode Code, const FBetideError &Error)
    {
        if (Error.IsError())
            UE_LOG(LogPlayInAppReviews, Warning, TEXT("%s"), *Error.Message);
        else
            UE_LOG(LogPlayInAppReviews, Log, TEXT("Review flow completed"));
    }
};
Start and complete a flexible update

GoogleUpdateGameInstance.h:

#pragma once
#include "Engine/GameInstance.h"
#include "InAppUpdatesManager.h"
#include "PlayInAppUpdates.h"
#include "GoogleUpdateGameInstance.generated.h"

UCLASS()
class UGoogleUpdateGameInstance : public UGameInstance
{
    GENERATED_BODY()
  public:
    void StartFlexibleUpdate()
    {
        if (auto *Manager = GetSubsystem<UInAppUpdatesManager>())
        {
            FRequestInfoOperationCompletedDelegate Completion;
            Completion.BindDynamic(this, &ThisClass::OnStartInfo);
            Manager->RequestInfo(Completion);
        }
    }
    void InstallDownloadedUpdate()
    {
        if (auto *Manager = GetSubsystem<UInAppUpdatesManager>())
        {
            FRequestInfoOperationCompletedDelegate Completion;
            Completion.BindDynamic(this, &ThisClass::OnInstallInfo);
            Manager->RequestInfo(Completion);
        }
    }

  private:
    UFUNCTION()
    void OnStartInfo(EAppUpdateErrorCode Code, UAppUpdateInfo *Info, const FBetideError &Error)
    {
        if (!CheckInfo(Info, Error))
            return;
        auto *Options =
            UAppUpdateOptions::CreateAppUpdateOptions(EAppUpdateType::AppUpdate_TYPE_FLEXIBLE);
        if (!Options || !Info->IsUpdateTypeAllowed(Options))
        {
            UE_LOG(LogPlayInAppUpdates, Log, TEXT("Flexible update is not available"));
            return;
        }
        if (auto *Manager = GetSubsystem<UInAppUpdatesManager>())
        {
            FUpdateOperationCompletedDelegate Completion;
            Completion.BindDynamic(this, &ThisClass::OnStarted);
            Manager->StartUpdate(Info, Options, Completion);
        }
    }
    UFUNCTION()
    void OnInstallInfo(EAppUpdateErrorCode Code, UAppUpdateInfo *Info, const FBetideError &Error)
    {
        if (!CheckInfo(Info, Error))
            return;
        const EAppUpdateStatus Status = Info->GetStatus();
        if (Status != EAppUpdateStatus::AppUpdate_DOWNLOADED)
        {
            UE_LOG(LogPlayInAppUpdates, Log, TEXT("Update status: %s"),
                   *UEnum::GetValueAsString(Status));
            return;
        }
        if (auto *Manager = GetSubsystem<UInAppUpdatesManager>())
        {
            FUpdateOperationCompletedDelegate Completion;
            Completion.BindDynamic(this, &ThisClass::OnInstalled);
            Manager->CompleteUpdate(Completion);
        }
    }
    UFUNCTION()
    void OnStarted(EAppUpdateErrorCode Code, const FBetideError &Error)
    {
        if (Error.IsError())
            UE_LOG(LogPlayInAppUpdates, Warning, TEXT("%s"), *Error.Message);
        else
            UE_LOG(LogPlayInAppUpdates, Log,
                   TEXT("Update flow completed; check download status before installing"));
    }
    UFUNCTION()
    void OnInstalled(EAppUpdateErrorCode Code, const FBetideError &Error)
    {
        if (Error.IsError())
            UE_LOG(LogPlayInAppUpdates, Warning, TEXT("%s"), *Error.Message);
        else
            UE_LOG(LogPlayInAppUpdates, Log, TEXT("Install request completed"));
    }
    static bool CheckInfo(UAppUpdateInfo *Info, const FBetideError &Error)
    {
        if (Error.IsError())
        {
            UE_LOG(LogPlayInAppUpdates, Warning, TEXT("%s"), *Error.Message);
            return false;
        }
        if (!Info)
        {
            UE_LOG(LogPlayInAppUpdates, Warning,
                   TEXT("Update info unavailable; use an Android Play build"));
            return false;
        }
        return true;
    }
};

Integrity: the current Request implementation ignores non-empty RequestHash values. Action-bound token verification needs that plugin fix first. Keep server-side hash checks; do not accept an unbound token as proof of a protected action.

Action-bound integrity token (blocked)
Prepare Integrity Token · Request

Blueprint example

Reviews: request the review flow, then launch it after the request succeeds. Completion does not prove that a review dialog appeared or that the player rated the game; Google applies display quotas. See review requirements.

Updates: request current update info, check that your chosen update type is allowed, then start it with Create App Update Options. The install example checks status once and completes only when it is Downloaded. Call it when the player chooses to restart, after starting the update in the same app session; the manager retains state from Start Update. Do not overlap info requests. Test through a supported Google Play installation path, as described in Google’s update testing guide.

Was this page helpful?