iOS で SceneKit を試す(Swift 3) その64 - SCNSkinner と Bone について
人型のキャラクタなどに稼動できる骨を入れて、骨を動かすと該当箇所のジオメトリが変化する 所謂、Skinning と Bone を使用してアニメーションを SCNSkinner が行う。

公式ドキュメントの画像を参照
正直、コード上で書くのは手間がかかり、Scene Editor で編集ができないため、 他のゲームエンジンと同様に 3DCG のオーサリングツールで Bone を入れ、 アニメーションを設定したオブジェクトファイルを作成した方が速い。
使い方
コードで設定する場合は、 キャラクタなどのスキンにボーンと各種設定を SCNNode から適応させる。
init(baseGeometry: SCNGeometry?,
bones: [SCNNode],
boneInverseBindTransforms: [NSValue]?,
boneWeights: SCNGeometrySource,
boneIndices: SCNGeometrySource)
| パラメーター名 | 説明 |
|---|---|
| baseGeometry | キャラクタなど変形させたいジオメトリ。スキン |
| bones | 階層化されたボーン情報。SCNNode がボーンとなるためその配列を渡す |
| boneInverseBindTransforms | ボーンの初期位置。SCNMatrix4 が格納された NSValue の配列 |
| boneWeights | ボーンが boneIndices で設定したスキンの頂点に対してどれだけの影響があるかを設定したもの。所謂、ウエイト。ここで設定する Geometry Source の頂点用のベクトルが4つの要素以上になると GPU から CPU に移りパフォーマンスが悪くなるので注意 |
| boneIndices | ボーンが動いた際、スキン上のどの頂点を動かすか設定する |
Scene Editor での見えかた
Scene Graph View でのスキン。
Attributes に Skinner が設定されている。

Scene Graph View でのボーン。
Xcode のメニューバー Editor > Display > Show Joints で簡易表示される。

わかりづらいので、同じく Show Bounding Box でボーンの範囲を表示している。

Xcode 9 からは 3DCG のオーサリングツール同様に正八面体を伸ばした形でボーンが表示される。
アニメーション

設定しているボーンをスキンに渡す
設定されているボーンの集まりであるスケルトンは、以下の方法で他のジオメトリに渡すことができる。
SCNNodeA.skinner.skeleton = SCNNodeB.skinner.skeleton
他のノードのボーンアニメーションを使用する
ボーンの構造が同じであれば、他の scn やオブジェクトファイルから呼び出してアニメーションをすることができる。
let url = Bundle.main.url(forResource: "art.scnassets/boss_attack", withExtension: "dae")
let sceneSource = SCNSceneSource(url: url!, options: [
SCNSceneSource.LoadingOption.animationImportPolicy : SCNSceneSource.AnimationImportPolicy.doNotPlay
])
let attackAnimation:CAAnimation = sceneSource!.entryWithIdentifier("attackID", withClass: CAAnimation.self)!
attackAnimation.fadeInDuration = 0.3
attackAnimation.fadeOutDuration = 0.3
baseNode.addAnimation(attackAnimation, forKey: "attackID")
IK (Inverse Kinematics) を使用してみる
コードでのみで設定可能である点と、3DCG のオーサリングツールでキャラクタアニメーションを作る関係上、あまり使用されることはないと思われるが機能的には存在している。
shoulderNode を肩のボーンとし、handNode を手のボーン。 そこまでを SCNIKConstraint の inverseKinematicsConstraint(chainRootNode:) で設定し
SCNIKConstraint の targetPosition で移動させる。
let ik:SCNIKConstraint = SCNIKConstraint.inverseKinematicsConstraint(chainRootNode: shoulderNode) handNode.constraints = [ik] ik.targetPosition = SCNVector3(2, 0, 2)
今回はここまで