Blog
Swift + Playgraoundメモ 2 – SpriteKitで物理シミュレーション
注意: Swiftのソースは全てXcode 6 Beta 6で確認しています。それ以前のバージョンでの動作は確認していません。また今後のXcodeのバージョンで動く保証もありませんので、ご了承ください。
Swift + Playground phyisics from Atsushi Tadokoro on Vimeo.
前回の「Swift + Playgraoundメモ 1 – SpriteKitでアニメーション」の続き。今回は、物理シミュレーションをやってみようかと。
まず、SKViewでビューを生成してそこのSKSceeのシーンを追加するところまでは、前回の通り。
import SpriteKit import XCPlayground // あとで使い回せるように幅と高さを定数に let width:CGFloat = 400 let height:CGFloat = 400 // SpriteKitのViewを生成 let view:SKView = SKView(frame: CGRectMake(0, 0, width, height)) // PlaygroundのTimelineに表示 XCPShowView("Live View", view) // シーンを生成、背景を黒にしてビューに追加 let scene:SKScene = SKScene(size: CGSizeMake(width, height)) scene.backgroundColor = SKColor.blackColor(); view.presentScene(scene)
おそらくゲームなどの用途を想定しているのか、SpriteKitにはあらかじめ物理エンジンが用意されている。Box2Dなどの物理エンジンを使用したことがあれば、さほど違和感なく使える感じ。
Box2Dでもそうだが、まず物理法則が適用される世界(World)を生成して、そこで重力や摩擦などのパラメータを指定する。そこに、物体(Body)を配置して、あとはエンジンの演算に丸投げというのが基本的な使い方。SpriteKitでは、世界(World)のためのクラスとしてSKPhysicsWorld、そして物体(Body)のためにSKPhysicsBodyというクラスをつかう。
SKPhysicsWorldとSKPhysicsBodyは、新規にインスタンスの生成などする必要はなく、あらかじめSKSceneのプロパティとして存在している。例えば、SKSceneのインスタンスがsceneだとすると
- SKPhysicsWorld : scene.physicsWorld
- SKPhysicsBody : scene.physicsBody
にパラメータを設定してやれば良い。
さっそく、まずはSKPhysicsWorldを追加。下向きに1の強さの重量を設定している。
import SpriteKit import XCPlayground let width:CGFloat = 400 let height:CGFloat = 400 let canvasWidth: UInt32 = UInt32(width) let canvasHeight: UInt32 = UInt32(height) let view:SKView = SKView(frame:CGRectMake(0, 0, width, height)) let scene:SKScene = SKScene(size:CGSizeMake(width, height)) scene.backgroundColor = SKColor.blackColor() view.presentScene(scene) XCPShowView("my view", view) // 物理エンジンのための世界(World)を設定 // 下向きに1の強さの重力 scene.physicsWorld.gravity = CGVectorMake(0, -1)
ここに、まずは運動させる形(SKShapeNode)を追加する。今回は、四角ではなく丸にしてみる。
import SpriteKit import XCPlayground let width:CGFloat = 400 let height:CGFloat = 400 let canvasWidth: UInt32 = UInt32(width) let canvasHeight: UInt32 = UInt32(height) let view:SKView = SKView(frame:CGRectMake(0, 0, width, height)) let scene:SKScene = SKScene(size:CGSizeMake(width, height)) scene.backgroundColor = SKColor.blackColor() view.presentScene(scene) XCPShowView("my view", view) // 物理エンジンのための世界(World)を設定 // 下向きに1の強さの重力 scene.physicsWorld.gravity = CGVectorMake(0, -1) // 円を追加 let radius:CGFloat = 6 let shape = SKShapeNode(circleOfRadius:radius) shape.fillColor = SKColor.blueColor() shape.position = CGPoint(x:200, y:380) scene.addChild(shape)
これで(200,300)の位置に半径6の円ができあがる。
いよいよここに物理法則を適用してみたい。やり方は実にシンプルで、作成したShapeを、SKSceneのphysicsBodyにするだけ。
import SpriteKit import XCPlayground let width:CGFloat = 400 let height:CGFloat = 400 let canvasWidth: UInt32 = UInt32(width) let canvasHeight: UInt32 = UInt32(height) let view:SKView = SKView(frame:CGRectMake(0, 0, width, height)) let scene:SKScene = SKScene(size:CGSizeMake(width, height)) scene.backgroundColor = SKColor.blackColor() view.presentScene(scene) XCPShowView("my view", view) // 物理エンジンのための世界(World)を設定 // 下向きに1の強さの重力 scene.physicsWorld.gravity = CGVectorMake(0, -1) // 円を追加 let radius:CGFloat = 6 let shape = SKShapeNode(circleOfRadius:radius) shape.fillColor = SKColor.blueColor() shape.position = CGPoint(x:200, y:380) scene.addChild(shape) // 円を物理法則が適用される物体に shape.physicsBody = SKPhysicsBody(circleOfRadius:radius)
これで、配置した円が重力にひっぱられて下に向かって落ちていくはず。簡単!!
ただし、この円は画面の枠からはみ出してすぐに見えなくなってしまう。これではつまらないので、画面の四隅に壁を設定してみる。こんな感じでOK。
import SpriteKit import XCPlayground let width:CGFloat = 400 let height:CGFloat = 400 let canvasWidth: UInt32 = UInt32(width) let canvasHeight: UInt32 = UInt32(height) let view:SKView = SKView(frame:CGRectMake(0, 0, width, height)) let scene:SKScene = SKScene(size:CGSizeMake(width, height)) scene.backgroundColor = SKColor.blackColor() view.presentScene(scene) XCPShowView("my view", view) // 物理エンジンのための世界(World)を設定 // 下向きに1の強さの重力 scene.physicsWorld.gravity = CGVectorMake(0, -1) // 円を追加 let radius:CGFloat = 6 let shape = SKShapeNode(circleOfRadius:radius) shape.fillColor = SKColor.blueColor() shape.position = CGPoint(x:200, y:380) scene.addChild(shape) // 円を物理法則が適用される物体に shape.physicsBody = SKPhysicsBody(circleOfRadius:radius) // 四隅に壁を設定 scene.physicsBody = SKPhysicsBody(edgeLoopFromRect:CGRect(x:0, y:0, width:width, height:height))
これで、壁でバウンドするようになったはず!
この方法で、いくらでも物体は追加できる。例えば円を100個に増やしてみる。乱数の生成にはarc4random_uniformという関数を利用。
import SpriteKit import XCPlayground let width:CGFloat = 400 let height:CGFloat = 400 let canvasWidth: UInt32 = UInt32(width) let canvasHeight: UInt32 = UInt32(height) let view:SKView = SKView(frame:CGRectMake(0, 0, width, height)) let scene:SKScene = SKScene(size:CGSizeMake(width, height)) scene.backgroundColor = SKColor.blackColor() view.presentScene(scene) XCPShowView("my view", view) // 物理エンジンのための世界(World)を設定 // 下向きに1の強さの重力 scene.physicsWorld.gravity = CGVectorMake(0, -1) // 円を100コ追加 for var i = 0; i < 100; i++ { let radius:CGFloat = 6 let shape = SKShapeNode(circleOfRadius:radius) shape.fillColor = SKColor.blueColor() shape.position = CGPoint( x: CGFloat(arc4random_uniform(canvasWidth)), y: CGFloat(arc4random_uniform(canvasHeight))) scene.addChild(shape) // 円を物理法則が適用される物体に shape.physicsBody = SKPhysicsBody(circleOfRadius:radius) } // 四隅に壁を設定 scene.physicsBody = SKPhysicsBody(edgeLoopFromRect:CGRect(x:0, y:0, width:width, height:height))
きちんと、100個の丸が相互に衝突判定している。
さらに、それぞれの円に物理的なパラメータを設定してみる。ここでは以下のパラメータを設定してみた。
- friction: 摩擦 → 0.1
- restitution: 反発力 → 0.99
- mass: 質量 → 1.0
import SpriteKit import XCPlayground let width:CGFloat = 400 let height:CGFloat = 400 let canvasWidth: UInt32 = UInt32(width) let canvasHeight: UInt32 = UInt32(height) let view:SKView = SKView(frame:CGRectMake(0, 0, width, height)) let scene:SKScene = SKScene(size:CGSizeMake(width, height)) scene.backgroundColor = SKColor.blackColor() view.presentScene(scene) XCPShowView("my view", view) // 物理エンジンのための世界(World)を設定 // 下向きに1の強さの重力 scene.physicsWorld.gravity = CGVectorMake(0, -1) // 円を100コ追加 for var i = 0; i < 100; i++ { let radius:CGFloat = 6 let shape = SKShapeNode(circleOfRadius:radius) shape.fillColor = SKColor.blueColor() shape.position = CGPoint( x: CGFloat(arc4random_uniform(canvasWidth)), y: CGFloat(arc4random_uniform(canvasHeight))) scene.addChild(shape) // 円を物理法則が適用される物体に shape.physicsBody = SKPhysicsBody(circleOfRadius:radius) // 物理パラメータを設定 shape.physicsBody.friction = 0.01 shape.physicsBody.restitution = 0.99 shape.physicsBody.mass = 1.0 } // 四隅に壁を設定 scene.physicsBody = SKPhysicsBody(edgeLoopFromRect:CGRect(x:0, y:0, width:width, height:height))
これで完成!! 30行ちょっとのコードでここまでできてしまうので、Swift楽しいなあ。しかもライブコーディング。