import SceneKit import GameKit #if os(macOS) typealias SCNColor = NSColor #else typealias SCNColor = UIColor #endif @MainActor class GameController: NSObject, SCNSceneRendererDelegate, GKLocalPlayerListener, GKMatchmakerViewControllerDelegate, GKMatchDelegate { let sceneRenderer: SCNSceneRenderer var match: GKMatch? var connected = false init(sceneRenderer renderer: SCNSceneRenderer) { sceneRenderer = renderer super.init() sceneRenderer.delegate = self sceneRenderer.scene = SCNScene(named: "Art.scnassets/ship.scn")! } func highlightNodes(atPoint point: CGPoint) { connected.toggle() if connected { connect() sceneRenderer.scene!.rootNode.childNode(withName: "ship", recursively: false)!.childNodes[0].geometry!.firstMaterial!.emission.contents = SCNColor.green } else { disconnect() sceneRenderer.scene!.rootNode.childNode(withName: "ship", recursively: false)!.childNodes[0].geometry!.firstMaterial!.emission.contents = SCNColor.red } } func connect() { GKLocalPlayer.local.register(self) GKLocalPlayer.local.authenticateHandler = { [self] viewController, error in print("authenticateHandler") if let viewController = viewController { #if os(macOS) (sceneRenderer as! NSView).window!.contentViewController!.presentAsSheet(viewController) #else (sceneRenderer as! UIView).window!.rootViewController!.present(viewController, animated: true) #endif } else if let error = error { fatalError(error.localizedDescription) } let request = GKMatchRequest() request.maxPlayers = 2 let viewController = GKMatchmakerViewController(matchRequest: request)! viewController.matchmakerDelegate = self #if os(macOS) GKDialogController.shared().parentWindow = (sceneRenderer as! NSView).window! GKDialogController.shared().present(viewController) #else (sceneRenderer as! UIView).window!.rootViewController!.present(viewController, animated: true) #endif } } func disconnect() { match?.delegate = nil match?.disconnect() GKLocalPlayer.local.unregisterListener(self) } nonisolated func player(_ player: GKPlayer, didAccept invite: GKInvite) { print("did accept invite") MainActor.assumeIsolated { guard let viewController = GKMatchmakerViewController(invite: invite) else { return } viewController.matchmakerDelegate = self #if os(macOS) GKDialogController.shared().parentWindow = (sceneRenderer as! NSView).window! GKDialogController.shared().present(viewController) #else (sceneRenderer as! UIView).window!.rootViewController!.present(viewController, animated: true) #endif } } nonisolated func matchmakerViewControllerWasCancelled(_ viewController: GKMatchmakerViewController) { print("cancelled") MainActor.assumeIsolated { #if os(macOS) GKDialogController.shared().dismiss(self) #else (sceneRenderer as! UIView).window!.rootViewController!.dismiss(animated: true) #endif } } nonisolated func matchmakerViewController(_ viewController: GKMatchmakerViewController, didFind match: GKMatch) { print("did find match") MainActor.assumeIsolated { self.match = match match.delegate = self } } nonisolated func matchmakerViewController(_ viewController: GKMatchmakerViewController, didFailWithError error: Error) { print("matchmaker failed \(error.localizedDescription)") } nonisolated func match(_ match: GKMatch, didFailWithError error: (any Error)?) { print("match failed \(error?.localizedDescription ?? "nil")") } nonisolated func match(_ match: GKMatch, player: GKPlayer, didChange state: GKPlayerConnectionState) { print("player \(player.displayName) did change state \(state.rawValue)") Task { switch state { case .connected: break case .disconnected, .unknown: let matchRequest = GKMatchRequest() matchRequest.recipients = [player] do { try await GKMatchmaker.shared().addPlayers(to: match, matchRequest: matchRequest) print("reinvited \(player.displayName) successfully") } catch { print("reinviting \(player.displayName) failed.\n\(error.localizedDescription)") } @unknown default: break } } } nonisolated func match(_ match: GKMatch, shouldReinviteDisconnectedPlayer player: GKPlayer) -> Bool { return false } nonisolated func match(_ match: GKMatch, didReceive data: Data, fromRemotePlayer player: GKPlayer) { print("did receive \(data)") } }