.NET MAUI nuget to google play games and apple game center. Unlock achievements and create compitition with leaderboards. Not affiliated with or endorsed by Microsoft, Google, or Apple.
$ dotnet add package Plugin.GamesThis project has no affiliation with Microsoft or the Maui/Xamarin teams.
dotnet add package Plugin.Games
<PropertyGroup>
<PackageReference Include="Plugin.Games" Version="10.0.40" />
</PropertyGroup>
| Achievements | Leaderboards | |
|---|---|---|
| Android | ✅ | ✅ |
| iOS | ✅ | ✅ |
| MacCatalyst | ✅ | ✅ |
| Windows |
Events, SavedGames, Recall, PlayerStats and Friends were omitted due to limitations of supporting package or because Apple doesn't provide a feature that could be abstracted in a similar way to provide anything of use. Additionaly packages like Plugin.Firebase already achieve many of these same features with a little more setup.
Matchmaking, VoiceChat, GameActivities and Challenges were omitted simply because Google doesn't provide equivalent services that could be used to abstract similar features. If these features are desired, they already ship with .net Maui apps under the namespace Microsoft.Maui.Platform.GameKit.
/Readme.md /docs/ Achievements.md Leaderboard.md /src/ /GamesTest/ GamesTest.csproj /Plugin.Games/ Plugin.Games.csproj
Before starting, make sure to set up a google cloud project, create an OAuth2.0 client, and register it with your published app. See below for the instructions: https://developer.android.com/games/pgs/console/setup
dotnet add package Plugin.Games
using Plugin.Games;
namespace GamesTest;
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
var window = new Window(new AppShell());
window.Created += async (s, e) =>
{
//Personal sign in, no server access. See AppDelegate or MainActivity for server access sign in examples.
try
{
var signedIn = await CrossGames.Current.SignInSilentlyAsync();
System.Diagnostics.Debug.WriteLine($"User Signed In: {signedIn}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Sign-in failed: {ex.Message}");
}
};
return window;
}
}
Players who are authenticated already will just sign in silently in the background with maybe a toast appearing in the user interface. For players who haven't authenticated, a sign in user interface action will prompt the user to sign in.
<manifest>
<application>
<meta-data android:name="com.google.android.gms.games.APP_ID"
android:value="@string/game_services_project_id"/>
</application>
</manifest>
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<!-- Replace 0000000000 with your game’s project id. Example value shown above. -->
<string name="game_services_project_id" translatable="false">1024987000491</string>
</resources>
string serverID = "28y9458jidfh2879";
List<GamesAuthScope> scopes = new List<GamesAuthScope>
{
GamesAuthScope.Profile,
GamesAuthScope.Email
};
try
{
GamesAuthResponse response = await CrossGames.Current.RequestServerSideAccessAsync(serverID, false, scopes);
if(response is null)
{
System.Diagnostics.Debug.WriteLine($"Server auth response is null.");
return;
}
else
{
string token = response.Android.AuthorizationCode;
//Pass the oauthToken to your backend server for verification and further processing.
System.Diagnostics.Debug.WriteLine($"Server authenticated. Access Token: {token}");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Authentication failed: {ex.Message}");
}
GamesImplementation.ShouldForceReload = true;
GamesImplementation.ShowLeaderboardsCode = 1234;
GamesImplementation.FriendRequestCode = 5678;
GamesImplementation.ShowAchievementsCode = 1337;
Then verify and handle the right intents later in the main activity by overriding OnActivityResult
protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == GamesImplementation.FriendRequestCode)
{
if (resultCode == Result.Ok)
{
// User accepted → retry the friends leaderboard call
}
else
{
// User declined → fall back to public leaderboard
}
}
}
private void OnGameCenterPrivlagesRevoked(object? sender, GamesCapability capability)
{
switch (capability)
{
case GamesCapability.Multiplayer:
//DisableMultiplayer();
break;
case GamesCapability.InGameCommunication:
//HideChat();
break;
case GamesCapability.ExplicitContent:
//ShowContentWarning();
break;
}
}
public override bool FinishedLaunching(UIApplication application, NSDictionary? launchOptions)
{
CrossGames.Current.OnCapabilityUnavailable += OnGameCenterPrivlagesRevoked;
return base.FinishedLaunching(application, launchOptions);
}
try
{
GamesAuthResponse response = await CrossGames.Current.RequestServerSideAccessAsync();
if(response is null)
{
System.Diagnostics.Debug.WriteLine($"User not signed in.");
return;
}
else
{
var publicURI = response.Apple.PublicKeyUrl;
var signature = Convert.ToBase64String(response.Apple.Signature);
//Use these values to verify the identity of the player on your backend server.
//You can also access the Salt and Timestamp from response.Apple.Salt and response.Apple.Timestamp
//To mitigate replay attacks, make sure the timestamp parameter is recent, and to avoid high network overhead,
//respect the cache expiration headers.
System.Diagnostics.Debug.WriteLine($"User signed in. Public Key URL: {publicURI}, Signature: {signature}");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Sign-in failed: {ex.Message}");
}
| Platform | Package Name | Version | SDK Name | Version |
|---|---|---|---|---|
| Android | Xamarin.GooglePlayServices.Games.V2 | 121.0.0.1 | Play Games Services | 21.0.0 |
| iOS | Microsoft.Maui.Platform.GameKit | Game Center |