前回の記事では、Dynamicを使って自動的に動かす方法を紹介しました。今回はKinematicを使って、動きをプログラムで制御する方法をご紹介します。
描画処理の流れ
Dynamicでは、SceneKitに全部処理をお任せできていましたが、自分で制御するには、そもそもどのように処理が進んでいくかのフローを をまず理解しておきましょう。
SceneKitでは、iPhoneデフォルトで60fps...つまり、1秒間に60frameの描画処理をしています。
その1frame毎に以下の処理が行われています。
rendererと書かれているのは、SCNSceneRendererDelegate のことで、SCNViewはこのDelegateを実装しています。
この図、おおまかにいうと以下の流れ。
- モノを動かしてみる (renderer:updateAtTime )
- SceneKitが衝突があるかを計算 (Scene Kit simulates physics)
- 衝突結果から跳ね返った位置に移動させる(renderer:didSimurlatePhysicsAtTime)
- 描画処理を開始 (renderer:willRenderScene:atTime:)
"まず動かして、衝突をSceneKitに判断してもらう" という方向になるわけです。
ではでは、Standlandで自由に動き回るGemmy達の実装を例にご紹介していきます。
青緑色の子がGemmy達
自動でウロウロ動き回ります。
このままGemmyが歩くとぶつかるね...
自動でウロウロ動き回ります。
このままGemmyが歩くとぶつかるね...
1. 何と衝突させるのかを指定
前回に少し説明をした、physicsBodyをKinematicで作成します。
let shape = SCNPhysicsShape(node: bodyNode, options: nil)
let physicsBody = SCNPhysicsBody(type: .Kinematic, shape: shape)
そして、何と衝突させるのか? の設定もします。
physicsBody.categoryBitMask = FLSPhysicsCategory.Gemmy
physicsBody.contactTestBitMask = FLSPhysicsCategory.GemmyHits
categoryBitMask が自分自身のBitMask。
contactTestBitMask が衝突する対象のBitMask。
Gemmy同士や、キャラ、看板には衝突するけど、床との衝突判定はいらない など設定ができるわけです。
幽霊キャラの場合は壁は衝突せずにすり抜けれる とか。
飛んでいるキャラは水の上にいけるけど、歩いているキャラは行けないとか。
パターンはいろいろ!
この2つのBitMaskはKinematicに限らず、Dynamic/Staticでも同様に設定します。
その他、physicsBodyにapplyForceの設定もあり、指定の向きに力を入れることもできます。
使っていないので試せていないのですが、重力以外の力、例えばボールを投げるとかそういう時に使えるものと思います。
この2つのBitMaskはKinematicに限らず、Dynamic/Staticでも同様に設定します。
その他、physicsBodyにapplyForceの設定もあり、指定の向きに力を入れることもできます。
使っていないので試せていないのですが、重力以外の力、例えばボールを投げるとかそういう時に使えるものと思います。
2. 動かす
renderer:updateAtTime
Gemmyが歩くのは、ぴょんぴょん跳ねているアニメーションに加え、updateAtTimeのタイミングで以下のようなコードで位置移動 することで実装しています。
let radians = node.rotation.z * node.rotation.w
let direction:float3 = float3(sin(radians), -cos(radians), 0.0)
let position = SCNVector3(float3(node.position) + direction * speed)
node.position = position
Gemmyが向いている方向へ移動する というコードです。
やっていることとしては、Nodeの位置(x, y, z)を変えているだけというシンプルなもの。
3. 衝突後の位置を判定
Gemmyを歩かせるようにしたら、衝突が発生するはず。衝突の情報は、SCNPhysicsContactDelegate で取得します。
このDelegateは、SCNView.scene!physicsWorld.contactDelegate で設定します。
physicsWorld:didBeginContact:
衝突した時にcallされます。
引数に情報として渡される SCNPhysicsContact が衝突の情報です。
- nodeA , nodeB が衝突したノード
- contactPoint が衝突した位置
- contactNormal は衝突して跳ね返ったりした方向
- collisionImpulse は衝突の勢い
- penetrationDistance は衝突で重なった距離
physicBodyに設定した重量や反発係数などを元にして、SceneKitが衝突の物理計算をして結果を返してくれているわけですね。素敵です。
これらの情報から、衝突して跳ね返ったらどこの位置になるかを計算します。
Gemmyでは、contactNormal × penetrationDistanceの値を、元のpositionから動かすようにしました。
physicsWorld:didUpdateContact:
衝突情報が変更された時に呼ばれます。
なので、didBeginContactと同じように衝突の処理を組み込んでおきます。
4. 衝突の位置を設定
renderer:didSimurlatePhysicsAtTime
衝突後の位置(position)をSCNPhysicsContactDelegateで計算しましたが、実際にNodeに設定するのは、didSimuratePhysicsAtTime で行います。
これで、処理は完成。
分かってしまうと簡単ですが、最初はなかなかスムーズには実装できませんでした。
Gemmyが壁をすり抜けてしまう...脱走事件が発生したりして!
衝突の跳ね返りの位置の設定が漏れている子がいたり、障害物がおかしくなっていたり という問題を一つ一つ見直していくことで解決していきました。
こうやってプログラムで制御することで、
「ランダムで歩いたり、向き変えたり、アニメーションいれる」
「何かにあたった後は、立ち止まって、向きを変える」
という自動制御Gemmyが出来上がっています。
Gemmy達がワヤワヤ動いている様子は是非アプリで見てみてくださいね!
「ランダムで歩いたり、向き変えたり、アニメーションいれる」
「何かにあたった後は、立ち止まって、向きを変える」
という自動制御Gemmyが出来上がっています。
Gemmy達がワヤワヤ動いている様子は是非アプリで見てみてくださいね!
作成したアプリはこちら↓
Standland - 座りすぎ解消!スタンドランド