石头、纸和剪刀
我将演示如何使用 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