diff --git a/docs/godot/identifying.mdx b/docs/godot/identifying.mdx index 6dcd450..8113fb7 100644 --- a/docs/godot/identifying.mdx +++ b/docs/godot/identifying.mdx @@ -1,9 +1,9 @@ --- sidebar_position: 2 -description: The Talo Godot plugin allows you to identify multiple aliases, authenticate and sync your players with Steamworks and Google Play. +description: The Talo Godot plugin allows you to identify multiple aliases, authenticate and sync your players with Steamworks, Google Play and Apple Game Center. --- -import { ScopeBadges } from '@site/src/components/ScopeBadges' +import { ScopeBadges } from "@site/src/components/ScopeBadges"; # Identifying a player @@ -16,7 +16,7 @@ You should identify a player after they have authenticated and before you attemp ## Identifying - + You can identify a player using `Talo.players.identify()`. The code sample below shows you how you could identify a player using a UI element (this example is also available in the Playground scene): @@ -88,7 +88,7 @@ Once all the relevant data has been cleared, the `Talo.players.identity_cleared` ## Merging players - + Sometimes you might start tracking a player's actions before you know their true identity. For example, you could be tracking events with an "anonymous" identifier and then later on the same player chooses their username before submitting a leaderboard entry. Since both of these players need to be identified, two players will be created. @@ -97,8 +97,8 @@ You can merge players using `Talo.players.merge()` by providing the IDs of both :::caution There are a few limitations to merging players: -- **Player 2** cannot have a Talo Player Authentication, Steam or Google Play Games alias. -- If **Player 1** has a Talo Player Authentication, Steam or Google Play Games alias, the merge must be initiated while identified as **Player 1** (i.e. `Talo.current_alias` must belong to Player 1). In this case, make sure your last `Talo.players.identify()` call before merging uses Player 1's alias. +- **Player 2** cannot have a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias. +- If **Player 1** has a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias, the merge must be initiated while identified as **Player 1** (i.e. `Talo.current_alias` must belong to Player 1). In this case, make sure your last `Talo.players.identify()` call before merging uses Player 1's alias. - Both players cannot have overlapping alias services. For example, if both players have an alias with the service "username", the merging process will fail. ::: @@ -208,6 +208,63 @@ After successfully authenticating the player, these [props](/docs/godot/player-p These props will be updated each time the player is identified using `Talo.players.identify_google_play_games()`. +## Apple Game Center integration + +:::tip +You can enable this integration on the [integrations page](https://dashboard.trytalo.com/integrations). +::: + +If you have the Apple Game Center integration enabled, Talo can identify a player using Apple's identity verification signature. You can do this via the `Talo.players.identify_game_center` function. + +:::caution +The Game Center plugin singleton is only available on iOS and macOS. It will not exist when running from the Godot editor. Make sure to check for its availability before calling any Game Center methods. +::: + +You'll need to fetch the local player's identity verification signature and pass the resulting values to Talo. Here's an example using the [GodotApplePlugins](https://github.com/migueldeicaza/GodotApplePlugins): + +```gdscript +extends Node + +# replace with your own bundle identifier +const bundle_id := "com.example.game" + +var game_center: GameCenterManager + +func _ready() -> void: + game_center = GameCenterManager.new() + + game_center.authentication_error.connect( + func (err: String): + push_error("Game Center auth failed: %s" % err) + ) + + game_center.authentication_result.connect( + func (success: bool): + if success: + _handle_auth_success() + ) + + game_center.authenticate() + +func _handle_auth_success() -> void: + var local_player := game_center.local_player + + local_player.fetch_items_for_identity_verification_signature( + func (res: Dictionary, err: Variant): + if err: + push_error(err) + else: + await Talo.players.identify_game_center( + res.url, + Marshalls.raw_to_base64(res.data), + Marshalls.raw_to_base64(res.salt), + res.timestamp, + local_player.team_player_id, + bundle_id + ) + ) +``` + ## Offline player cache If the `cache_player_on_identify` setting is enabled (default `true`), Talo will store player data locally. If a player tries to identify while offline, Talo will try and use local data if it exists. diff --git a/docs/integrations/apple-game-center.md b/docs/integrations/apple-game-center.md new file mode 100644 index 0000000..bf2be34 --- /dev/null +++ b/docs/integrations/apple-game-center.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 3 +description: Use Talo's native Apple Game Center integration to automatically identify players signed in to Game Center. +--- + +# Apple Game Center + +Using your app's Bundle ID, you can automatically identify players signed in to Apple Game Center. + +You can enable this integration on the [integrations page](https://dashboard.trytalo.com/integrations). + +![The Talo integrations page showing the Apple Game Center settings](/img/apg-integration.png) + +
+ +## Authentication + +To get started, enter your app's Bundle ID into the dashboard. Talo will use this to verify the identity verification signature provided by Game Center. + +To identify a player, fetch the local player's identity verification signature from Game Center and pass the values to Talo. Talo will cryptographically verify the signature against Apple's public key and automatically identify the player without them needing to create an account. + +More info is available in the [Godot plugin docs](/docs/godot/identifying#apple-game-center-integration) and [Unity package docs](/docs/unity/identifying#apple-game-center-integration). diff --git a/docs/unity/identifying.mdx b/docs/unity/identifying.mdx index c940a1e..825ba9d 100644 --- a/docs/unity/identifying.mdx +++ b/docs/unity/identifying.mdx @@ -1,6 +1,6 @@ --- sidebar_position: 2 -description: The Talo Unity package allows you to identify multiple aliases, authenticate and sync your players with Steamworks and Google Play. +description: The Talo Unity package allows you to identify multiple aliases, authenticate and sync your players with Steamworks, Google Play and Apple Game Center. --- import { ScopeBadges } from '@site/src/components/ScopeBadges' @@ -25,24 +25,24 @@ using TaloGameServices; public class IdentifyPlayer: MonoBehaviour { - public string service = 'username', identifier = '123456'; - - public void OnButtonClick() - { - Identify(); - } - - private async void Identify() - { - try - { - await Talo.Players.Identify(service, identifier); - } - catch (Exception ex) - { - Debug.LogError(ex.Message); - } - } + public string service = 'username', identifier = '123456'; + + public void OnButtonClick() + { + Identify(); + } + + private async void Identify() + { + try + { + await Talo.Players.Identify(service, identifier); + } + catch (Exception ex) + { + Debug.LogError(ex.Message); + } + } } ``` @@ -61,7 +61,7 @@ After a successful identification, the `Talo.Players.OnIdentified()` event will ```csharp Talo.Players.OnIdentified += async (player) => { - await Talo.Saves.GetSaves(); + await Talo.Saves.GetSaves(); }; ``` @@ -78,16 +78,16 @@ Sometimes you might need to check if a player has been identified before. You ca ```csharp public void DoStuffIfIdentified() { - try - { - Talo.IdentityCheck(); - } - catch (Exception ex) - { - return; - } - - // do stuff + try + { + Talo.IdentityCheck(); + } + catch (Exception ex) + { + return; + } + + // do stuff } ``` @@ -100,24 +100,24 @@ Once all the relevant data has been cleared, the `Talo.Players.OnIdentityCleared ```csharp private async void ClearIdentity() { - try - { - await Talo.Players.ClearIdentity(); - } - catch (Exception ex) - { - Debug.LogError($"Failed to clear identity: {ex.Message}"); - } + try + { + await Talo.Players.ClearIdentity(); + } + catch (Exception ex) + { + Debug.LogError($"Failed to clear identity: {ex.Message}"); + } } // Listen for the identity cleared event void Start() { - Talo.Players.OnIdentityCleared += () => - { - Debug.Log("Player identity has been cleared"); - // Handle post-clear logic here - }; + Talo.Players.OnIdentityCleared += () => + { + Debug.Log("Player identity has been cleared"); + // Handle post-clear logic here + }; } ``` @@ -132,8 +132,8 @@ You can merge players using `Talo.Players.Merge()` by providing the IDs of both :::caution There are a few limitations to merging players: -- **Player 2** cannot have a Talo Player Authentication, Steam or Google Play Games alias. -- If **Player 1** has a Talo Player Authentication, Steam or Google Play Games alias, the merge must be initiated while identified as **Player 1** (i.e. `Talo.CurrentAlias` must belong to Player 1). In this case, make sure your last `Talo.Players.Identify()` call before merging uses Player 1's alias. +- **Player 2** cannot have a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias. +- If **Player 1** has a Talo Player Authentication, Steam, Google Play Games or Apple Game Center alias, the merge must be initiated while identified as **Player 1** (i.e. `Talo.CurrentAlias` must belong to Player 1). In this case, make sure your last `Talo.Players.Identify()` call before merging uses Player 1's alias. - Both players cannot have overlapping alias services. For example, if both players have an alias with the service "username", the merging process will fail. ::: @@ -149,7 +149,7 @@ Debug.Log(Talo.CurrentAlias.Service) // "username" var mergedPlayer = await Talo.Players.Merge(player1Id, player2Id, new MergeOptions { - postMergeIdentityService = "anonymous" // go back to the anonymous alias + postMergeIdentityService = "anonymous" // go back to the anonymous alias }); Debug.Log(Talo.CurrentAlias.Service) // "anonymous" @@ -172,25 +172,25 @@ string identity = "talo"; void SignInWithSteam() { - // It's not necessary to add event handlers if they are - // already hooked up. - // Callback.Create return value must be assigned to a - // member variable to prevent the GC from cleaning it up. - // Create the callback to receive events when the session ticket - // is ready to use in the web API. - // See GetAuthSessionTicket document for details. - m_AuthTicketForWebApiResponseCallback = Callback.Create(OnAuthCallback); - - SteamUser.GetAuthTicketForWebApi(identity); + // It's not necessary to add event handlers if they are + // already hooked up. + // Callback.Create return value must be assigned to a + // member variable to prevent the GC from cleaning it up. + // Create the callback to receive events when the session ticket + // is ready to use in the web API. + // See GetAuthSessionTicket document for details. + m_AuthTicketForWebApiResponseCallback = Callback.Create(OnAuthCallback); + + SteamUser.GetAuthTicketForWebApi(identity); } void OnAuthCallback(GetTicketForWebApiResponse_t callback) { - m_SessionTicket = BitConverter.ToString(callback.m_rgubTicket).Replace("-", string.Empty); - m_AuthTicketForWebApiResponseCallback.Dispose(); - m_AuthTicketForWebApiResponseCallback = null; + m_SessionTicket = BitConverter.ToString(callback.m_rgubTicket).Replace("-", string.Empty); + m_AuthTicketForWebApiResponseCallback.Dispose(); + m_AuthTicketForWebApiResponseCallback = null; - Talo.Players.IdentifySteam(m_SessionTicket, identity); + Talo.Players.IdentifySteam(m_SessionTicket, identity); } ``` @@ -236,17 +236,17 @@ selectedScopes.Add(AuthScope.EMAIL); // Call RequestServerSideAccess with additional scopes and retrieve // authcode and grantedscopes list. PlayGamesPlatform.Instance.RequestServerSideAccess( - /* forceRefreshToken= */ false, - selectedScopes, - (AuthResponse authResponse) => - { - string authCode = authResponse.GetAuthCode(); - List grantedScopes = authResponse.GetGrantedScopes(); - - // Pass the auth code to Talo - // Alternatively, you can `await` the result - _ = Talo.Players.IdentifyGooglePlayGames(authCode); - } + /* forceRefreshToken= */ false, + selectedScopes, + (AuthResponse authResponse) => + { + string authCode = authResponse.GetAuthCode(); + List grantedScopes = authResponse.GetGrantedScopes(); + + // Pass the auth code to Talo + // Alternatively, you can `await` the result + _ = Talo.Players.IdentifyGooglePlayGames(authCode); + } ); ``` @@ -259,6 +259,69 @@ After successfully authenticating the player, these [props](/docs/unity/player-p These props will be updated each time the player is identified using `Talo.Players.IdentifyGooglePlayGames()`. +## Apple Game Center integration + +:::tip +You can enable this integration on the [integrations page](https://dashboard.trytalo.com/integrations). +::: + +If you have the Apple Game Center integration enabled, Talo can identify a player using Apple's identity verification signature. You can do this via the `Talo.Players.IdentifyGameCenter` function. + +You'll need to fetch the local player's identity verification signature using [`FetchItems`](https://github.com/apple/unityplugins/blob/main/plug-ins/Apple.GameKit/Apple.GameKit_Unity/Assets/Apple.GameKit/Source/GKLocalPlayer.cs) from the [Apple Game Kit Unity plugin](https://github.com/apple/unityplugins) and pass the resulting values to Talo. Here's a modified version of an example [provided by Unity](https://docs.unity.com/en-us/authentication/platform-signin/apple-game-center): + +```csharp +using UnityEngine; +using System.Threading.Tasks; +using Apple.GameKit; + +public class IdentifyPlayer : MonoBehaviour +{ + private void Start() + { + Talo.Players.OnIdentified += OnIdentified; + + GKLocalPlayer.AuthenticateError += (err) => { + Debug.LogError(err.LocalizedDescription); + }; + + GKLocalPlayer.AuthenticateUpdate += async (player) => { + if (player.IsAuthenticated) + { + await Identify(); + } + }; + } + + public async void OnLoginClick() + { + await GKLocalPlayer.Authenticate(); + } + + private async Task Identify() + { + var fetchItemsResponse = await GKLocalPlayer.Local.FetchItemsForIdentityVerificationSignature(); + + var publicKeyUrl = fetchItemsResponse.PublicKeyUrl; + var signature = fetchItemsResponse.GetSignature(); + var salt = fetchItemsResponse.GetSalt(); + var timestamp = fetchItemsResponse.Timestamp; + + await Talo.Players.IdentifyGameCenter( + publicKeyUrl, + signature, + salt, + timestamp, + GKLocalPlayer.Local.TeamPlayerId + ); + } + + private void OnIdentified(Player player) + { + Debug.Log($"Player identifier: {player.id}"); + } +} +``` + ## Offline player cache If the `cachePlayerOnIdentify` setting is enabled (default `true`), Talo will store player data locally. If a player tries to identify while offline, Talo will try and use local data if it exists. diff --git a/static/img/apg-integration.png b/static/img/apg-integration.png new file mode 100644 index 0000000..e4d187a Binary files /dev/null and b/static/img/apg-integration.png differ