七爪源码:使用 Multipeer Connectivity 框架和 SwiftUI 4 构建游戏

七爪源码:使用 Multipeer Connectivity 框架和 SwiftUI 4 构建游戏

首页冒险解谜多工艺构建更新时间:2024-08-03

石头、纸和剪刀

我将演示如何使用 swiftUI 4 实现一个基本的 Multipeer 连接应用程序,不需要 UIKit!

事不宜迟,让我们开始吧!

应用结构

我们的应用程序的基本结构如下:

游戏将是一个基本的“石头、纸、剪刀”游戏。用户将相互配对,然后他们将看到三个选项,石头、纸或剪刀。当用户选择一个动作时,它将被发送到对手的设备,一旦计时器到了,结果就会显示出来。

有了基本的概述,让我们深入研究一些代码。

代码

我们将从创建 MultipeerSession 对象开始。首先,我们需要将 MultipeerConnectivity 导入到我们的类中,并继承 NSObject 和 ObrvableObject。

class RPSMultipeersession: NSObject, ObservableObject { private let serviceType = "rps-service" private var myPeerID: MCPeerID public let serviceAdvertiser: MCNearbyServiceAdvertiser public let serviceBrowser: MCNearbyServiceBrowser public let session: MCSession }

在这里,我们创建了一个 serviceType 字符串,它将让其他正在扫描对等点的设备知道我们正在使用 RPS 应用程序并且仅在寻找 RPS 对等点。 这个字符串可以是任何东西来区分我们的 Multipeer 服务和其他服务。 然后我们创建一些实例变量来保存我们的 MCPeerID、MCNearbyServiceAdvertiser、MCNearbyServiceBrowser 和 MCSession。 这些字段需要公开,以便我们可以在 RPSMultipeerSession 类之外对它们执行操作。

在我们对象的 init() 内部,我们需要为上面创建的变量赋值。

init(username: String) { myPeerID = MCPeerID(displayName: username) session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .none) serviceAdvertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: serviceType) serviceBrowser = MCNearbyServiceBrowser(peer: peerID, serviceType: serviceType) super.init() }

在我们的应用程序中,我们将允许用户创建用户名,以便更轻松地发现对等点。在这里,我们将提供的用户名作为初始化程序中的参数,并从中创建一个 MCPeerID。

同样在初始化器内部,我们创建:

并且不要忘记调用 super.init() 来调用超类的 init 方法!

接下来,我们需要考虑如何从对等方接收数据。稍后,我们将为我们的会话对象创建一个委托,该委托将从我们的对等方接收一个数据对象,然后我们可以将其转换为更易于使用的东西。

由于我们实际上只有四个选项(石头、纸、剪刀和无),我们将使用枚举来使处理响应更具可读性和更容易使用。

像这样放置在与我们的 RPSMultipeerSession 类相同的文件中就足够了:

enum Move: String, CaseIterable, CustomStringConvertible { case rock, paper, scissors, unknown var description : String { switch self { case .rock: return "Rock" case .paper: return "Paper" case .scissors: return "Scissors" default: return "Thinking" } } }

稍后我们将使用移动的字符串表示来向我们的玩家显示图像。 通过使用 CustomStringConvertible,我们可以减少执行此操作所需的代码量。

现在我们已经创建并可用了 Move 枚举,我们应该考虑需要为我们的视图提供什么类型的数据。 我们知道,我们的 PairView 将允许玩家找到并与他们的朋友配对,它需要访问当前可用同伴的列表。

同样的视图需要知道我们何时收到来自其他玩家的邀请,以及该玩家是谁。 GameView 需要知道我们何时收到对手的动作。

我们的多个视图可能会发现了解我们当前是否与玩家配对很有用,最后,我们的 PairView 需要有某种方式来接受或拒绝来自另一个玩家的邀请。

@Published var availablePeers: [MCPeerID] = [] @Published var receivedMove: Move = .unknown @Published var recvdInvite: Bool = false @Published var recvdInviteFrom: MCPeerID? = nil @Published var paired: Bool = false @Published var invitationHandler: ((Bool, MCSession?) -> Void)?

总之,我们的 RPSMultipeerSession 中将有六个 @Published 属性。使这些变量@Published 使我们的视图不仅可以看到变量的值,而且可以在值更改时通知它们。

除此之外,我们需要为我们的会话、serviceAdvertiser 和 serviceBrowser 创建一些委托。让我们从最长的 MCSessionDelegate 开始。

会话委托具有处理方法:

我们实际上只关心其中两种方法:当对等方更改状态时以及我们从用户接收数据时。即使是这种情况,这些方法中的每一个都需要在委托内部实现。

Swift 有一个简洁的特性,叫做扩展。如果您不熟悉,扩展基本上可以让您将代码添加到任何 Swift 类。

可以在 String 类上创建一个扩展来对字符串执行任何类型的操作。扩展非常强大,我强烈建议您查看细节,但现在这应该是让我们继续前进的充分介绍。

为了防止我们的 RPSMultipeerSession 变得太大而无法处理,我们将利用 Swift 的扩展来实现这些委托。我们可以简单地做:

extension RPSMultipeerSession: MCSessionDelegate

并在那里实现委托函数,在主类之外,但仍然在同一个文件中。可以将这些代表放在单独的文件中,但我个人选择将它们全部放在一个文件中。

extension RPSMultipeerSession: MCSessionDelegate { func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { log.info("peer \(peerID) didChangeState: \(state.rawValue)") switch state { case MCSessionState.notConnected: // Peer disconnected break case MCSessionState.connected: // Peer connected break default: // Peer connecting or something else break } } func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { if let string = String(data: data, encoding: .utf8), let move = Move(rawValue: string) { // Received move from peer } else { log.info("didReceive invalid value \(data.count) bytes") } } public func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { log.error("Receiving streams is not supported") } public func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { log.error("Receiving resources is not supported") } public func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { log.error("Receiving resources is not supported") } public func session(_ session: MCSession, didReceiveCertificate certificate: [Any]?, fromPeer peerID: MCPeerID, certificateHandler: @escaping (Bool) -> Void) { certificateHandler(true) } }

就像我之前说的,这是一个*大*。 确保使用 Xcode 的自动完成功能来获得声明的函数。

如您所见,大多数函数只是向控制台打印一行,实际上根本不做任何事情。 这是因为我们的应用程序不支持发送或接收流或资源。 不过,这可能会随着教程的进行而改变。

我们还没有完成! 如果您按照代码进行操作,您可能已经注意到委托实际上并没有做任何事情。 我们需要实现响应对等连接状态变化和接收对手数据的逻辑。 我将在第 2 部分详细介绍如何处理这些事件,所以现在,让我们继续。

接下来,我们将实现 MCNearbyServiceAdvertiserDelegate。 这个更容易消化:

extension RPSMultipeerSession: MCNearbyServiceAdvertiserDelegate { func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: Error) { log.error("ServiceAdvertiser didNotStartAdvertisingPeer: \(String(describing: error))") } func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { log.info("didReceiveInvitationFromPeer \(peerID)") } }

服务广告主有两种方法:一种是在广告主由于某种原因无法开始投放广告时调用,另一种是在我们收到其他玩家的邀请时调用。 后者将再次在第 2 部分中实施!

最后但同样重要的是,我们需要实现 MCNearbyServiceBrowserDelegate。

extension RPSMultipeerSession: MCNearbyServiceBrowserDelegate { func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) { //TODO: Tell the user something went wrong and try again log.error("ServiceBroser didNotStartBrowsingForPeers: \(String(describing: error))") } func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { log.info("ServiceBrowser found peer: \(peerID)") // Add the peer to the list of available peers } func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { log.info("ServiceBrowser lost peer: \(peerID)") // Remove lost peer from list of available peers } }

此委托具有在以下情况下调用的方法:

现在我们已经设置好了代理,我们可以将它们应用到我们的会话、serviceAdvertiser 和 serviceBrowser。

session.delegate = self serviceAdvertiser.delegate = self serviceBrowser.delegate = self serviceAdvertiser.startAdvertisingPeer() serviceBrowser.startBrowsingForPeers()

在调用 super.init() 之后,我们将它添加到我们的 init() 中。 这将分配代表并开始向/为同行做广告和浏览。

我们快完成了,但我们不能忘记告诉我们的广告商和浏览器在 deinit() 内部停止

deinit { serviceAdvertiser.stopAdvertisingPeer() serviceBrowser.stopBrowsingForPeers() }

现在我们已经完成了所有这些,我们的 RPSMultipeerSession.swift 文件应该如下所示:

// // RPSMultipeerSession.swift // RPS // // Created by Joe Diragi on 7/28/22. // import MultipeerConnectivity import os enum Move: String, CaseIterable, CustomStringConvertible { case rock, paper, scissors, unknown var description : String { switch self { case .rock: return "Rock" case .paper: return "Paper" case .scissors: return "Scissors" default: return "Thinking" } } } class RPSMultipeerSession: NSObject, ObservableObject { private let serviceType = "rps-service" private var myPeerID: MCPeerID public let serviceAdvertiser: MCNearbyServiceAdvertiser public let serviceBrowser: MCNearbyServiceBrowser public let session: MCSession private let log = Logger() @Published var availablePeers: [MCPeerID] = [] @Published var receivedMove: Move = .unknown @Published var recvdInvite: Bool = false @Published var recvdInviteFrom: MCPeerID? = nil @Published var paired: Bool = false @Published var invitationHandler: ((Bool, MCSession?) -> Void)? init(username: String) { let peerID = MCPeerID(displayName: username) self.myPeerID = peerID session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .none) serviceAdvertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: serviceType) serviceBrowser = MCNearbyServiceBrowser(peer: peerID, serviceType: serviceType) super.init() session.delegate = self serviceAdvertiser.delegate = self serviceBrowser.delegate = self serviceAdvertiser.startAdvertisingPeer() serviceBrowser.startBrowsingForPeers() } deinit { serviceAdvertiser.stopAdvertisingPeer() serviceBrowser.stopBrowsingForPeers() } } extension RPSMultipeerSession: MCSessionDelegate { func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { log.info("peer \(peerID) didChangeState: \(state.rawValue)") switch state { case MCSessionState.notConnected: // Peer disconnected break case MCSessionState.connected: // Peer connected break default: // Peer connecting or something else break } } func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { if let string = String(data: data, encoding: .utf8), let move = Move(rawValue: string) { // Received move from peer } else { log.info("didReceive invalid value \(data.count) bytes") } } public func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { log.error("Receiving streams is not supported") } public func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { log.error("Receiving resources is not supported") } public func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { log.error("Receiving resources is not supported") } public func session(_ session: MCSession, didReceiveCertificate certificate: [Any]?, fromPeer peerID: MCPeerID, certificateHandler: @escaping (Bool) -> Void) { certificateHandler(true) } } extension RPSMultipeerSession: MCNearbyServiceAdvertiserDelegate { func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: Error) { log.error("ServiceAdvertiser didNotStartAdvertisingPeer: \(String(describing: error))") } func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { log.info("didReceiveInvitationFromPeer \(peerID)") } } extension RPSMultipeerSession: MCNearbyServiceBrowserDelegate { func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) { //TODO: Tell the user something went wrong and try again log.error("ServiceBroser didNotStartBrowsingForPeers: \(String(describing: error))") } func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { log.info("ServiceBrowser found peer: \(peerID)") // Add the peer to the list of available peers } func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { log.info("ServiceBrowser lost peer: \(peerID)") // Remove lost peer from list of available peers } }

关注七爪网,获取更多APP/小程序/网站源码资源!

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved