Game Center 管理器

GameCenterManager 提供了一种简单的方法,可以将 Game Center 回合制多人游戏支持添加到应用程序中。

支持

如果您觉得 GameCenterManager 有用,并希望帮助支持其持续开发和维护,请考虑进行小额捐赠,尤其是在商业产品中使用它时。

Buy Me A Coffee

正是因为有像您这样的贡献者的支持,我才能继续免费构建、发布和维护像 GameCenterManager 这样高质量、文档齐全的 Swift Package。

安装

Swift Package Manager(Xcode 11 及以上版本)

  1. 在 Xcode 中,选择 File > Add Package Dependency… 菜单项。
  2. https://github.com/Appracatappra/GameCenterManager.git 粘贴到对话框中。
  3. 按照 Xcode 的说明完成安装。

为什么不使用 CocoaPods、Carthage 等?

支持多个依赖管理器会使维护库的复杂性和耗时呈指数级增长。

由于 Swift Package Manager 已集成到 Xcode 11(及更高版本)中,因此它是未来支持的最简单选择。

概述

通过使用 GameCenterManagerMultiplayerGameManager,您可以大大减少支持应用程序中 Game Center 回合制多人游戏所需的样板代码量。

连接 GameCenterManager 事件

在您的游戏视图启动之前,您需要连接 `GameCenterManage` 事件。 您可以在主应用程序中使用以下代码

import SwiftUI
import SwiftletUtilities
import LogManager
import SwiftUIKit

@main
struct PackageTesterApp: App {
    @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
    @Environment(\.scenePhase) private var scenePhase
    @Environment(\.colorScheme) var colorScheme
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) { oldScenePhase, newScenePhase in
            switch newScenePhase {
            case .active:
                Debug.info(subsystem: "PackageTesterApp", category: "Scene Phase", "App is active")
            case .inactive:
                Debug.info(subsystem: "PackageTesterApp", category: "Scene Phase", "App is inactive")
            case .background:
                Debug.info(subsystem: "PackageTesterApp", category: "Scene Phase", "App is in background")
            @unknown default:
                Debug.notice(subsystem: "PackageTesterApp", category: "Scene Phase", "App has entered an unexpected scene: \(oldScenePhase), \(newScenePhase)")
            }
        }
    }
}

/// Class the handle the event that would typically be handled by the Application Delegate so they can be handled in SwiftUI.
class AppDelegate: NSObject, UIApplicationDelegate {
    
    /// Handles the app finishing launching
    /// - Parameter application: The app that has started.
    func applicationDidFinishLaunching(_ application: UIApplication) {
        // Register to receive remote notifications
        UIApplication.shared.registerForRemoteNotifications()
    }
    
    /// Handle the application getting ready to launch
    /// - Parameters:
    ///   - application: The application that is going to launch.
    ///   - launchOptions: Any options being passed to the application at launch time.
    /// - Returns: Returns `True` if the application can launch.
    func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    
        // Wireup Game Center Events
        GameCenterManager.shared.gameStateEncoder = {
            // TODO: Insert your code here to encode your game state.
            return MasterDataStore.shared.gameState.encoded
        }
        
        GameCenterManager.shared.gameStateDecoder = { data in
            // TODO: Insert your code here to decode your game state. Return `true` if successfully decoded.
            if let state = MurderCase(from: data) {
                MasterDataStore.shared.gameState = state
                return true
            } else {
                return false
            }
        }
        
        GameCenterManager.shared.playerTurnEnd = { player, participants in
            // TODO: Handle the player's turn ending.
            MasterDataStore.shared.gameState.playerTurnEnded(player: player, participants: participants)
        }
        
        GameCenterManager.shared.playerQuitInTurn = { player, participants in
            // TODO: Handle the player quitting in-turn.
            MasterDataStore.shared.gameState.playerQuit()
        }
        
        GameCenterManager.shared.playerQuitOutOfTurn = { player in
            // TODO: Handle the player quitting out-of-turn.
            MasterDataStore.shared.gameState.playerQuit()
        }
        
        GameCenterManager.shared.playerWonGame = { player in
            // TODO: Handle a player winning the game.
            MasterDataStore.shared.gameState.playerWon(player: player)
        }
        
        GameCenterManager.shared.playerLostGame = { playerName in
            // TODO: Handle the player losing the game.
            MasterDataStore.shared.gameState.playerLost(playerName: playerName)
        }
        
        GameCenterManager.shared.startNewGame = {
            // TODO: Handle a new game starting.
            if let match = GameCenterManager.shared.currentMatch {
                MasterDataStore.shared.gameState = MurderCase.BuildMurder(numberOfPlayers: match.participants.count, isMultiplayer: true)
            }
        }
        
        GameCenterManager.shared.changeView = { matchState in
            // TODO: Handle a request to switch view based on the match state.
            switch matchState {
            case .ended:
                MasterDataStore.shared.gameState.endOfGameStats()
                MasterDataStore.shared.changeView(newView: .gameLobby)
            case .open, .matching:
                MasterDataStore.shared.gameState.showGameboard()
            default:
                MasterDataStore.shared.changeView(newView: .gameLobby)
            }
        }
        
        GameCenterManager.shared.playerMatchEvent = { player in
            // TODO: Handle the player receiving a match event (such as another player making a move).
            MasterDataStore.shared.gameState.assignPlayerToDetective(teamPlayerId: player.displayName)
            MasterDataStore.shared.gameState.setCurrentPlayer()
            MultiplayerConversations.startTurn()
        }
        
        // Informthe app that the launch has completed successfully.
        return true
    }
    
    /// Handles the app receiving a remote notification
    /// - Parameters:
    ///   - application: The app receiving the notifications.
    ///   - userInfo: The info that has been sent to the App.
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
        
    }
}

有了这段代码,在 func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool 中进行任何样式更改,它们将应用于之后构建的所有视图。

ConnectToGameCenter 辅助视图

以下 ConnectToGameCenter 辅助视图可以更轻松地连接到游戏中心,从而接管了许多所需的样板代码

import SwiftUI
import SwiftletUtilities
import GameKitUI
import GameKit
import GameCenterManager
import StoreKit
import LogManager
import AppStoreManager
import SoundManager
import SwiftUIKit

struct ConnectToGameCenter: View {
    typealias AccessPointvent = () -> Bool
    
    var location:GKAccessPoint.Location = .topTrailing
    var shouldDisplayAccessPoint:AccessPointvent? = nil
    
    @State private var checkForGameCenter:Bool = true
    
    var body: some View {
        if checkForGameCenter {
            GKAuthenticationView(failed: {error in
                Debug.info(subsystem: "Game Center", category: "Login", "Failed: \(error.localizedDescription)")
                Execute.onMain {
                    GameCenterManager.shared.isGameCenterEnabled = false
                    checkForGameCenter = false
                }
            }, authenticated: {player in
                Debug.info(subsystem: "Game Center", category: "Login", "Hello \(player.displayName)")
                GKAccessPoint.shared.location = location
                
                // Should we display the access point?
                if let test = shouldDisplayAccessPoint {
                    if test() {
                        GKAccessPoint.shared.isActive = GKLocalPlayer.local.isAuthenticated
                    } else {
                        GKAccessPoint.shared.isActive = false
                    }
                } else {
                    GKAccessPoint.shared.isActive = GKLocalPlayer.local.isAuthenticated
                }
                
                // Has a listener been registered
                if GameCenterManager.shared.currentGameManager == nil && GKLocalPlayer.local.isAuthenticated {
                    GameCenterManager.shared.currentGameManager = MultiplayerGameManager()
                        GKLocalPlayer.local.register(GameCenterManager.shared.currentGameManager!)
                    Debug.info(subsystem: "Game Center", category: "Multiplayer Game", "Game Manager registered")
                    }
                
                Execute.onMain {
                    GameCenterManager.shared.allowMultiplayer = (GKLocalPlayer.local.isAuthenticated && !GKLocalPlayer.local.isMultiplayerGamingRestricted)
                    GameCenterManager.shared.isGameCenterEnabled = true
                    checkForGameCenter = false
                }
            })
        }
    }
}

#Preview {
    ConnectToGameCenter()
}

在应用程序的第一个视图中使用此代码将玩家连接到 Game Center

import SwiftUI
import SwiftletUtilities
import GameKitUI
import GameKit
import GameCenterManager
import StoreKit
import LogManager
import AppStoreManager
import SoundManager
import SwiftUIKit
import SpeechManager

struct MainMenuLandscape: View {
    @ObservedObject var dataStore = MasterDataStore.shared
    
    var body: some View {
        ZStack {
            ...
            
            ConnectToGameCenter() {
                // TODO: Switch to the correct view when connected. 
                return (dataStore.currentView == .menuView)
            }
        } // End of ZStack
        .onDisappear() {
            GKAccessPoint.shared.isActive = false
        }
    }
}

#Preview {
    MainMenuLandscape()
}

使用 MultiplayerGameManger

MultiplayerGameManger 类允许您将游戏状态更改发送到 Game Center,用于您的回合制应用程序。 以下是通过静态调用 MultiplayerGameManger 最常用的功能

文档

GraceLanguage Package 包括其所有功能的完整 DocC 文档