-
Notifications
You must be signed in to change notification settings - Fork 122
Recall API Demo #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Recall API Demo #105
Conversation
wickedcube
commented
Nov 3, 2025
- Simple Node.js Backend for Recall API demo
- 'RECALL API' scripting defined symbol to toggle
Summary of ChangesHello @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
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| if (!fs.existsSync(KEY_FILE_PATH)) { | ||
| throw new Error(`Service account key file not found at path: ${KEY_FILE_PATH}`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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]}..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)}..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_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. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| if (buildItem == _recallAPI && !_playGamesServicesItem.IsEnabled) | ||
| { | ||
| if (defineCount > 0) | ||
| { | ||
| defineString += ";"; | ||
| } | ||
| Debug.Log(buildItem.GetDirective); | ||
| defineString += buildItem.GetDirective; | ||
| ++defineCount; | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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";| 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.");
}
}