:github_url: hide :allow_comments: False .. DO NOT EDIT THIS FILE!!! .. Generated automatically from GameCenter/GameCenterGuide.md. .. _gamecenter_gamecenterguide__using-apple-s-gamekit-apis-with-godot: Using Apple's GameKit APIs with Godot ===================================== This is a quick guide on using the APIs in this Godot addon to access Apple's GameKit APIs. For an overview of what you can do with GameKit, check [Apple's GameKit Documentation](https://developer.apple.com/documentation/gamekit/) One of the design choices in this binding has been to surface the same class names that Apple uses for their own data types to simplify looking things up and finding resources online. The method names on the other hand reflect the Godot naming scheme. So instead of calling ``loadPhoto`` on ``GKPlayer``, you would use the ``load_photo`` method. And instead of the property ``gamePlayerID``, you would access ``game_player_id``. .. _gamecenter_gamecenterguide__table-of-contents: Table of Contents ================= * `Installation <#gamecenter_gamecenterguide__installation>`__ * `Authentication <#gamecenter_gamecenterguide__authentication>`__ * `Players <#gamecenter_gamecenterguide__players>`__ * `Achievements <#gamecenter_gamecenterguide__achievements>`__ * `Realtime Matchmaking <#gamecenter_gamecenterguide__realtime-matchmaking>`__ * `Turn-Based Matchmaking <#gamecenter_gamecenterguide__turn-based-matchmaking>`__ .. _gamecenter_gamecenterguide__installation: Installation ============ .. _gamecenter_gamecenterguide__installing-in-your-project: Installing in your project -------------------------- Make sure that you have added the directory containing the "GodotApplePlugins" to your project, it should contain both a ``godot_apple_plugins.gdextension`` and a ``bin`` directory with the native libraries that you will use. The APIs have been exposed to both MacOS and iOS, so you can iterate quickly on your projects. .. _gamecenter_gamecenterguide__entitlements: Entitlements ------------ For your software to be able to use the GameKit APIs, you will need your Godot engine to have the ``com.apple.developer.game-center`` entitlements. The easiest way to do this is to use Xcode to add the entitlement to your iOS project. See the file :doc:`Entitlements ` for additional directions * without this, calling the APIs won't do much. .. _gamecenter_gamecenterguide__authentication: Authentication ============== Create an instance of ``GameCenterManager``, and then you can connect to the ``authentication_error`` and ``authentication_result`` signals to track the authentication state. Then call the ``authenticate()`` method to trigger the authentication: .. code-block:: gdscript var game_center: GameCenterManager func _ready() -> void: game_center = GameCenterManager.new() game_center.authentication_error.connect(func(error: String) -> void: print("Received error %s" % error) ) game_center.authentication_result.connect(func(status: bool) -> void: print("Authentication updated, status: %s" % status ) game_center.authenticate() .. _gamecenter_gamecenterguide__players: Players ======= .. _gamecenter_gamecenterguide__fetch-the-local-player: Fetch the Local Player ---------------------- From the ``GameCenterManager`` instance, you can access ``local_player``, which is a ``GKLocalPlayer``. ``GKLocalPlayer`` is a subclass of ``GKPlayer`` and represents the player using your game, with properties that track the local player state. .. code-block:: gdscript var local: GKLocalPlayer func _ready() -> void: gameCenter = GameCenterManager.new() local = gameCenter.local_player print("ONREADY: local, is auth: %s" % local.is_authenticated) print("ONREADY: local, player ID: %s" % local.game_player_id) There are a number of interesting properties in ``local_player`` that you might want to use in your game like ``is_authenticated``, ``is_underage``, ``is_multiplayer_gaming_restricted`` and so on. .. _gamecenter_gamecenterguide__gkplayer: GKPlayer -------- * `GKPlayer `__ This is the base class for a player, either the local one or friends and contains properties and methods that are common to both Apple Documentation: * `GKLocalPlayer `__ .. _gamecenter_gamecenterguide__loading-a-player-photo: Loading a Player Photo ---------------------- .. code-block:: gdscript # Here, we put the image inside an existing TextureRect, named $texture_rect: local_player.load_photo(true, func(image: Image, error: Variant)->void: if error == null: $texture_rect.texture = ImageTexture.create_from_image(image) ) .. _gamecenter_gamecenterguide__friends: Friends ------- .. code-block:: gdscript # Loads the local player's friends list if the local player and their friends grant access. local_player.load_friends(func(friends: Array[GKPlayer], error: Variant)->void: if error: print(error) else: for friend in friends: print(friend.display_name) ) # Loads players to whom the local player can issue a challenge. local_player.local.load_challengeable_friends(func(friends: Array[GKPlayer], error: Variant)->void: if error: print(error) else: for friend in friends: print(friend.display_name) ) # Loads players from the friends list or players that recently participated in a game with the local player. local_player.load_recent_friends(func(friends: Array[GKPlayer], error: Variant)->void: if error: print(error) else: for friend in friends: print(friend.display_name) ) .. _gamecenter_gamecenterguide__fetchitemsforidentityverificationsignature: FetchItemsForIdentityVerificationSignature ------------------------------------------ * `Apple Documentation `__ .. code-block:: gdscript local_player.fetch_items_for_identity_verification_signature(func(values: Dictionary, error: Variant)->void: if error: print(error) else: print("Identity dictionary") print(values) ) .. _gamecenter_gamecenterguide__saved-games: Saved Games =========== .. _gamecenter_gamecenterguide__handling-conflicts: Handling Conflicts ------------------ When multiple devices save games with the same name, conflicts can occur. You can handle these conflicts by registering a listener and implementing the resolution logic. .. code-block:: gdscript var game_center: GameCenterManager var local: GKLocalPlayer func _ready() -> void: game_center = GameCenterManager.new() local = game_center.local_player # Register the listener to receive conflict signals local.register_listener() local.conflicting_saved_games.connect(_on_conflicting_saved_games) func _on_conflicting_saved_games(player: GKPlayer, conflicts: Array) -> void: print("Received conflict for player: %s" % player.alias) # Logic to determine which data to keep (e.g., newest, highest score, or user choice) # For this example, we assume we want to keep the data from the first conflicting save. var chosen_save = conflicts[0] as GKSavedGame chosen_save.load_data(func(data: PackedByteArray, error: Variant) -> void: if error: print("Error loading data: %s" % error) return # Resolve the conflict using the chosen data local.resolve_conflicting_saved_games(conflicts, data, func(saved_games: Array[GKSavedGame], error: Variant) -> void: if error: print("Error resolving conflict: %s" % error) else: print("Conflict resolved!") ) ) .. _gamecenter_gamecenterguide__saved-game-modifications: Saved Game Modifications ------------------------ You can also listen for modifications to saved games (e.g., from other devices). .. code-block:: gdscript func _ready() -> void: # ... setup local player ... local.saved_game_modified.connect(_on_saved_game_modified) func _on_saved_game_modified(player: GKPlayer, saved_game: GKSavedGame) -> void: print("Saved game modified: %s" % saved_game.name) # Reload data or update UI .. _gamecenter_gamecenterguide__achievements: Achievements ============ * `GKAchievement `__ * `GKAchievementDescription `__ .. _gamecenter_gamecenterguide__list-all-achievements: List all achievements --------------------- Note: This only returns achievements with progress that the player has reported. Use ``GKAchievementDescription`` for a list of all available achievements. .. code-block:: gdscript GKAchievement.load_achievements(func(achievements: Array[GKAchievement], error: Variant)->void: if error: print("Load achievement error %s" % error) else: for achievement in achievements: print("Achievement: %s" % achievement.identifier) ) .. _gamecenter_gamecenterguide__list-descriptions: List Descriptions ----------------- .. code-block:: gdscript GKAchievementDescription.load_achievement_descriptions(func(adescs: Array[GKAchievementDescription], error: Variant)->void: if error: print("Load achievement description error %s" % error) else: for adesc in adescs: print("Achievement Description ID: %s" % adesc.identifier) print(" Unachieved: %s" % adesc.unachieved_description) print(" Achieved: %s" % adesc.achieved_description) ) .. _gamecenter_gamecenterguide__load-achievement-description-image: Load Achievement Description Image ---------------------------------- .. code-block:: gdscript adesc.load_image(func(image: Image, error: Variant)->void: if error == null: $texture_rect.texture = ImageTexture.create_from_image(image) else: print("Error loading achievement image %s" % error) .. _gamecenter_gamecenterguide__report-progress: Report Progress --------------- .. _gamecenter_gamecenterguide__reporting-achievement-first-time: Reporting Achievement First Time ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: gdscript var id = "a001" var percentage = 100 GKAchievementDescription.load_achievement_descriptions(func(descriptions: Array[GKAchievementDescription], error: Variant)->void: if error: print("Load achievement descriptions error %s" % error) else: for desc in descriptions: if desc.identifier == id: var new_achievement := GKAchievement.new() new_achievement.identifier = desc.identifier new_achievement.percent_complete = percentage GKAchievement.report_achievement([new_achievement], func(error: Variant)->void: if error: print("Error submitting achievement") else: print("Success!") ) ) .. _gamecenter_gamecenterguide__updating-already-reported-achievement: Updating Already Reported Achievement ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: gdscript var id = "a001" var percentage = 100 GKAchievement.load_achievements(func(achievements: Array[GKAchievement], error: Variant)->void: if error: print("Load achievement error %s" % error) else: for achievement in achievements: if achievement.identifier == id: if not achievement.is_completed: achievement.percent_complete = percentage achievement.show_completion_banner = true GKAchievement.report_achievement([achievement], func(error: Variant)->void: if error: print("Error submitting achievement") else: print("Success!") ) ) .. _gamecenter_gamecenterguide__reset-all-achievements: Reset All Achievements ---------------------- .. code-block:: gdscript GKAchievement.reset_achievements(func(error: Variant)->void: if error: print("Error resetting" % error) else: print("Success") ) .. _gamecenter_gamecenterguide__realtime-matchmaking: Realtime Matchmaking ==================== * `GKMatch `__ * `GKMatchRequest `__ .. _gamecenter_gamecenterguide__events: Events ------ You can use the convenience ``request_match`` method after configuring your request, and on your callback setup the ``game_match`` to track the various states of the match, like this: .. code-block:: gdscript var req = GKMatchRequest.new() req.max_players = 2 req.min_players = 1 req.invite_message = "Join me in a quest to fun" GKMatchmakerViewController.request_match(req, func(game_match: GKMatch, error: Variant)->void: if error: print("Could not request a match %s" % error) else: print("Got a match!") game_match.data_received.connect(func (data: PackedByteArray, from_player: GKPlayer)->void: print("Received data from Player") ) game_match.data_received_for_recipient_from_player.connect(func(data: PackedByteArray, for_recipient: GKPlayer, from_remote_player: GKPlayer)->void: print("Received data from a player to another player") ) gameMatch.did_fail_with_error.connect(func(error: String)->void: print("Match failed with %s" % error) ) gameMatch.should_reinvite_disconnected_player = (func(player: GKPlayer)->bool: # We always reinvite return true ) gameMatch.player_changed.connect(func(player: GKPlayer, connected: bool)->void: print("Status of player changed to %s" % connected) ) ) .. _gamecenter_gamecenterguide__disconnect: Disconnect ---------- .. code-block:: gdscript game_match.disconnect() .. _gamecenter_gamecenterguide__send-to-all: Send to all ----------- .. code-block:: gdscript var data = "How do you do fellow kids".to_utf8_buffer() game_match.send_data_to_all_players(data, GKMatch.SendDataMode.reliable) .. _gamecenter_gamecenterguide__send-to-players: Send to Players --------------- .. code-block:: gdscript game_match.send(array, [first_player, second_player], GKMatch.SendDataMode.reliable) .. _gamecenter_gamecenterguide__rule-based-matchmaking-properties-ios-macos-17-2: Rule-Based Matchmaking Properties (iOS/macOS 17.2+) --------------------------------------------------- You can attach properties to ``GKMatchRequest`` to let Game Center use rule-based matching data, and inspect those values from ``GKMatch`` once a match is created. .. code-block:: gdscript var request := GKMatchRequest.new() request.min_players = 2 request.max_players = 2 request.queue_name = "ranked_duo" request.properties = { "region": "us-east", "skill_bucket": 12, "mode": "ranked" } # Keys can be a player's game_player_id (or GKPlayer object). request.recipient_properties = { friend_player.game_player_id: {"role": "support"} } On the resulting ``GKMatch``: .. code-block:: gdscript print(game_match.properties) print(game_match.player_properties) # Dictionary keyed by game_player_id .. _gamecenter_gamecenterguide__invite-acceptance-event: Invite Acceptance Event ----------------------- ``GKLocalPlayer`` now emits an ``invite_accepted`` signal when an invite is accepted. Use it to immediately resolve to a ``GKMatch``. .. code-block:: gdscript var local := game_center.local_player var matchmaker := GKMatchmaker.new() func _ready() -> void: local.register_listener() local.invite_accepted.connect(func(player: GKPlayer, invite: GKInvite) -> void: matchmaker.match_for_invite(invite, func(match: GKMatch, error: Variant) -> void: if error: print("Invite match error: %s" % error) else: print("Joined invited match") ) ) .. _gamecenter_gamecenterguide__turn-based-matchmaking: Turn-Based Matchmaking ====================== Use ``GKTurnBasedMatch`` for asynchronous turn-based sessions, and register a ``GKLocalPlayer`` listener to receive turn-based events. .. code-block:: gdscript var local := game_center.local_player func _ready() -> void: local.register_listener() local.turn_event_received.connect(func(player: GKPlayer, match: GKTurnBasedMatch, did_become_active: bool) -> void: print("Turn event for match %s (active=%s)" % [match.match_id, did_become_active]) ) local.match_requested_with_other_players.connect(func(player: GKPlayer, recipients: Array) -> void: print("Turn-based request with %d recipients" % recipients.size()) ) local.turn_based_match_ended.connect(func(player: GKPlayer, match: GKTurnBasedMatch) -> void: print("Turn-based match ended: %s" % match.match_id) ) Create/find and advance a turn: .. code-block:: gdscript var request := GKMatchRequest.new() request.min_players = 2 request.max_players = 2 GKTurnBasedMatch.find(request, func(match: GKTurnBasedMatch, error: Variant) -> void: if error: print("Find turn-based match failed: %s" % error) return match.load_match_data(func(data: PackedByteArray, load_error: Variant) -> void: if load_error: print("Load match data error: %s" % load_error) return var updated_data := "next turn payload".to_utf8_buffer() var next_participants: Array = match.participants match.end_turn( next_participants, 604800.0, # one week default timeout updated_data, func(end_error: Variant) -> void: if end_error: print("End turn error: %s" % end_error) ) ) ) Exchange-related signals: * ``exchange_received(player, exchange, match)`` * ``exchange_canceled(player, exchange, match)`` * ``exchange_completed(player, replies, match)`` * ``player_wants_to_quit_match(player, match)`` .. _gamecenter_gamecenterguide__turn-based-matchmaker-ui: Turn-Based Matchmaker UI ------------------------ You can present Apple's built-in turn-based matchmaking UI and handle its delegate callbacks through signals. .. code-block:: gdscript var request := GKMatchRequest.new() request.min_players = 2 request.max_players = 2 var controller := GKTurnBasedMatchmakerViewController.create_controller(request) controller.did_find_match.connect(func(match: GKTurnBasedMatch) -> void: print("Found turn-based match %s" % match.match_id) ) controller.cancelled.connect(func(_: String) -> void: print("Turn-based matchmaking cancelled") ) controller.failed_with_error.connect(func(message: String) -> void: print("Turn-based matchmaking failed: %s" % message) ) controller.present() .. _gamecenter_gamecenterguide__challenges: Challenges ========== ``GKLocalPlayer`` now emits challenge-related signals once you call ``register_listener()``. .. code-block:: gdscript local.challenge_received.connect(func(player: GKPlayer, challenge: GKChallenge) -> void: print("Challenge from: %s" % (challenge.issuing_player.display_name if challenge.issuing_player else "unknown")) print("Message: %s" % challenge.message) ) local.challenge_completed.connect(func(player: GKPlayer, challenge: GKChallenge, friend_player: GKPlayer) -> void: print("Challenge completed by %s" % friend_player.display_name) ) Load currently received challenges: .. code-block:: gdscript GKChallenge.load_received_challenges(func(challenges: Array, error: Variant) -> void: if error: print("Load challenges error: %s" % error) else: for challenge in challenges: print("Challenge type: %s state: %s" % [challenge.challenge_type, challenge.state]) ) .. _gamecenter_gamecenterguide__leaderboards: Leaderboards ============ .. _gamecenter_gamecenterguide__report-score: Report Score ------------ .. code-block:: gdscript GKLeaderboard.load_leaderboards(PackedStringArray(["MyLeaderboard"]), func(leaderboards: Array [GKLeaderboard], error: Variant)->void: var score = 100 var context = 0 if error: print("Error loading leaderboard %s" % error) else: leaderboards[0].submit_score(score, context, local, func(error: Variant)->void: if error: print("Error submitting leadeboard %s" % error) ) ) .. _gamecenter_gamecenterguide__load-leaderboards: Load Leaderboards ----------------- .. code-block:: text # Loads all leaderboards GKLeaderboard.load_leaderboards(PackedStringArray(), func(leaderboards: Array [GKLeaderboard], error: Variant)->void: if error: print("Error loading leaderboards %s" % error) else: print("Got %s" % leaderboards) ) # Load specific ones GKLeaderboard.load_leaderboards(PackedStringArray(["My leaderboard"]), func(leaderboards: Array [GKLeaderboard], error: Variant)->void: if error: print("Error loading leaderboard %s" % error) else: print("Got %s" % leaderboards) ) .. _gamecenter_gamecenterguide__load-scores: Load Scores -----------