Ответ 1
Вам не нужно перемещать якоря. (ручная волна) Это не тот API, который вы ищете...
Добавление объектов ARAnchor
к сеансу эффективно касается "маркировки" точки в пространстве реального мира, чтобы вы могли ссылаться на нее позже. Точкой (1,1,1)
(например) всегда является точка (1,1,1)
- вы не можете ее перемещать где-то еще, потому что тогда это не точка (1,1,1)
.
Сделать двумерную аналогию: привязки - это ориентиры, подобные границам вида. Система (или другая часть вашего кода) сообщает о том, где находятся ее границы, и представление рисует его содержимое относительно этих границ. Якоря в AR дает вам ориентиры, которые вы можете использовать для рисования контента в 3D.
То, о чем вы спрашиваете, действительно касается перемещения (и анимации движения) виртуального контента между двумя точками. И сама ARKit действительно не касается отображения или анимации виртуального контента - там много отличных графических движков, поэтому ARKit не нужно изобретать это колесо. То, что ARKit делает, обеспечивает реальную систему отсчета для отображения или анимации контента с использованием существующих графических технологий, таких как SceneKit или SpriteKit (или Unity или Unreal, или настраиваемый движок, созданный с помощью Metal или GL).
Поскольку вы упомянули о попытке сделать это с помощью SpriteKit... будьте осторожны, это становится беспорядочным. SpriteKit - это 2D-движок, и в то время как ARSKView
предоставляет некоторые способы подгонки третьего измерения, эти способы имеют свои пределы.
ARSKView
автоматически обновляет xScale
, yScale
и zRotation
каждого спрайта, связанного с ARAnchor
, обеспечивая иллюзию трехмерной перспективы. Но это относится только к узлам, прикрепленным к якорям, и, как отмечено выше, привязки статичны.
Однако вы можете добавить другие узлы в свою сцену и использовать те же самые свойства, чтобы эти узлы соответствовали узлам ARSKView
. Вот какой код вы можете добавить/заменить в проекте шаблона 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 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
, потому что вы получите один обратный вызов для каждого node. -
Определить положение, поворот и значения шкалы для анимированного node путем интерполяции между двумя значениями конечных точек на основе текущего времени.
-
Установите новые атрибуты в node. (Или, возможно, оживить эти атрибуты в течение очень короткой продолжительности, чтобы он не перескакивал слишком много в одном кадре?)
Такая работа по созданию поддельной 3D-иллюзии в 2D-графическом инструменталитете - поэтому мои комментарии о SpriteKit не являются отличным первым шагом в ARKit.
Если вам нужны 3D-позиционирование и анимация для ваших AR-оверлей, гораздо проще использовать набор 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 анимация остается там, где она должна была. (И без дополнительной работы для непосредственного интерполяции и анимации атрибутов.)