您无需移动锚点。(挥手)那不是你要找的 API……
将ARAnchor
对象添加到会话中实际上是在“标记”现实世界空间中的一个点,以便您以后可以参考它。点(1,1,1)
(例如)永远是点(1,1,1)
——你不能把它移到别的地方,因为那样它就不再是点(1,1,1)
了。
做一个 2D 类比:锚点是类似于视图边界的参考点。系统(或您的另一段代码)告诉视图它的边界在哪里,并且视图相对于这些边界绘制其内容。AR 中的锚点为您提供了可用于绘制 3D 内容的参考点。
您要问的实际上是关于在两点之间移动(和动画移动)虚拟内容。而且 ARKit 本身并不是关于显示或动画虚拟内容的——那里有很多很棒的图形引擎,所以 ARKit 不需要重新发明那个轮子。ARKit 所做的是为您提供一个真实世界的参考框架,以便您使用现有的图形技术(如 SceneKit 或 SpriteKit(或 Unity 或 Unreal,或使用 Metal 或 GL 构建的自定义引擎))显示或动画内容。
既然您提到尝试使用 SpriteKit 执行此操作...请注意,它会变得混乱。SpriteKit 是一个 2D 引擎,虽然ARSKView
提供了一些在其中插入第三维的方法,但这些方法有其局限性。
ARSKView
自动更新与关联的每个精灵的xScale
、yScale
和,提供 3D 透视的错觉。但这仅适用于附加到锚点的节点,并且如上所述,锚点是静态的。zRotation
ARAnchor
但是,您可以将其他节点添加到场景中,并使用这些相同的属性使这些节点与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
,我们无法在制作动画时调整动画的开始/结束状态,因此球会一直在其原始(屏幕空间)位置/旋转/缩放之间来回弹跳。
您可以通过直接为球设置动画来做得更好,但这需要大量工作。您需要在每一帧(或每个view(_:didUpdate:for:)
委托回调)上:
在动画的每一端保存基于锚的节点的更新位置、旋转和缩放值。每个回调都需要执行两次didUpdate
,因为每个节点都会收到一个回调。
通过基于当前时间在两个端点值之间进行插值,计算出动画节点的位置、旋转和缩放值。
在节点上设置新属性。(或者可能在很短的时间内将其设置为这些属性,因此它不会在一帧中跳跃太多?)
将虚假的 3D 错觉硬塞进 2D 图形工具包需要做很多工作——因此我对 SpriteKit 的评论并不是进入 ARKit 的第一步。
如果您想要 AR 叠加层的 3D 定位和动画,使用 3D 图形工具包要容易得多。这是前面示例的重复,但使用了 SceneKit。从 ARKit/SceneKit Xcode 模板开始,取出宇宙飞船,然后将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 版本不同,动画保持在它应该在的位置。(并且无需直接插入和动画属性的额外工作。)