GameKitDemo
Demo GameKit
Install / Use
/learn @Enryun/GameKitDemoREADME
Multiplayer Counter Demo with GameKit
https://github.com/user-attachments/assets/85a9807b-00bf-47da-bfe1-4e1269c08588
A simple SwiftUI multiplayer demo using GameKit, where two players can increment a counter, and the updated value is synchronized between devices in real-time.
Table of Contents
Introduction
This project demonstrates how to implement real-time multiplayer functionality in a SwiftUI app using GameKit. It provides a simple counter that two (or more) players can increment, and the counter value is synchronized between both players' devices. The project serves as a starting point for building more complex multiplayer games or apps.
Features
- Real-time multiplayer using GameKit.
- Asynchronous programming with Swift's async/await.
- State management with @Published properties and ObservableObject.
- Clean and maintainable code with MVVM architecture.
- Automatic matchmaking with Game Center.
- Error handling and user feedback.
- Supports iOS 15.0 and above.
Requirements
- Xcode 13.0 or later.
- Swift 5.9 or later.
- iOS 15.6 or later.
- Two devices or simulators signed into Game Center with different accounts.
Installation
- Update the bundle identifier to a unique value associated with your Apple Developer account.
- Go to your project's Signing & Capabilities and set up the necessary capabilities:
- Make sure to create the Project in AppstoreConnect and enable GameKit else it will not work in real devices:
Usage
Starting a Match
- Open the app on both devices or simulators.
- Initiate Matchmaking
- On each device, tap the Find Match button.
- The Game Center matchmaking UI will appear.
- Connect with Another Player
https://github.com/user-attachments/assets/85a9807b-00bf-47da-bfe1-4e1269c08588
- The matchmaking service will automatically connect the two devices.
- Once connected, the game will start automatically.
- On either device, tap the Increment Counter button.
- The updated counter value will appear on both devices.
- Repeat tapping the button on either device to continue incrementing.
Tap the End Game button to disconnect and return to the main menu.
ProjectStructure
Models
MultiplayerState: Enum representing the different states of the multiplayer session.AuthenticationError: Enum for handling authentication-related errors.
ViewModels
MultiplayerViewModel: Manages the game logic, state transitions, and communication with GameKitManager.
@MainActor
final class MultiplayerViewModel: ObservableObject {
@Published var state: MultiplayerState = .idle
@Published var connectedPlayers: [GKPlayer] = []
@Published var counter: Int = 0
private var isHost: Bool = false
// Initialization and subscriptions...
func incrementCounter() {
counter += 1
sendCounterUpdate()
}
private func sendCounterUpdate() {
let messageData = [
"action": "updateCounter",
"counterValue": counter
] as [String : Any]
do {
let data = try JSONSerialization.data(withJSONObject: messageData, options: [])
sendData(data)
} catch {
state = .error("Failed to send counter update: \(error.localizedDescription)")
}
}
// Other methods...
}
Views
MultiplayerView: The main SwiftUI view that updates based on the MultiplayerViewModel's state.
Managers
GameKitManager: Handles all Game Center-related functionality, including authentication, matchmaking, and data transmission.
final class GameKitManager: NSObject, ObservableObject {
static let shared = GameKitManager()
// Authentication Properties
@Published private(set) var isAuthenticated: Bool = GKLocalPlayer.local.isAuthenticated
@Published var authenticationError: AuthenticationError?
@Published var authenticationViewController: UIViewController?
// Multiplayer Properties
@Published var currentMatch: GKMatch?
@Published var matchError: Error?
@Published var receivedData: (data: Data, player: GKPlayer)?
@Published var playerConnectionState: (player: GKPlayer, state: GKPlayerConnectionState)?
func findMatch(minPlayers: Int, maxPlayers: Int, viewController: UIViewController) throws {
guard isAuthenticated else {
throw AuthenticationError.custom(message: "Local player is not authenticated. Please sign in to Game Center.")
}
let request = GKMatchRequest()
request.minPlayers = minPlayers
request.maxPlayers = maxPlayers
let mmvc = GKMatchmakerViewController(matchRequest: request)
mmvc?.matchmakerDelegate = self
if let mmvc {
viewController.present(mmvc, animated: true)
}
}
func sendData(_ data: Data, mode: GKMatch.SendDataMode = .reliable) throws {
guard let match = currentMatch else {
throw NSError(domain: "No active match", code: 0, userInfo: nil)
}
try match.sendData(toAllPlayers: data, with: mode)
}
func disconnectMatch() {
currentMatch?.disconnect()
currentMatch = nil
}
// Other methods...
}
Author
James Thang, find me on LinkedIn
Learn more about SwiftUI, check out my book :books: on Amazon
