- Apple.Core
See the Quick-Start Guide for general installation instructions.
Since most calls to GameKit are asynchronous, the public methods are Task
, or Task<>
based. For a comprehensive guide to GameKit on Apple devices, please see GameKit Developer Documentation
If there is any error reported from GameKit, it will be reported by throwing a GameKitException
. In all cases, a try -catch
should be used to properly handle exceptions.
private async Task Start()
{
try
{
var result = await ...
}
catch(GameKitException exception)
{
Debug.LogError(exception);
}
}
- Players
- Achievements
- GKGameCenterViewController
- Realtime Matchmaking
- Turn Based Matchmaking
- Leaderboards
- Access Point
- Challenges
- Invites
var player = await GKLocalPlayer.Authenticate();
Debug.Log($"GameKit Authentication: isAuthenticated => {player.IsAuthenticated}");
Note: This call is not asynchronous.
var localPlayer = GKLocalPlayer.Local;
Debug.Log($"Local Player: {localPlayer.DisplayName}");
if(!localPlayer.IsUnderage) {
// Ask for analytics permissions, etc.
}
Each call to LoadPlayerPhoto generates a new Texture2D so ensure you cache as necessary per your own use-case.
var player = await GKLocalPlayer.Local;
// Resolves a new instance of the players photo as a Texture2D.
var photo = await player.LoadPhoto(size);
// Loads the local player's friends list if the local player and their friends grant access.
var friends = await GKLocalPlayer.Local.LoadFriends();
// Loads players to whom the local player can issue a challenge.
var challengeableFriends = await GKLocalPlayer.Local.LoadChallengeableFriends();
// Loads players from the friends list or players that recently participated in a game with the local player.
var recentPlayers = await GKLocalPlayer.Local.LoadRecentPlayers();
var fetchItemsResponse = await GKLocalPlayer.Local.FetchItems();
var signature = fetchItemsResponse.GetSignature();
var salt = fetchItemsResponse.GetSalt();
var publicKeyUrl = fetchItemsResponse.PublicKeyUrl;
var timestamp = fetchItemsResponse.Timestamp;
Note: This only returns achievements with progress that the player has reported. Use GKAchievementDescription for a list of all available achievements.
var achievements = await GKAchievement.LoadAchievements();
foreach (var a in achievements)
{
Debug.Log($"Achievement: {a.Identifier}");
}
var descriptions = await GKAchievementDescription.LoadAchievementDescriptions();
foreach (var d in descriptions)
{
Debug.Log($"Achievement: {a.Identifier}, Unachieved Description: {d.UnachievedDescription}, Achieved Description: {d.AchievedDescription}");
}
var descriptions = await GKAchievementDescription.LoadAchievementDescriptions();
foreach (var d in descriptions)
{
var image = await d.LoadImage();
// Do something with the image.
}
var achievementId = "a001";
var progressPercentage = 100;
var showCompletionBanner = true;
var achievements = await GKAchievement.LoadAchievements();
// Only completed achievements are returned.
var achievement = achievements.FirstOrDefault(a => a.Identifier == achievementId);
// If null, initialize it
achievement ??= GKAchievement.Init(achievementId);
if(!achievement.IsCompleted) {
achievement.PercentComplete = progressPercentage;
achievement.ShowCompletionBanner = showCompletionBanner;
// Accepts a param GKAchievement[] for reporting multiple achievements.
await GKAchievement.Report(achievement, ...);
}
await GKAchievement.ResetAchievements();
The Task will resolve when the dialog has been closed.
var gameCenter = GKGameCenterViewController.Init(GKGameCenterViewController.GKGameCenterViewControllerState.Achievement);'
// await for user to dismiss...
await gameCenter.Present();
var matchRequest = GKMatchRequest.Init();
matchRequest.MinPlayers = 2;
matchRequest.MaxPlayers = 2;
GKMatch match = await GKMatchmakerViewController.Request(matchRequest);
match.Delegate.DataReceived += OnMatchDataReceived;
match.Delegate.DataReceivedForPlayer ++ OnMatchDataReceivedForPlayer;
match.Delegate.DidFailWithError += OnMatchErrorReceived;
match.Delegate.PlayerConnectionChanged += OnMatchPlayerConnectionChanged;
match.Delegate.ShouldReinviteDisconnectedPlayer += OnShouldReinviteDisconnectedPlayer;
private void OnMatchDataReceived(byte[] data, GKPlayer fromPlayer)
{
// Process data
}
private void OnMatchDataReceivedForPlayer(byte[] data, GKPlayer forRecipient, GKPlayer fromPlayer)
{
// Process data
}
private void OnMatchErrorReceived(GameKitException exception)
{
// Handle error
}
private void OnMatchPlayerConnectionChanged(GKPlayer player, GKPlayerConnectionState state)
{
// Handle state change
}
private bool ShouldReinviteDisconnectedPlayerHandler(GKPlayer player)
{
// Reinvite disconnected player
}
var request = GKMatchRequest.Init();
request.MinPlayers = 1;
request.MaxPlayers = 4;
request.PlayerAttributes = 0;
request.PlayerGroup = 0;
request.RestrictToAutomatch = false;
// If using rule-based matchmaking...
request.QueueName = "NameOfYourMatchmakerQueue";
request.Properties = GKMatchProperties.FromJson(jsonPropertyBagToSendToServer);
// -or-
request.Properties = new NSMutableDictionary<NSString, NSObject> {
{ "YourPropertyNameHere", new NSNumber(3.14159) },
{ "AnotherPropertyName", new NSString("some string value") }
};
var match = await GKMatchmakerViewController.Request(request);
// A match based on the GKMatchRequest.
var match = await GKMatchmaker.Shared.FindMatch(request);
// A match from an accepted invite.
var matchForInvite = await GKMatchmaker.Shared.Match(invite);
// Initiates a request to find players for a hosted match.
var players = await GKMatchmaker.Shared.FindPlayers(request);
// Initiates a request to find players for a hosted match via rule-based matchmaking.
GKMatchedPlayers matchedPlayers = GKMatchmaker.Shared.FindMatchedPlayers(request);
// Invites additional players to an existing match...
await GKMatchmaker.Shared.AddPlayers(match, request);
// Finds the number of players, across player groups, who recently requested a match.
var numMatchRequests = await GKMatchmaker.Shared.QueryActivity();
// Finds the number of players, across player groups, who recently requested a match via the specified rule-based matchmaking queue.
var numMatchRequests = await.GKMatchmaker.Shared.QueryQueueActivity("NameOfYourQueue");
// Finds the number of players in a player group who recently requested a match.
var numMatchRequestsInGroup = await GKMatchmaker.Shared.QueryPlayerGroupActivity(playerGroupId);
// Cancels a matchmaking request.
GKMatchmaker.Shared.Cancel();
// Cancels a pending invitation to another player.
GKMatchmaker.Shared.CancelPendingInvite(playerBeingInvited);
match.Disconnect();
Sends a message to all players
var data = Encoding.UTF8.GetBytes("Hello World");
match.Send(data, GKMatch.GKSendDataMode.Reliable);
Sends a message to the selected players
var players = new GKPlayer[] { ... };
var data = Encoding.UTF8.GetBytes("Hello World");
match.SendToPlayers(data, players, GKMatch.GKSendDataMode.Reliable);
// Creates the channel
var channel = match.VoiceChat("myChannelName");
channel.Start();
// Enable to sample microphone
if(TalkToPlayers)
channel.IsActive = true;
else
channel.IsActive = false;
GKTurnBasedMatch.TurnEventReceived += OnTurnEnded;
GKTurnBasedMatch.MatchEnded += OnMatchEnded;
private void OnTurnEnded(GKPlayer player, GKTurnBasedMatch match, bool didBecomeActive)
{
// Handle turn ended
}
private void OnMatchEnded(GKPlayer player, GKTurnBasedMatch match)
{
// Handle match ended
}
var turnBasedMatches = await GKTurnBasedMatch.LoadMatches();
foreach (var match in turnBasedMatches)
{
Debug.Log($"TurnBasedMatch: {match.MatchId}");
}
This call creates a GKMatchRequest with the specified parameters and passes it into a GKTurnBasedMatchmakerViewController. Once a user has selected or created a new match, the Task will resolve. If the user cancels, or an error occurs, a GameKitException will be thrown.
var request = GKMatchRequest.Init();
request.MinPlayers = 2;
request.MaxPlayers = 2;
var match = await GKTurnBasedMatchmakerViewController.Request(request);
When the local player takes their turn, they can pass the updated TurnBasedMatch to the EndTurn method.
[System.Serializable]
public class GameData
{
public int PlayerId;
public int Count;
};
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
var gameData = new GameData()
{
PlayerId = 0,
Count = 1
};
formatter.Serialize(stream, gameData);
var data = stream.ToArray();
await match.EndTurn(data);
}
[System.Serializable]
public class GameData
{
public int PlayerId;
public int Count;
};
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
var gameData = new GameData()
{
PlayerId = 0,
Count = 1
};
formatter.Serialize(stream, gameData);
var data = stream.ToArray();
// Set the outcomes...
foreach (var participant in match.Participants) {
participant.MatchOutcome = Outcome.Won;
}
await match.EndMatch(data);
}
GKTurnBasedMatch.ExchangeReceived += OnExchangeReceived;
GKTurnBasedMatch.ExchangeCompleted += OnExchangeCompleted;
GKTurnBasedMatch.ExchangeCanceled += OnExchangeCanceled;
private void OnExchangeReceived(GKPlayer player, GKTurnBasedExchange exchange, GKTurnBasedMatch match) {
}
private void OnExchangeCompleted(GKPlayer player, NSArray<GKTurnBasedExchangeReply> replies, GKTurnBasedExchange exchange, GKTurnBasedMatch match) {
}
private void OnExchangeCanceled(GKPlayer player, GKTurnBasedExchange exchange, GKTurnBasedMatch match) {
}
// Creates and sends the exchange to the patricipants
var exchange = await turnBasedMatch.SendExchange(participants, data, localizableMessageKey, arguments, GKTurnBasedMatch.ExchangeTimeoutDefault);
// Cancel
await exchange.Cancel(localizableMessageKey, arguments);
// Reply
await exchange.Reply(localizableMessageKey, arguments, data);
// Saves the merged data for the current turn without ending the turn
await turnBasedMatch.SaveMergedMatch(data, exchanges);
var leaderboardId = "MyLeaderboard";
var score = 100;
var context = 0;
// Filter leadboards by params string[] identifiers
var leaderboards = await GKLeaderboard.LoadLeaderboards("MyLeaderboard");
var leaderboard = leaderboards.FirstOrDefault();
// Submit
await leaderboard.SubmitScore(score, context, GKLocalPlayer.Local);
var allLeaderboards = await GKLeaderboard.LoadLeaderboards();
var filteredLeaderboards = await GKLeaderboard.LoadLeaderboards("lb1", "lb3");
Using a leaderboard reference, you can load the entries:
var playerScope = GKLeaderboard.PlayerScope.Global;
var timeScope = GKLeaderboard.TimeScope.AllTime;
var rankMin = 1;
var rankMax = 100;
var scores = await leaderboard.LoadEntries(playerScope, timeScope, rankMin, rankMax);
// Unsupported on tvOS
var image = await leaderboard.LoadImage();
If you need to check whether the GameCenter / AccessPoint overlay is currently visble, you can query the following api call.
private void Update()
{
if(GKAccessPoint.Shared.IsPresentingGameCenter)
{
// Disable controllers
}
}
Note: This call is not asynchronous.
GKAccessPoint.Shared.Location = GKAcessPoint.GKAccessPointLocation.TopLeading;
GKAccessPoint.Shared.ShowHighlights = true;
GKAccessPoint.Shared.IsActive = true; // or false to hide.
Note: This call is not asynchronous.
GKAccessPoint.Shared.Trigger();
var challenges = await GKChallenge.LoadReceivedChallenges();
foreach (var c in challenges)
{
// Deprecated as GKScore was deprecated in < iOS 14, tvOS 14, and macOS 11
if(c is GKScoreChallenge) {
// Kept for historical purposes
}
if(c is GKAchievementChallenge achievementChallenge) {
Debug.Log($"Achievement Challenge: {achievementChallenge.Achievement?.Identifier}");
}
}
GKInvite.InviteAccepted += OnInviteAccepted;
public void OnInviteAccepted(GKPlayer invitedPlayer, GKInvite invite) {
}