【iOS-ARKit】创建多用户AR体验-Creating a Multiuser AR Experience

使用MultipeerConnectivity框架在附近设备之间传输ARKit世界地图数据以创建AR体验的共享基础。

Overview

此示例应用程序演示了两个或更多iOS 12设备的简单共享AR体验。 在探索代码之前,请尝试构建并运行应用,以熟悉它演示的用户体验:

  1. 在一台设备上运行应用程序。 您可以查看本地环境,然后点击以在真实世界的表面上放置虚拟3D角色。 (再次点击以放置该角色的多个副本。)
  2. 在第二个设备上运行应用程序。 在两个设备屏幕上,一条消息表明它们已自动加入共享会话。
  3. 点击一个设备上的发送世界地图按钮。 确保其他设备位于发送地图之前第一台设备访问的区域,或者具有与周围环境类似的视图。
  4. 另一台设备显示一条消息,指示它已收到地图并正在尝试使用它。 当这个过程成功时,两个设备都会在相同的真实世界位置上显示虚拟内容,并且在任一设备上点击都会将虚拟内容视为可见。

按照以下步骤查看此应用程序如何使用ARWorldMap类保存和恢复ARKit的空间映射状态,以及MultipeerConnectivity框架在附近设备之间发送世界地图。

运行AR会话并放置AR内容

此应用程序扩展了构建ARKit应用程序的基本工作流程。 (有关详细信息,请参阅构建您的第一个AR体验。)它定义了启用了平面检测的ARWorldTrackingConfiguration,然后在附加到显示AR体验的ARSCNView的ARSession中运行该配置。

当UITapGestureRecognizer在屏幕上检测到轻击时,handleSceneTap方法使用ARKit命中测试在真实世界表面上查找3D点,然后放置标记该位置的ARAnchor。 当ARKit调用委托方法ARSCNView时,应用程序会加载ARSCNView的3D模型以显示在锚点的位置。

连接到同样的设备

示例MultipeerSession类提供了有关此应用程序使用的MultipeerConnectivity功能的简单抽象。 在主视图控制器创建MultipeerSession实例(应用程序启动时)后,它开始运行MCNearbyServiceAdvertiser以广播设备加入多对等会话的能力,并通过MCNearbyServiceBrowser查找其他设备:

session = MCSession(peer: myPeerID, securityIdentity: nil, encryptionPreference: .required)
session.delegate = self

serviceAdvertiser = MCNearbyServiceAdvertiser(peer: myPeerID, discoveryInfo: nil, serviceType: MultipeerSession.serviceType)
serviceAdvertiser.delegate = self
serviceAdvertiser.startAdvertisingPeer()

serviceBrowser = MCNearbyServiceBrowser(peer: myPeerID, serviceType: MultipeerSession.serviceType)
serviceBrowser.delegate = self
serviceBrowser.startBrowsingForPeers()

当MCNearbyServiceBrowser找到另一个设备时,它会调用 browser:foundPeer:withDiscoveryInfo: delegate方法。 要将其他设备邀请到共享会话,请调用browser的 invitePeer:toSession:withContext:timeout:

public func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String: String]?) {
    // Invite the new peer to the session.
    browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
}

当其他设备收到该邀请时,MCNearbyServiceAdvertiser将调用 advertiser:didReceiveInvitationFromPeer:withContext:invitationHandler: delegate方法。 要接受邀请,请调用所提供的invitationHandler:

func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
    // Call handler to accept invitation and join the session.
    invitationHandler(true, self.session)
}

重要:此应用会自动加入找到的第一个附近会话。 根据您想要创建的共享AR体验的种类,您可能希望更精确地控制广播,邀请和接受行为。 有关详细信息,请参阅MultipeerConnectivity文档

在多人会议中,所有参与者根据定义都是平等的; 没有将设备明确分离为主机(或服务器或主机)和来宾(或客户机或从机)角色。 但是,您可能希望为您自己的AR体验定义这些角色。 例如,多人游戏设计可能需要服务器角色来仲裁游戏玩法。 如果您需要按角色分隔对等点,则可以选择适合您应用设计的方式。 例如:

  • 让用户在开始会话之前选择是作为主机还是来宾。 主机使用MCNearbyServiceAdvertiser广播可用性,客人使用MCNearbyServiceBrowser查找要加入的主机。
  • 以同龄人身份加入会话,然后在同伴之间进行协商以提名主人。 (这种方法对于需要主角色的设计是有帮助的,但也允许同行随时加入或离开。)

捕获并发送AR World Map

ARWorldMap对象包含ARKit用于在真实世界空间中定位用户设备的所有空间映射信息的快照。 将地图可靠地共享到其他设备需要两个关键步骤:找到拍摄地图并捕获和发送地图的好时机。

ARKit提供了一个worldMappingStatus值,该值指示当前是捕获世界地图的好时机(还是等到ARKit已映射更多本地环境为好)。 该应用使用该值为其“发送世界地图”按钮提供视觉反馈:

switch frame.worldMappingStatus {
case .notAvailable, .limited:
    sendMapButton.isEnabled = false
case .extending:
    sendMapButton.isEnabled = !multipeerSession.connectedPeers.isEmpty
case .mapped:
    sendMapButton.isEnabled = !multipeerSession.connectedPeers.isEmpty
}
mappingStatusLabel.text = frame.worldMappingStatus.description

当用户点击发送世界地图按钮时,应用程序调用 getCurrentWorldMapWithCompletionHandler: 从正在运行的ARSession捕获地图,然后使用NSKeyedArchiver将其序列化为数据对象,并将其发送到多对话会话中的其他设备:

sceneView.session.getCurrentWorldMap { worldMap, error in
    guard let map = worldMap
        else { print("Error: \(error!.localizedDescription)"); return }
    guard let data = try? NSKeyedArchiver.archivedData(withRootObject: map, requiringSecureCoding: true)
        else { fatalError("can't encode map") }
    self.multipeerSession.sendToAllPeers(data)
}

接收并重新定位到共享地图

当设备接收多方会话中另一个参与方发送的数据时,session:didReceiveData:fromPeer: delegate方法提供该数据。 为了使用它,应用程序使用NSKeyedUnarchiver对ARWorldMap对象进行反序列化,然后使用该映射作为 initialWorldMap: 创建并运行一个新的ARWorldTrackingConfiguration

if let unarchived = try? NSKeyedUnarchiver.unarchivedObject(of: ARWorldMap.classForKeyedUnarchiver(), from: data),
    let worldMap = unarchived as? ARWorldMap {

    // Run the session with the received world map.
    let configuration = ARWorldTrackingConfiguration()
    configuration.planeDetection = .horizontal
    configuration.initialWorldMap = worldMap
    sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

    // Remember who provided the map for showing UI feedback.
    mapProvider = peer
}

然后,ARKit尝试重新定位到新的世界地图 - 也就是说,将接收到的空间映射信息与它感知的本地环境进行协调。 为获得最佳效果:

  1. 在共享世界地图之前,彻底扫描发送设备上的本地环境。
  2. 将接收设备放置在发送设备旁边,以便两者都能看到相同的环境视图。

分享AR内容和用户操作

共享世界地图也共享所有现有的锚点。 在这个应用程序中,这意味着只要接收设备重新定位到世界地图,它就会显示发送设备在捕获并发送世界地图之前放置的所有3D角色。 但是,录制和传输世界地图并重新定位到世界地图是耗时的,带宽密集型操作,所以当新设备加入会话时,您只应采取一次这些步骤。

要创建持续的共享增强现实体验,其中每个用户的操作都会影响其他用户可见的增强现实场景,在每个设备重新定位到同一世界地图后,您应该只共享重新创建每个用户操作所需的信息。 例如,在这个应用程序中,用户可以点击在场景中放置一个虚拟3D角色。 该角色是静态的,所以将角色放置在另一个参与设备上需要的只是角色在世界空间中的位置和方向。

这个应用程序通过在对等点之间共享ARAnchor对象来传递虚拟角色位置。 当一个用户点击场景时,应用程序创建一个锚点并将其添加到本地ARSession,然后使用Data将该ARAnchor序列化并将其发送到多对话会话中的其他设备:

// Place an anchor for a virtual character. The model appears in renderer(_:didAdd:for:).
let anchor = ARAnchor(name: "panda", transform: hitTestResult.worldTransform)
sceneView.session.add(anchor: anchor)

// Send the anchor info to peers, so they can place the same content.
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: anchor, requiringSecureCoding: true)
    else { fatalError("can't encode anchor") }
self.multipeerSession.sendToAllPeers(data)

当其他对等方从多方会话接收数据时,它们会测试该数据是否包含归档的ARAnchor; 如果是的话,他们解码并将其添加到他们的会话:

if let unarchived = try? NSKeyedUnarchiver.unarchivedObject(of: ARAnchor.classForKeyedUnarchiver(), from: data),
    let anchor = unarchived as? ARAnchor {

    sceneView.session.add(anchor: anchor)
}

这只是将动态功能添加到共享AR体验中的一种策略 - 其他许多策略都是可能的。 选择一个适合您的应用的用户交互,渲染和网络要求。 例如,用户在AR世界空间投掷投射物的游戏可能会定义具有初始位置和速度等属性的自定义数据类型,然后使用Swift的Codable协议将该信息序列化为通过网络发送的二进制表示形式。

See Also

AR World Sharing and Persistence

ARWorldMap

来自世界追踪AR会话的空间映射状态和一组锚。

概览

世界地图中的会话状态包括ARKit对用户移动设备的物理空间的了解(ARKit用于确定设备的位置和方向)以及添加到会话中的任何ARAnchor对象(它可以表示检测到的实时空间)世界功能或由您的应用程序放置的虚拟内容)。

使用 getCurrentWorldMapWithCompletionHandler 之后保存会话的世界地图,可以将其分配给配置的 initialWorldMap 属性,并使用 runWithConfiguration:options: 以相同的空间感知和锚点启动另一个会话。

通过保存世界地图并使用它们开始新的会话,您的应用可以添加新的AR功能:

  • 多用户AR体验。通过将归档的ARWorldMap对象发送到附近用户的设备来创建共享参考框架。有了两台设备可以跟踪同一张世界地图,您可以建立一种网络体验,让用户可以看到相同的虚拟内容并与之互动。
  • 持续的AR体验。应用程序变为不活动状态时保存世界地图,然后在下次应用程序在相同物理环境中启动时恢复它。您可以使用恢复的世界地图中的锚点将相同的虚拟内容放置在保存的会话的相同位置。
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zyq522376829/article/details/80662398
个人分类: iOS
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭