ARKitのサンプルにアニメーションを追加しよう
今回扱うのはアニメーションです。
ただAR空間の物体に動きが入るとよりリアルさが出て楽しくなります。
アニメーションの設定方法
アニメーションの設定方法は大きく3つあります。
- SCNAction
- SCNTransaction
- CoreAnimation
SCNAction
直進や回転、拡大縮小、フェードイン/フェードアウトをそれぞれ対応したActionを使ってAR上の物体にアニメーションを適用することができます。
SCNTransaction
AR空間の物体は60FPSのループで常に描画されていますが、その間隔を伸ばすことによりアニメーションを設定することができます。
イメージとしては物体を1m右に動かすことを1秒かけて行うことで、その間の動きが描画される感じです。
CoreAnimation
通常AR空間に表示する3DオブジェクトはBlenderなどの3Dモデリングツールを使って作られますが、3Dモデリングツールでは形を作るだけでなくオブジェクトにアニメーションを設定することができます。
そのような、外部ツールで設定されたアニメーションを呼び出すことでアニメーションを描画することができます。
キャラクターが歩くなどの複雑なアニメーションの場合は大抵CoreAnimationを使います。
修正方針
今回は次の2つのアニメーションをそれぞれ別の方法で実装してみます。
- 平面検知された時に表示される飛行機をフェードインさせる
- 飛行機をきりもみ回転させる
飛行機のフェードイン
SCNTransactionと、物体の透過を設定するopacityというプロパティを使って実装します。
まずARKitサンプルの飛行機を平面に置いてみようでshipの初期状態をHiddenにしましたが、これをopacityに変更しておきます。
まずARKitサンプルの飛行機を平面に置いてみようでshipの初期状態をHiddenにしましたが、これをopacityに変更しておきます。
ship.opacity = 0このopacityは0〜1が設定でき、0が完全に透明な状態を示します。
次に、func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)で飛行機を置いていますが、この処理の最後に次のSCNTransaction処理を追加します。
SCNTransaction.begin() SCNTransaction.animationDuration = 1.5 ship.opacity = 1 SCNTransaction.commit()
飛行機のきりもみ回転
単純に飛行機を回転させる場合は次の2行で実装できます。
しかし、これを単純にshipに追加してしまうと、フェードインしている状態で回転をしてしまいます。
フェードインが完了してから回転をしたい場合は次のように実装をします。
let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1)) ship.runAction(rotationAction)
しかし、これを単純にshipに追加してしまうと、フェードインしている状態で回転をしてしまいます。
フェードインが完了してから回転をしたい場合は次のように実装をします。
let completion = { let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1)) ship.runAction(rotationAction) } SCNTransaction.begin() SCNTransaction.animationDuration = 1.5 SCNTransaction.completionBlock = completion ship.opacity = 1 SCNTransaction.commit()
実際に動作している様子
実際のコード
import UIKit import SceneKit import ARKit class ViewController: UIViewController, ARSCNViewDelegate { @IBOutlet var sceneView: ARSCNView! override func viewDidLoad() { super.viewDidLoad() // Set the view's delegate sceneView.delegate = self // Show statistics such as fps and timing information sceneView.showsStatistics = true // Create a new scene let scene = SCNScene(named: "art.scnassets/ship.scn")! if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) { ship.opacity = 0 } // Setup Omni Light let light = SCNLight() light.type = .omni let LightNode = SCNNode() LightNode.light = light LightNode.name = "light" LightNode.position = SCNVector3(-1,2,-1) scene.rootNode.addChildNode(LightNode) // Set the scene to the view sceneView.scene = scene } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // Create a session configuration let configuration = ARWorldTrackingConfiguration() configuration.planeDetection = [.horizontal] // Run the view's session sceneView.session.run(configuration) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // Pause the view's session sceneView.session.pause() } // MARK: - ARSCNViewDelegate /* // Override to create and configure nodes for anchors added to the view's session. func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { let node = SCNNode() return node } */ func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) { ship.removeFromParentNode() node.addChildNode(ship) let configuration = ARWorldTrackingConfiguration() sceneView.session.pause() sceneView.session.run(configuration) let completion = { let rotationAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 1, duration: 1)) ship.runAction(rotationAction) } SCNTransaction.begin() SCNTransaction.animationDuration = 1.5 SCNTransaction.completionBlock = completion ship.opacity = 1 SCNTransaction.commit() } } func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return } let ambientIntensity = lightEstimate.ambientIntensity let ambientColorTemperature = lightEstimate.ambientColorTemperature if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) { guard let light = lightNode.light else { return } light.intensity = ambientIntensity light.temperature = ambientColorTemperature } } func session(_ session: ARSession, didFailWithError error: Error) { // Present an error message to the user } func sessionWasInterrupted(_ session: ARSession) { // Inform the user that the session has been interrupted, for example, by presenting an overlay } func sessionInterruptionEnded(_ session: ARSession) { // Reset tracking and/or remove existing anchors if consistent tracking is required } }
0 件のコメント:
コメントを投稿