SpriteKit的SKScene中实现Multipeer Connectivity的技术咨询
Hey there! Let's figure out how to get Multipeer Connectivity working smoothly with your SpriteKit GameplayScene—you’re exactly right that the error stems from SKScene not being a view controller, but we can fix this with a clean, modular approach.
The Core Idea
Instead of forcing your SKScene to act like a view controller (by conforming to MCBrowserViewControllerDelegate), we’ll split responsibilities:
- A dedicated manager class handles all Multipeer Connectivity logic and protocol conformance.
- Your view controller (the one presenting the
SKScene) takes care of presenting theMCBrowserViewController(since only view controllers can present other view controllers). - Your
GameplaySceneinteracts with the manager to send/receive data and triggers the browser via a callback to the view controller.
Step 1: Create a Multipeer Connectivity Manager
This class will encapsulate all session handling, advertising, browsing, and protocol methods. It keeps your scene clean and focused on game logic.
import MultipeerConnectivity class MultipeerManager: NSObject, MCSessionDelegate, MCBrowserViewControllerDelegate { var session: MCSession! let peerID = MCPeerID(displayName: UIDevice.current.name) private var advertiser: MCAdvertiserAssistant? // Callbacks to pass events back to your scene var onPeerConnected: ((MCPeerID) -> Void)? var onPeerDisconnected: ((MCPeerID) -> Void)? var onReceivedData: ((Data, MCPeerID) -> Void)? override init() { super.init() session = MCSession( peer: peerID, securityIdentity: nil, encryptionPreference: .required ) session.delegate = self } // Start advertising your game service func startAdvertising() { advertiser = MCAdvertiserAssistant( serviceType: "your-game-id", // Replace with a unique service type discoveryInfo: nil, session: session ) advertiser?.start() } // Get the browser view controller (to be presented by your VC) func makeBrowserViewController() -> MCBrowserViewController { let browser = MCBrowserViewController( serviceType: "your-game-id", session: session ) browser.delegate = self return browser } // Send data to connected peers func sendData(_ data: Data, to peers: [MCPeerID] = []) { let targetPeers = peers.isEmpty ? session.connectedPeers : peers do { try session.send(data, toPeers: targetPeers, with: .reliable) } catch { print("Failed to send data: \(error.localizedDescription)") } } // MARK: - MCSessionDelegate func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { switch state { case .connected: onPeerConnected?(peerID) case .disconnected: onPeerDisconnected?(peerID) default: break } } func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { onReceivedData?(data, peerID) } // Required empty implementations for other delegate methods func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {} func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {} func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {} // MARK: - MCBrowserViewControllerDelegate func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) { browserViewController.dismiss(animated: true) } func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) { browserViewController.dismiss(animated: true) } }
Step 2: Update Your View Controller
The view controller that presents your GameplayScene will hold the manager and handle presenting the browser when the scene requests it.
import UIKit import SpriteKit class GameViewController: UIViewController { private let multipeerManager = MultipeerManager() override func viewDidLoad() { super.viewDidLoad() guard let skView = view as? SKView else { return } let gameplayScene = GameplayScene(size: skView.bounds.size) // Pass the manager to the scene gameplayScene.multipeerManager = multipeerManager // Set up a callback to present the browser when the scene asks gameplayScene.onRequestBrowser = { [weak self] in guard let self = self else { return } let browser = self.multipeerManager.makeBrowserViewController() self.present(browser, animated: true) } skView.presentScene(gameplayScene) multipeerManager.startAdvertising() } }
Step 3: Integrate with Your GameplayScene
Now your scene can use the manager to handle communication, without needing to conform to view controller-specific protocols.
import SpriteKit class GameplayScene: SKScene { var multipeerManager: MultipeerManager! var onRequestBrowser: (() -> Void)? // Callback to trigger browser presentation override func didMove(to view: SKView) { super.didMove(to: view) // Set up manager callbacks to handle game-specific logic multipeerManager.onReceivedData = { [weak self] data, peerID in self?.handleReceivedGameData(data, from: peerID) } multipeerManager.onPeerConnected = { peerID in print("Peer joined: \(peerID.displayName)") // Send initial game state to the new peer here } // Add a simple button to trigger the browser let connectButton = SKLabelNode(text: "Connect to Player") connectButton.position = CGPoint(x: size.width/2, y: size.height/2) connectButton.name = "connectButton" addChild(connectButton) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let touchLocation = touch.location(in: self) if atPoint(touchLocation).name == "connectButton" { onRequestBrowser?() // Ask the VC to present the browser } } // Example: Handle incoming game data (e.g., player position) private func handleReceivedGameData(_ data: Data, from peerID: MCPeerID) { if let playerPosition = try? JSONDecoder().decode(CGPoint.self, from: data) { // Update your scene's player node with the received position if let playerNode = childNode(withName: "remotePlayer") { playerNode.position = playerPosition } } } // Example: Send your player's position to peers func sendPlayerPosition(_ position: CGPoint) { guard let data = try? JSONEncoder().encode(position) else { return } multipeerManager.sendData(data) } } // Helper extension to make CGPoint codable for easy data transfer extension CGPoint: Codable { enum CodingKeys: String, CodingKey { case x, y } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(x, forKey: .x) try container.encode(y, forKey: .y) } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) x = try container.decode(CGFloat.self, forKey: .x) y = try container.decode(CGFloat.self, forKey: .y) } }
Why This Works
- Separation of Concerns: The manager handles all Multipeer logic, your scene handles game rendering/input, and the VC handles view presentation—no one class does too much.
- No Protocol Conflict: You avoid forcing
SKSceneto conform toMCBrowserViewControllerDelegate, which requires view controller context to present the browser. - Reusability: The
MultipeerManagercan be reused across other scenes or projects without modification.
内容的提问来源于stack exchange,提问作者B.Toaster

