2016年4月8日金曜日

SceneKit - 重力と当たり判定用のカラダ

SceneKitシリーズ 第四弾。
醍醐味である、物理シュミレーションの話を開始です!

SceneKitは、まぁフレームワークなので、用意された箇所に設定していけばある程度のものは簡単に作ることができます。





ボールを落としているこんな例は、以下のコードだけで実現しています。

import UIKit
import SceneKit
class ViewController: UIViewController {
@IBOutlet weak var sceneView: SCNView!
override func loadView() {
super.loadView()
let scene = SCNScene()
self.sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
setupScene(scene)
let y:Float = 8
//反発係数を変えた3つ球体を追加
scene.rootNode.addChildNode(createSphere(SCNVector3Make(-1.5, y, 0), restitution: 0.5))
scene.rootNode.addChildNode(createSphere(SCNVector3Make(0, y, 0), restitution: 1.0))
scene.rootNode.addChildNode(createSphere(SCNVector3Make(1.5, y, 0), restitution: 1.5))
//4秒後にもう一つ球体を追加
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(4 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {
scene.rootNode.addChildNode(self.createSphere(SCNVector3Make(-1, y, 0), restitution: 1.0))
})
}
private func setupScene(scene:SCNScene) {
//カメラ追加
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 1, z: 15)
scene.rootNode.addChildNode(cameraNode)
//床を作成
let floor = SCNFloor()
floor.reflectivity = 0.25
let floorNode = SCNNode(geometry: floor)
let floorShape = SCNPhysicsShape(geometry: floor, options: nil)
let floorBody = SCNPhysicsBody(type: .Static, shape: floorShape)
floorNode.physicsBody = floorBody
scene.rootNode.addChildNode(floorNode)
}
//球体を作成
private func createSphere(position:SCNVector3, restitution:CGFloat) -> SCNNode {
let sphere = SCNSphere(radius: 0.5)
let node = SCNNode(geometry: sphere)
node.position = position
if let material = node.geometry?.firstMaterial {
material.diffuse.contents = UIColor.redColor()
material.specular.contents = UIColor.whiteColor()
}
let physicsShape = SCNPhysicsShape(geometry: sphere, options: nil)
let physicsBody = SCNPhysicsBody(type: .Dynamic, shape: physicsShape)
physicsBody.restitution = restitution
node.physicsBody = physicsBody
return node
}
}
床、カメラ、球体を作成してSceneViewに追加しているだけですが、これで勝手に球体が落ちて、当たって、跳ね返って...という動きになります。素敵。

この動きを実現している内容を詳しくみていきます。

重力の設定

まず、SceneKitの世界には重力がデフォルトで設定されています。

SCNSceneのphysicsWorld.gravityが重力の設定で、printしてみるとこんな値。

SCNVector3(x: 0.0, y: -9.80000019, z: 0.0)

9.8という数字、見覚えありませんか?
そう、物理で習った、重力加速度の値です。
physicsWorldには、gravity(重力)以外に、speed(スピード)、timeStep(fps) の設定があり、この世界全体の設定することができます。

PhysicsBodyの設定

物理シュミレーションの世界に参加するには、PhysicsBodyというオブジェクトの設定が必要です。いうなれば、当たり判定をするためのカラダです。


SCNNodeの中には、Geometryという見かけ上の情報と、PhysicsBodyという物理シュミレーションのための情報が別々にあるという構成です。

PhysicsBodyの種類は、以下の3つ
  • Static : 動かない、静止物
  • Dynamic : 動くもの
  • Kinematic :  動くものだけど、自動的にはうごかない

今回のサンプルコードでは、床はStatic、球体はDynamicにしています。
Kinematicの使いドコロとしては、たとえば、ユーザがPadを使ってキャラを動かすような場合です。
StandlandのGemmy(ジェミー)達も、Kinematicです。
Kinematicでの判定のお話はまた次回書きますね。

type以外に、mass(重量)、restitution(反発)、friction(摩擦)などのパラメータがあり、動きを変えることができます。

PhysicsShapeの設定

PhysicsShapeというのは、物理シュミレーションをするときの「形」の設定です。
デフォルトで用意されているSCNBox, SCNSphereなどはSCNGeometryを継承しているので、以下のように指定ができます。

let physicsShape = SCNPhysicsShape(geometry: sphere, options: nil)

3Dモデルから作成した場合は、SCNNodeを引数に以下のように指定。

let physicsShape = SCNPhysicsShape(node: modelNode, options: nil)

これらのコードは、物理的な形は、見た目の形(Geometry)から作る ということをしています。

複雑な形のモノの場合、それの物理計算のコストも高くなる。
物理計算上では、そんなに複雑でなくてシンプルな形でいいよ って場合も結構あるかと。
そんな時には、このメソッドの第二引数のoptionsを設定します。

SCNPhysicsShapeTypeKeyで設定できる以下の3つ。
  • SCNPhysicsShapeTypeBoundingBox
  • SCNPhysicsShapeTypeConvexHull
  • SCNPhysicsShapeTypeConcavePolyhedron

下に行くほど、より細かい(より遅い)設定になります。

とっても重い処理

簡単に書けるなーですが、処理としてはとても重い。
単純に考えても、「当たり判定をして、描画して」という繰り返しを1秒に60回(60fps)しているわけですから。
シミュレーターで動かすと、マシンのファンが高速回転始めてしまいます。
SceneKitは、MetalとOpenGLで動かすことはできますが、シミュレーターではMetalはうごかないのでツライのであろう...
サンプルを動かす程度ならいいですが、実際開発をする場合には実機は必須ですw

ちなみに、今回説明した内容は、わかりやすくコードで全てかきましたが、SceneKitのEditorでも設定することができます。
サンプルコードでいうと..."4秒後にもう一つ球体を追加" 以外は全部Editorで設定できます。


さて、次回は、Gemmyで使っているKinematicでのprogrammaticallyな方法をば。


作成したアプリはこちら↓


Standland - 座りすぎ解消!スタンドランド



• • •