Skip to content

Enryun/GameKitDemo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

Multiplayer Counter Demo with GameKit

iOS 15.0+

IMG_0974.-.ROTATE.-.Videobolt.net.mp4

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

  1. Introduction
  2. Features
  3. Requirements
  4. Installation
  5. Usage
  6. ProjectStructure
  7. Author

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:
GameKit2
  • Make sure to create the Project in AppstoreConnect and enable GameKit else it will not work in real devices:
GameKit3

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
IMG_0974.-.ROTATE.-.Videobolt.net.mp4
  • 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 📚 on Amazon

About

Demo GameKit

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages