SceneKitで3Dモデルをアニメーションさせる方法はいくつかの選択肢がありますが、3Dモデルの腕や足を自由自在にアニメーションさせようとなると、選択肢はSCNIKConstraintか、SCNAnimationになってくると思います。
<アニメーションの方法>
- SCNTransaction ・・・ シーン全体をノードの回転や移動を使って一斉にアニメーションさせる
- SCNAction ・・・ 個別のノードについてノードの回転や移動を使ってアニメーションさせる
- SCNIKConstraint ・・・ 連結されたボーンを使って操り人形のように動かす
- SCNAnimation ・・・ 事前に設定されたアニメーションを実行する
それぞれの違いはこちらの記事が詳しいです。
SceneKitにおけるアニメーションの実施方法と使い分けについて
SCNAnimationは事前に定義しておけば自由自在に操作できますが、アニメーションの速度や移動量をインタラクティブに操作したいときにはSCNIKConstraintが適しています。
ということで、SCNIKConstraintでロボットの腕を自由自在にアニメーションさせる方法を説明します。
SCNIKConstraintとは?
SCNIKConstraintのIKは、Inverse kinematicsと呼ばれるものです。
こちらの記事がわかりやすかったので引用します。
Inverse Kinematicsの実装メモ
IKとは、人間がとある場所に手を伸ばすときのように手、手首、腕、肩と連動して体を動かすように、連結されたものがとある点に向かうようにする動作を言います。
SCNIKConstraintは、SceneKitのノードにこのIKを適用できるようにするための仕組みです。

手順
今回は、ロボットの左腕がドラッグ量にあわせて移動するようなアニメーションを作ります。
1.3Dモデルを用意する
3Dモデルにはボーンの設定が入っているものが必要です。
今回は、既存のモデルを使用します。
AppleのARKitのサンプルプロジェクトにロボットの3Dモデル(ファイル名: robot.usdz)が付属しているので、これを使用します。

なお、こちらの記事にはモデリングツールで作っている例がありました。
2.Xcodeのプロジェクト作成からGameのプロジェクトを作成する
3.プロジェクトにusdzのファイルをドラッグ & ドロップする
usdz形式の3DモデルをSceneKitに読み込む方法はこちらの記事をご覧ください。
usdz形式の3DモデルをSceneKitに読み込みんでアニメーションさせる
4.画面がドラッグされたら検知するようにする
viewDidLoadで、UIPanGestureRecognizerを登録します。
let tapGesture = UIPanGestureRecognizer(target: self, action: #selector(handleDrag(_:)))
scnView.addGestureRecognizer(tapGesture)
5.起点と終点となる2つのジョイントを決定する
Xcodeでモデルのボーンの情報を確認しながら、起点と終点(実際に動く部分)となる2つのジョイントを決定します。
今回は、左の肩のあたりを起点として、左手を終点として移動させるようにしました。
2点のジョイントは適切に選ばないと、動かないか、おかしな動きになります。
例えば、腰と左手の2点を選んだらサタデーナイトフィーバーのジョン・トラボルタのようになってしまいました。

6.ドラッグされたらロボットの腕が動くようにする
ドラッグの移動量を取得し、それを元にロボットの腕の移動先を決定します。
@objc
func handleDrag(_ gestureRecognize: UIPanGestureRecognizer) {
// アニメーションの起点となる場所を取得する(ここでは左腕の肩)
guard let root = scene.rootNode.childNode(withName: "left_shoulder_1_joint", recursively: true) else { return }
// 実際に移動させる場所を取得する(ここでは左腕の手)
guard let target = scene.rootNode.childNode(withName: "left_hand_joint", recursively: true) else { return }
// SCNIKConstraintに、起点と移動させる場所を指定する
let ik:SCNIKConstraint = SCNIKConstraint.inverseKinematicsConstraint(chainRootNode: root)
target.constraints = [ik]
// ドラッグの移動量から、ロボットの腕の移動量を決定する
let move = gestureRecognize.translation(in: view)
let targetPosition = SCNVector3(target.position.x + Float(move.x * 0.1), target.position.y + Float(move.y * 0.1 * -1), target.position.z)
// 腕を移動させる
ik.targetPosition = targetPosition
}
仕上がり
iOSアプリ開発に関する情報を発信しています。
Twitterでも発信していますのでフォローして下さい。
https://twitter.com/jugemjugemjugem