【ARKit】childNode(withName:recursively:)でThread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value


はじめに

こちらを参考にARKitで3Dモデルを表示しようとしたところ、「 Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value」のエラーが出たので、その対処を備忘録として。

実行環境

  • Xcode 11.2.1
  • MagicaVoxel 0.99.4
  • Mixamo

コード

上記リンクのサンプルはボタンで使うscnを切り替えていたのですが、簡単にテストするために変数を「swing」固定としました。また SceneKit Catalog の名前も変更(art.scnassets)してあります。

ViewController.swift
var selectedItem: String? = "swing"
//(中略)
let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn")

エラーになった箇所

以下のfunctionの childNode(withName:recursively:) で 「 Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value」のエラーが出ました。

addItem
func addItem(hitTestResult: ARHitTestResult) {
        if let selectedItem = self.selectedItem {

            // アセットのより、シーンを作成
            let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn")

            // ノード作成
            let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))!

            // 現実世界の座標を取得
            let transform = hitTestResult.worldTransform
            let thirdColumn = transform.columns.3

            // アイテムの配置
            node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z)
            self.mainSceneView.scene.rootNode.addChildNode(node)
        }
    }

原因

SCNScene.childNode(withName:recursively:) は、SCNScene から withNname で指定した名前のノードを取得するメソッドですが、swing.scnに "swing" というノードが存在しないためでした。

対処

swing.scnを開いて、Scene graphパネルの左下の「+」ボタンでノードを追加して「swing」に名前を変更して、元々あったノードを「swing」ノードの配下にドラッグして移動します。

3Dオブジェクトのサイズ調整

3Dオブジェクトのサイズが大きすぎたので、サイズ変更のコードを追加してあります。
node.scale = SCNVector3(0.05, 0.05, 0.05)

addItem
    /// アイテム配置メソッド
    func addItem(hitTestResult: ARHitTestResult) {
        if let selectedItem = self.selectedItem {

            // アセットのより、シーンを作成
            let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn")

            // ノード作成
            let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))!

            // 現実世界の座標を取得
            let transform = hitTestResult.worldTransform
            let thirdColumn = transform.columns.3

            // アイテムの配置
            node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z)

            // アイテムのサイズを変更
            node.scale = SCNVector3(0.05, 0.05, 0.05)

            self.mainSceneView.scene.rootNode.addChildNode(node)
        }
    }

ちなみに MagicaVoxelで作ったときのサイズは、21 21 21 でした。

この辺の適正サイズや,sncファイルのscale値で変更した方がいいのかは、現時点ではちょっとわからず。。。

まとめ

前提の知識(3Dモデルとか)が乏しいのでいろいろ躓いています。他の3Dアプリで作ったモデルはちゃんとノード名がつくんですかね。。。

MagicaVoexlについてはこちらを参考にしました。やはり公開することを考えると、オリジナルのモデルが必要になるので、簡単でいいです。

MagicaVoexlにアニメーションをつけるのには、Maximoを使いました。こちらもモデルをアップロードしてアニメーション選ぶだけなので、これまた簡単です。こちらを参考にしました(モデルをMagicaVoexlで作る場合は、Blender部分はすっ飛ばして大丈夫です)。