yoppa.org


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の円ができあがる。

screenshot_216

いよいよここに物理法則を適用してみたい。やり方は実にシンプルで、作成した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))

これで、壁でバウンドするようになったはず!

screenshot_217

この方法で、いくらでも物体は追加できる。例えば円を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個の丸が相互に衝突判定している。

screenshot_218

さらに、それぞれの円に物理的なパラメータを設定してみる。ここでは以下のパラメータを設定してみた。

  • 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))

screenshot_219

これで完成!! 30行ちょっとのコードでここまでできてしまうので、Swift楽しいなあ。しかもライブコーディング。