我正在尝试使用新的ARKit来替代我拥有的另一个类似解决方案。太棒了!但是我似乎无法弄清楚如何以编程方式移动ARAnchor。我想将锚点缓慢移动到用户的左侧。
将锚点创建在用户前方2米处:
var translation = matrix_identity_float4x4 translation.columns.3.z = -2.0 let transform = simd_mul(currentFrame.camera.transform, translation) let anchor = ARAnchor(transform: transform) sceneView.session.add(anchor: anchor)
之后,将对象移至用户的左/右(x轴)…
anchor.transform.columns.3.x = anchor.transform.columns.3.x + 0.1
每隔50毫秒(或任何其他时间)重复一次。
上面的方法不起作用,因为transform是仅获取属性。
我需要一种方法来改变AR对象在空间中相对于用户的位置,从而保持AR体验的完好无损- 意思是,如果您移动设备,AR对象将会移动但不会被“卡住”就像简单地画在相机上一样,但移动时就像您在走过时看到一个人在移动- 他们在移动并且您在移动并且看起来很自然。
请注意,此问题的范围仅与如何相对于用户(ARAnchor)在空间中移动物体有关,而与平面(ARPlaneAnchor)或与另一个检测到的曲面(ARHitTestResult)无关。
谢谢!
您无需移动锚点。 (手势) 这不是您要查找的API …
将ARAnchor对象添加到会话中实际上是关于“标记”现实世界中的点,以便以后可以引用它。点(1,1,1)(例如)始终是点(1,1,1)-您无法将其移动到其他地方,因为那样就不再是该点(1,1,1)。
ARAnchor
(1,1,1)
进行2D类比:锚点是参考点,类似于视图的边界。系统(或另一段代码)告诉视图其边界在哪里,视图相对于那些边界绘制其内容。AR中的锚点为您提供了可用于3D绘图内容的参考点。
您要问的实际上是关于在两点之间移动虚拟内容(并对其进行动画处理)。而且,ARKit本身并不是要显示或动画化虚拟内容- 那里有很多很棒的图形引擎,因此ARKit不需要重新发明轮子。ARKit的作用是为您提供一个现实的参考框架,供您使用SceneKit或SpriteKit(或Unity或Unreal,或使用Metal或GL构建的自定义引擎)等现有图形技术显示或设置内容的动画。
既然您提到要尝试使用SpriteKit进行此操作,请注意,它变得凌乱。SpriteKit是一个2D引擎,尽管ARSKView提供了一些方法来解决其中的三维问题,但这些方法有其局限性。
ARSKView
ARSKView自动更新xScale,yScale以及zRotation与相关联的每个精灵的ARAnchor,提供3D立体的错觉。但这仅适用于附加到锚点的节点,并且如上所述,锚点是静态的。
xScale
yScale
zRotation
但是,您可以将其他节点添加到场景中,并使用这些相同的属性使这些节点与ARSKView-managed节点匹配。您可以在ARKit / SpriteKit Xcode模板项目中添加/替换以下代码来执行此操作。我们将从一些基本逻辑开始,在第三个拍子上(在使用前两个拍子放置锚点之后)运行弹跳动画。
var anchors: [ARAnchor] = [] override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // Start bouncing on touch after placing 2 anchors (don't allow more) if anchors.count > 1 { startBouncing(time: 1) return } // Create anchor using the camera's current position guard let sceneView = self.view as? ARSKView else { return } if let currentFrame = sceneView.session.currentFrame { // Create a transform with a translation of 30 cm in front of the camera var translation = matrix_identity_float4x4 translation.columns.3.z = -0.3 let transform = simd_mul(currentFrame.camera.transform, translation) // Add a new anchor to the session let anchor = ARAnchor(transform: transform) sceneView.session.add(anchor: anchor) anchors.append(anchor) } }
然后,使该动画发生一些SpriteKit的乐趣:
var ballNode: SKLabelNode = { let labelNode = SKLabelNode(text: "🏀") labelNode.horizontalAlignmentMode = .center labelNode.verticalAlignmentMode = .center return labelNode }() func startBouncing(time: TimeInterval) { guard let sceneView = self.view as? ARSKView, let first = anchors.first, let start = sceneView.node(for: first), let last = anchors.last, let end = sceneView.node(for: last) else { return } if ballNode.parent == nil { addChild(ballNode) } ballNode.setScale(start.xScale) ballNode.zRotation = start.zRotation ballNode.position = start.position let scale = SKAction.scale(to: end.xScale, duration: time) let rotate = SKAction.rotate(toAngle: end.zRotation, duration: time) let move = SKAction.move(to: end.position, duration: time) let scaleBack = SKAction.scale(to: start.xScale, duration: time) let rotateBack = SKAction.rotate(toAngle: start.zRotation, duration: time) let moveBack = SKAction.move(to: start.position, duration: time) let action = SKAction.repeatForever(.sequence([ .group([scale, rotate, move]), .group([scaleBack, rotateBack, moveBack]) ])) ballNode.removeAllActions() ballNode.run(action) }
这是一个视频,因此您可以看到此代码的实际效果。您会注意到,只有在不移动相机的情况下,这种错觉才会起作用- 对于AR来说并不是那么好。使用时SKAction,我们不能在动画时调整动画的开始/结束状态,因此,球会在其原始(屏幕空间)位置/旋转/比例之间不断来回弹跳。
SKAction
您可以通过直接对球进行动画处理来做得更好,但这是很多工作。您需要在每个框架(或每个view(_:didUpdate:for:)委托回调)上:
view(_:didUpdate:for:)
保存动画两端的基于锚点的节点的更新后的位置,旋转和比例值。每个didUpdate回调您需要两次,因为每个节点都会得到一个回调。
didUpdate
通过基于当前时间在两个端点值之间进行插值,计算出要设置动画的节点的位置,旋转和缩放值。
在节点上设置新属性。(或者可以在很短的时间内将其设置为这些属性的动画,这样它在一帧内不会跳得太多吗?)
将虚假的3D幻象刺入2D图形工具箱需要大量工作,因此我对SpriteKit的评论并不是ARKit迈出的重要一步。
如果您希望AR叠加层具有3D定位和动画效果,则使用3D图形工具包要容易得多。这是前面示例的重复,但是使用了SceneKit。从ARKit / SceneKit Xcode模板开始,取出飞船,然后touchesBegan从上方将相同的功能粘贴到ViewController。(也将as ARSKView演员表更改为as ARSCNView。)
touchesBegan
ViewController
as ARSKView
as ARSCNView
然后,一些快速代码用于放置2D广告牌精灵,并通过SceneKit匹配ARKit / SpriteKit模板的行为:
// in global scope func makeBillboardNode(image: UIImage) -> SCNNode { let plane = SCNPlane(width: 0.1, height: 0.1) plane.firstMaterial!.diffuse.contents = image let node = SCNNode(geometry: plane) node.constraints = [SCNBillboardConstraint()] return node } // inside ViewController func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { // emoji to image based on https://stackoverflow.com/a/41021662/957768 let billboard = makeBillboardNode(image: "⛹️".image()) node.addChildNode(billboard) }
最后,为弹跳球添加动画:
let ballNode = makeBillboardNode(image: "🏀".image()) func startBouncing(time: TimeInterval) { guard let sceneView = self.view as? ARSCNView, let first = anchors.first, let start = sceneView.node(for: first), let last = anchors.last, let end = sceneView.node(for: last) else { return } if ballNode.parent == nil { sceneView.scene.rootNode.addChildNode(ballNode) } let animation = CABasicAnimation(keyPath: #keyPath(SCNNode.transform)) animation.fromValue = start.transform animation.toValue = end.transform animation.duration = time animation.autoreverses = true animation.repeatCount = .infinity ballNode.removeAllAnimations() ballNode.addAnimation(animation, forKey: nil) }
这次,动画代码比SpriteKit版本短很多。 这是实际效果。
因为我们从3D开始工作,所以实际上是在两个3D位置之间进行动画处理- 与SpriteKit版本不同,动画会保留在原本应有的位置。(并且无需进行直接内插和设置属性动画的额外工作。)