Blender で作成したアニメーション付きモデルを ARKit で配置する方法を解説していきます。
今回は ARKit で実際にアプリで表示できるようにしてみましょう。
前回の記事は以下です。
アニメーションの名前をチェックする
まずは作成したアニメーションの名前を知る必要があるので、作成したdaeファイルをテキストエディタで開いてください。下図の矢印が指しているところがアニメーションの名前となります。
おそらく unnamed_animation__1
と命名されていると思います。名前をコピーしたらテキストエディタは閉じても構いません。
(なぜか Blender 側でアニメーションの名前を設定していても unnamed_animation__1
になってしまいます...)
Xcodeにデータを取り込む
File > New > File... から、SceneKit Catalog を作成します。これが 3Dデータのアセットを保存する場所になります。
そして、作成した art.scnassets の中に daeファイルをドラッグ&ドロップしてください。
ARKit で実装
UIKit の資産を使うので、UIViewControllerRepresentable
でラップしてあげます。
あとは ARSCNView
を使って実装していきましょう。
アニメーションは別に呼び出して addAnimation
で追加してあげないと動かないので注意です。
import SwiftUI
import RealityKit
import ARKit
// SwiftUI 用の UIViewController ラッパー
struct ARViewControllerContainer: UIViewControllerRepresentable {
typealias UIViewControllerType = ARViewController
let arViewController: ARViewController
init() {
arViewController = ARViewController()
}
func makeUIViewController(context: Context) -> ARViewController {
return arViewController
}
func updateUIViewController(_ arViewController: ARViewController, context: Context) {}
}
class ARViewController: UIViewController, ARSCNViewDelegate {
let modelName= "Yaguchi_Panic" // モデルの名前
let animationName = "unnamed_animation__1" // アニメーションの名前
var sceneView = ARSCNView()
let configuration = ARWorldTrackingConfiguration()
var animations = [String: CAAnimation]()
var idle: Bool = true
var n = 0
var modelNode = [SCNNode()]
var isDetectPlane = false
var frameNode: SCNNode!
var planeNode: SCNNode? = nil
var isShowingAnnotation = false
var size: Float = 0.05
// 画面ロード時に sceneView の設定をする
override func viewDidLoad() {
super.viewDidLoad()
self.configuration.planeDetection = .horizontal
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
self.sceneView.addGestureRecognizer(tapGestureRecognizer)
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
self.view = sceneView
}
// 画面表示時にセッションを開始する
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.sceneView.session.run(configuration)
self.sceneView.autoenablesDefaultLighting = true
}
// 画面非表示時にセッションを停止する
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
// 画面タップ時の動作
@objc func tapped(sender: UITapGestureRecognizer) {
// タップされた位置を取得する
let tapLocation = sender.location(in: sceneView)
// タップされた位置のARアンカーを探す
guard let raycast = sceneView.raycastQuery(from: tapLocation, allowing: .estimatedPlane, alignment: .any) else { return }
guard let result = sceneView.session.raycast(raycast).first else { return }
self.addItem(hitTestResult: result)
}
/// アイテム配置メソッド
private func addItem(hitTestResult: ARRaycastResult) {
sceneView.debugOptions = []
// 現実世界の座標を取得
let transform = hitTestResult.worldTransform
let thirdColumn = transform.columns.3
// モデルをロードしてシーンにノードを追加
let heroScene = SCNScene(named: "art.scnassets/\(modelName).dae")
for childNode in heroScene!.rootNode.childNodes {
modelNode[n].addChildNode(childNode)
}
// モデルからシーンのソースを作成
let sceneURL = Bundle.main.url(forResource: "art.scnassets/\(modelName)", withExtension: "dae")
let sceneSource = SCNSceneSource(url: sceneURL!, options: nil)
// モデルのシーンからアニメーションを呼び出す
if let animationObject = sceneSource?.entryWithIdentifier("\(animationName)", withClass: CAAnimation.self) {
animationObject.repeatCount = 0
// モデルにアニメーションを追加
modelNode[n].addAnimation(animationObject, forKey: nil)
}
// モデルのサイズを設定
modelNode[n].scale = SCNVector3Make(size, size, size)
// モデルの向きをカメラ方向に設定
if let camera = sceneView.pointOfView {
modelNode[n].eulerAngles.y = camera.eulerAngles.y
}
// アイテムの配置
modelNode[n].position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z)
// シーンビューに追加する
sceneView.scene.rootNode.addChildNode(modelNode[n])
n += 1
// ノードを追加する
modelNode.append(SCNNode())
}
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
print("renderer: didAdd")
}
}