Skip to content
Shiny.NET

Game Center

NuGet package Shiny.AppDeviceBridge.GameCenter

The game center bridge gives the page the platform’s game service: Apple Game Center on iOS, Mac Catalyst and macOS, and Google Play Games Services v2 on Android. It’s built on Shiny.GameCenter. Unlocks, progress and scores are recorded on the device first and sent once the player is signed in and online, so a page can record them offline without retrying.

Platform Backed by
iOS, Mac Catalyst, macOS GameKit
Android Play Games Services v2
Windows, Linux nothing: every route answers 501
Terminal window
dotnet add package Shiny.AppDeviceBridge.GameCenter
builder.UseAppDeviceBridge(
bridge => bridge.AddGameCenterBridge(o => o
.AddAchievement("first-win", appleId: "com.acme.firstwin", googleId: "CgkI…")
.AddAchievement("ten-wins", appleId: "com.acme.tenwins", googleId: "CgkI…", totalSteps: 10)
.AddLeaderboard("high-score", appleId: "com.acme.highscore", googleId: "CgkI…")
),
webApp => { /* … */ }
);

The page names achievements and leaderboards by these keys, so it never carries App Store Connect or Play Console ids. A key that isn’t registered goes to the service as a platform id. Incremental achievements must be registered with the step count set in the Play Console, because Apple counts progress as a percentage and the device converts it. An app that already calls services.AddGameCenter(…) keeps its registration, and the options passed to the bridge are not used.

Platform setup is Shiny.GameCenter’s: the Game Center entitlement and capability on Apple, and the Play Games APP_ID and signing-key SHA-1s on Android. See Shiny.GameCenter.

import { GameCenterBridge } from "@shinyorg/appdevicebridge";
const games = new GameCenterBridge();
const { player } = await games.signIn(); // null when the player declines
await games.unlock("first-win");
await games.setProgress("ten-wins", { steps: 4 }); // absolute, never a delta
await games.submitScore("high-score", { score: 1200 });
const top = await games.getScores("high-score", { scope: "Friends", timeScope: "Week", maxResults: 10 });
await games.showLeaderboard({ leaderboardKey: "high-score" });

In Blazor, register AddGameCenterBridgeClient() and inject IGameCenterBridge.

unlock, progress, increment, reveal and scores never fail because the player is offline or signed out. They are stored on the device, kept across restarts, and sent after sign-in or when connectivity returns. Progress is an absolute step count, so a retry never counts twice, and progress never goes backwards. GET games reports how many items are still waiting in pendingCount; POST games/flush sends them now and answers the new status.

Reading — achievements, scores and friends — needs a signed-in player and answers 401 not_authenticated otherwise.

Shiny signs the player in silently at launch. POST games/signin shows the service’s sign-in UI when it’s needed and answers { player }, which is null when the player declines. On Apple, after repeated cancels Game Center stops offering its sheet, and the player has to sign in from Settings. games.player fires whenever the player signs in, signs out or switches accounts.

GET games/friends asks for consent the first time. Apple shows a one-time prompt, and needs NSGKFriendListUsageDescription in Info.plist. Google shows a dialog and answers ConsentRequested: ask again once the player is back in the app.

Status Code When
401 not_authenticated No player is signed in, for a read
404 invalid_id The service doesn’t know the achievement or leaderboard
409 no_user_interface No window to show the service’s UI in
503 network, unavailable, not_configured The service can’t be reached, isn’t on the device, or the app isn’t set up for it
502 game_service_failed Anything else
GET/_bridge/games#

The service on this device, the signed-in player, and how much is waiting to be sent.

C#IGameCenterBridge.GetStatusAsyncTSGameCenterBridge.getStatus
Response
200 GameCenterStatus
GameCenterStatus · 3 fields
platformGameServicePlatform
playerGamePlayer | nullThe signed-in player, or null.
pendingCountnumberUnlocks, progress and scores recorded on the device that the service has not accepted yet.
POST/_bridge/games/signin#

Signs the player in, showing the service's sign-in UI when it is needed. Declining is a result, not an error: player is null.

C#IGameCenterBridge.SignInAsyncTSGameCenterBridge.signIn
Response
200 SignInResult
SignInResult · 1 field
playerGamePlayer | nullNull when the player declined — or, on Apple, when Game Center has stopped offering its sheet after repeated cancels, and the player has to sign in from Settings.
GET/_bridge/games/achievements#

Every achievement, merged with progress not sent yet. Needs a signed-in player.

C#IGameCenterBridge.GetAchievementsAsyncTSGameCenterBridge.getAchievements
ParameterInType
forceReloadqueryboolean | nulloptional
Response
200 Achievement[]
Achievement · 9 fields
keystringThe app's logical key, or the platform id when the app did not register the achievement.
platformIdstringThe Game Center or Play Games achievement id.
titlestring | nullLocalized, when the service supplied one.
descriptionstring | nullLocalized, when the service supplied one.
currentStepsnumberNever lower than what was recorded on this device.
totalStepsnumber1 for an unlock-once achievement.
percentCompletenumber0 to 100.
isUnlockedboolean
isHiddenbooleanHidden from the player until it has progress (Apple) or is revealed (Google).
POST/_bridge/games/achievements/{key}/unlock#

Completes an achievement.

C#IGameCenterBridge.UnlockAsyncTSGameCenterBridge.unlock
ParameterInType
keypathstringrequired
Response
204 No content
POST/_bridge/games/achievements/{key}/progress#

Sets an incremental achievement to an absolute step count. Progress never goes backwards.

C#IGameCenterBridge.SetProgressAsyncTSGameCenterBridge.setProgress
ParameterInType
keypathstringrequired
Body
application/json · AchievementProgressRequest
AchievementProgressRequest · 1 field
stepsnumberAn absolute step count, never a delta. A value at or below the current progress is ignored; one above the total is clamped.
Response
204 No content
POST/_bridge/games/achievements/{key}/increment#

Adds steps to an incremental achievement, counted from the furthest progress this device knows of.

C#IGameCenterBridge.IncrementAsyncTSGameCenterBridge.increment
ParameterInType
keypathstringrequired
Body
application/json · AchievementIncrementRequest
AchievementIncrementRequest · 1 field
stepsnumberSteps to add, at least 1.
Response
204 No content
POST/_bridge/games/achievements/{key}/reveal#

Makes a hidden achievement visible. Google only; Game Center has no reveal, so it does nothing there.

C#IGameCenterBridge.RevealAsyncTSGameCenterBridge.reveal
ParameterInType
keypathstringrequired
Response
204 No content
POST/_bridge/games/achievements/show#

Opens the service's achievements UI. Completes once it is open, not when the player closes it.

C#IGameCenterBridge.ShowAchievementsAsyncTSGameCenterBridge.showAchievements
Response
204 No content
POST/_bridge/games/leaderboards/{key}/scores#

Submits a score. The service decides whether it is a new best.

C#IGameCenterBridge.SubmitScoreAsyncTSGameCenterBridge.submitScore
ParameterInType
keypathstringrequired
Body
application/json · SubmitScoreRequest
SubmitScoreRequest · 1 field
scorenumber
Response
204 No content
POST/_bridge/games/leaderboards/{key}/query#

The top scores of a leaderboard, for everyone or for the player's friends. Needs a signed-in player, and friends consent for Friends.

C#IGameCenterBridge.GetScoresAsyncTSGameCenterBridge.getScores
ParameterInType
keypathstringrequired
Body
application/json · LeaderboardScoresRequest
LeaderboardScoresRequest · 3 fields
scopeLeaderboardScope
timeScopeLeaderboardTimeScope
maxResultsnumber1 to 100; the top 25 when not given.
Response
200 LeaderboardEntry[]
LeaderboardEntry · 5 fields
playerGamePlayer
scorenumber
formattedScorestring | nullThe score formatted the way the leaderboard is set up to show it.
ranknumber
isLocalPlayerbooleanThe signed-in player's own row.
POST/_bridge/games/leaderboards/show#

Opens one leaderboard, or all of them.

C#IGameCenterBridge.ShowLeaderboardAsyncTSGameCenterBridge.showLeaderboard
Body
application/json · ShowLeaderboardRequest
ShowLeaderboardRequest · 1 field
leaderboardKeystring | nullOne leaderboard, or every leaderboard when null.
Response
204 No content
GET/_bridge/games/friends#

The player's friends who can see this game, asking for consent the first time. Apple needs NSGKFriendListUsageDescription in Info.plist. Google shows a dialog and answers ConsentRequested: ask again when the player is back.

C#IGameCenterBridge.GetFriendsAsyncTSGameCenterBridge.getFriends
Response
200 FriendsResult
FriendsResult · 2 fields
statusFriendsAccessStatus
friendsGamePlayer[]
POST/_bridge/games/flush#

Sends what is queued now and answers with what is left. It runs on its own after every change, at sign-in and when connectivity returns, so call it only to wait for the queue to drain.

C#IGameCenterBridge.FlushAsyncTSGameCenterBridge.flush
Response
200 GameCenterStatus
GameCenterStatus · 3 fields
platformGameServicePlatform
playerGamePlayer | nullThe signed-in player, or null.
pendingCountnumberUnlocks, progress and scores recorded on the device that the service has not accepted yet.
EVENTgames.player#

The player signed in, signed out or switched accounts.

C#IGameCenterBridge.OnPlayerChangedAsyncTSGameCenterBridge.onPlayerChanged
Payload
PlayerChanged
playerGamePlayer | nullNull when signed out.

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

GameCenterStatusobject#

The game service on this device.

platformGameServicePlatform
playerGamePlayer | nullThe signed-in player, or null.
pendingCountnumberUnlocks, progress and scores recorded on the device that the service has not accepted yet.
GameServicePlatformenum#

The game service behind the bridge on this device.

"AppleGameCenter""GooglePlayGames""Custom"
GamePlayerobject#

A player of the game service.

idstringApple gamePlayerID (stable for this game) or Google playerId.
displayNamestringThe player's display name or alias.
platformGameServicePlatform
SignInResultobject#

How a sign-in ended.

playerGamePlayer | nullNull when the player declined — or, on Apple, when Game Center has stopped offering its sheet after repeated cancels, and the player has to sign in from Settings.
Achievementobject#

An achievement, merged with progress recorded on this device that the service has not accepted yet.

keystringThe app's logical key, or the platform id when the app did not register the achievement.
platformIdstringThe Game Center or Play Games achievement id.
titlestring | nullLocalized, when the service supplied one.
descriptionstring | nullLocalized, when the service supplied one.
currentStepsnumberNever lower than what was recorded on this device.
totalStepsnumber1 for an unlock-once achievement.
percentCompletenumber0 to 100.
isUnlockedboolean
isHiddenbooleanHidden from the player until it has progress (Apple) or is revealed (Google).
AchievementProgressRequestobject#
stepsnumberAn absolute step count, never a delta. A value at or below the current progress is ignored; one above the total is clamped.
AchievementIncrementRequestobject#
steps?numberSteps to add, at least 1.
SubmitScoreRequestobject#
scorenumber
LeaderboardScopeenum#

Whose scores to load from a leaderboard.

"Global""Friends"
LeaderboardTimeScopeenum#

The window of scores to load from a leaderboard.

"Today""Week""AllTime"
LeaderboardEntryobject#

One row of a leaderboard.

playerGamePlayer
scorenumber
formattedScorestring | nullThe score formatted the way the leaderboard is set up to show it.
ranknumber
isLocalPlayerbooleanThe signed-in player's own row.
ShowLeaderboardRequestobject#

Which leaderboard UI to open.

leaderboardKey?string | nullOne leaderboard, or every leaderboard when null.
FriendsAccessStatusenum#

Whether the player has let this game see their friends.

"Granted""Denied""Restricted""ConsentRequested""Unavailable"
PlayerChangedobject#

The signed-in player changed: signed in, signed out or switched accounts.

playerGamePlayer | nullNull when signed out.

Like every bridge, /_bridge/games is behind the bridge policy: callers on this device only, plus the launch session. See Security.