Skip to content

Conversation

@wickedcube
Copy link
Contributor

  1. Simple Node.js Backend for Recall API demo
  2. 'RECALL API' scripting defined symbol to toggle

@wickedcube wickedcube self-assigned this Nov 3, 2025
@gemini-code-assist
Copy link

Summary of Changes

Hello @wickedcube, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a fully functional demonstration of the Google Play Games Services Recall API. It establishes a Node.js backend to manage player data and persona linking, complemented by a Unity client that leverages this backend to showcase how players can effortlessly transfer their game progress between devices. The implementation focuses on providing a clear example of how to integrate and utilize the Recall API for persistent, cross-device player experiences.

Highlights

  • Recall API Demo Implementation: Introduced a complete, end-to-end demonstration of the Google Play Games Services (PGS) Recall API, enabling seamless cross-device progress saving and restoration without traditional login screens.
  • Node.js Backend Server: Added a new Node.js Express server (trivialkart-recall-server) that handles linking personas, creating player accounts, and updating player progress, integrating with Google's Recall API.
  • Unity Client Integration: Integrated the Recall API functionality into the Unity client, including a new PGSRecallManager script to manage session validation, account creation, and periodic progress updates with the backend server.
  • Conditional Compilation and Build Options: Implemented conditional compilation (#if RECALL_API) across several Unity scripts to toggle between the existing Cloud Save Manager and the new Recall API functionality, and added a new menu item in the Unity Editor to enable/disable the Recall API feature.
  • Documentation and Setup Guides: Provided detailed README.md files for both the Node.js server and the Unity client, outlining the prerequisites, setup steps, and the end-to-end demo flow for the Recall API.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a demonstration of the Google Play Games Services Recall API, including a Node.js backend server and corresponding Unity client logic. The implementation is well-structured, with clear separation between the old cloud save system and the new Recall API functionality using preprocessor directives. The addition of a custom editor menu to toggle the feature is a nice touch.

My main concerns are with the server's data persistence and some implementation details on the client side that could be made more robust. The server uses an in-memory map for player data, which means progress is lost on server restart, contradicting the "persistent progress-saving" mentioned in the documentation. On the client, hardcoded URLs and fragile GameObject.Find calls could be improved.

Overall, this is a great starting point for a Recall API demo. The feedback provided aims to improve its robustness and align the implementation with the documentation.

const { v4: uuidv4 } = require('uuid'); // Used for generating unique tokens
require('dotenv').config();

const playerDatabase = new Map();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The playerDatabase is an in-memory Map. This means any saved player progress will be lost when the server restarts. This is suitable for a very basic demo, but it contradicts the goal of demonstrating persistent progress saving across devices and sessions, as mentioned in the README. For a more robust demonstration, consider using a simple file-based database (like lowdb) or a lightweight SQL database (like sqlite3) to persist player data across server restarts.

Comment on lines +16 to +18
if (!fs.existsSync(KEY_FILE_PATH)) {
throw new Error(`Service account key file not found at path: ${KEY_FILE_PATH}`);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the KEY_FILE_PATH environment variable is not set, process.env.KEY_FILE_PATH will be undefined. Passing undefined to fs.existsSync() will cause a TypeError and crash the server on startup with a less-than-clear error message. It's better to explicitly check if the environment variable is set and provide a clear error message.

Suggested change
if (!fs.existsSync(KEY_FILE_PATH)) {
throw new Error(`Service account key file not found at path: ${KEY_FILE_PATH}`);
}
if (!KEY_FILE_PATH) {
throw new Error('The KEY_FILE_PATH environment variable is not set. Please check your .env file.');
}
if (!fs.existsSync(KEY_FILE_PATH)) {
throw new Error(`Service account key file not found at path: ${KEY_FILE_PATH}`);
}

if (recallAccess != null)
{
_currentRecallSessionId = recallAccess.sessionId;
Debug.Log($"[PGSRecallManager] Success! Received Session ID: {_currentRecallSessionId[..20]}...");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using the range operator [..20] to get a substring for logging can throw an ArgumentOutOfRangeException if the _currentRecallSessionId string is shorter than 20 characters. While session IDs are usually long, it's safer to guard against this possibility. This could crash the application.

                Debug.Log($"[PGSRecallManager] Success! Received Session ID: {_currentRecallSessionId.Substring(0, Math.Min(_currentRecallSessionId.Length, 20))}...");

if (recallAccess != null)
{
_currentRecallSessionId = recallAccess.sessionId;
Debug.Log($"[PGSRecallManager] Success! Received Session ID: {_currentRecallSessionId.Substring(0, 20)}...");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

_currentRecallSessionId.Substring(0, 20) will throw an ArgumentOutOfRangeException if the _currentRecallSessionId string has fewer than 20 characters, which can crash the application. You should ensure you don't try to access characters beyond the string's length.

                    Debug.Log($"[PGSRecallManager] Success! Received Session ID: {_currentRecallSessionId.Substring(0, Math.Min(_currentRecallSessionId.Length, 20))}...");

@@ -0,0 +1,162 @@
# PGS Recall API with a Unity Client & Node.js Server Demo

This guide will walk you through setting up a complete, end-to-end demonstration of the Google Play Games Services (PGS) Recall API. It has been updated to include a persistent progress-saving mechanism. The goal is to show how a player can start playing on one device, save their progress in near real-time, and then seamlessly continue with that exact progress on a new device without needing a traditional login screen. We'll use a local Node.js server to act as your game's backend and a Unity client to simulate the game.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The guide states that this demo "has been updated to include a persistent progress-saving mechanism." However, the Node.js server uses an in-memory Map as its database, which is not persistent and will lose all data upon restarting. This could be confusing for users following the guide. Please either update the server to use a persistent storage solution (e.g., a file-based DB like lowdb or a database like SQLite) or update the README to clarify that the persistence is only for the duration of the server's runtime.

Comment on lines +184 to +187
if (buildItem == _recallAPI && !_playGamesServicesItem.IsEnabled)
{
if (defineCount > 0)
{
defineString += ";";
}
Debug.Log(buildItem.GetDirective);
defineString += buildItem.GetDirective;
++defineCount;
continue;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This conditional check is redundant. The logic in TogglePlayGamesServicesAction already ensures that _recallAPI.IsEnabled is set to false when _playGamesServicesItem is disabled. Since buildItem.GetDirective will return null or an empty string for a disabled item, the subsequent if (!string.IsNullOrEmpty(activeDirective)) check already handles this case. You can safely remove this if block to simplify the code.

Comment on lines +14 to +17
private const string SERVER_BASE_URL = "http://192.168.0.103:3000"; // Update with your IP
private const string RECALL_SESSION_ENDPOINT = SERVER_BASE_URL + "/recall-session";
private const string CREATE_ACCOUNT_ENDPOINT = SERVER_BASE_URL + "/create-account";
private const string UPDATE_PROGRESS_ENDPOINT = SERVER_BASE_URL + "/update-progress";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The server URLs are constructed from a hardcoded base URL. This makes it difficult to change the server address without modifying the code, which is inconvenient during development as local IP addresses can change. It's better to expose the base URL as a configurable field in the Unity Editor.

    [Header("Server Configuration")]
    [Tooltip("The base URL of the recall server, e.g., http://192.168.0.103:3000")]
    [SerializeField] private string _serverBaseUrl = "http://192.168.0.103:3000";

    private string RECALL_SESSION_ENDPOINT => $"{_serverBaseUrl}/recall-session";
    private string CREATE_ACCOUNT_ENDPOINT => $"{_serverBaseUrl}/create-account";
    private string UPDATE_PROGRESS_ENDPOINT => $"{_serverBaseUrl}/update-progress";

Comment on lines +65 to +74
private void Start()
{
var playboardCanvas = GameObject.Find("PlayBoardCanvas");
_dummyLoginPanel = playboardCanvas.transform.Find("DummyLoginPanel");
_usernameText = playboardCanvas.transform.Find("UsernameLabel").GetComponent<Text>();
_usernameInputField = _dummyLoginPanel.transform.Find("Username").GetComponent<InputField>();
_loginButton = _dummyLoginPanel.transform.Find("Login").GetComponent<Button>();

_loginButton.onClick.AddListener(DummyLogin);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using GameObject.Find and transform.Find is fragile and can lead to runtime errors if the scene hierarchy or object names change. It's more robust to declare these UI elements as [SerializeField] private fields and assign them in the Unity Inspector. This creates a direct reference that is safer and more efficient.

You should change the field declarations for _dummyLoginPanel, _usernameInputField, _loginButton, and _usernameText to be:

[Header("UI References")]
[SerializeField] private Transform _dummyLoginPanel;
[SerializeField] private InputField _usernameInputField;
[SerializeField] private Button _loginButton;
[SerializeField] private Text _usernameText;

Then, you can simplify the Start method.

    private void Start()
    {
        if (_loginButton != null)
        {
            _loginButton.onClick.AddListener(DummyLogin);
        }
        else
        {
            Debug.LogError("[PGSRecallManager] Login Button is not assigned in the inspector.");
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant