Swift OS X native app test from Atsushi Tadokoro on Vimeo.
PlaygroundでSceneKitをつかって3Dでいろいろ遊んでいるうちに、このスケッチをOS Xのアプリにしてみたい、という欲求が高まってきた。実際にやってみたら、思いの外簡単だったので、メモとして記録。
まずは、Playgroundでプログラムを作成。この段階で、ライブコーディングで色や大きさ動きなど細かなパラメータの調整を終えておく。まさにスケッチする感覚。
import SceneKit
import XCPlayground
let width:CGFloat = 400
let height:CGFloat = 400
// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene
// 背景の設定
view.backgroundColor = NSColor.blackColor()
// ライティング
view.autoenablesDefaultLighting = true
// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)
scene.rootNode.addChildNode(cameraNode)
// 0.0〜1.0の範囲でランダムなCGFloat生成
func randomCGFloat() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
// 立方体を100個回転
for var i = 0; i < 100; i++ {
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.03)
box.firstMaterial.diffuse.contents = NSColor(
calibratedHue: randomCGFloat(),
saturation: randomCGFloat() * 0.5 + 0.5, brightness: 2.0, alpha: 0.75)
box.firstMaterial.specular.contents = NSColor.whiteColor()
var boxNode = SCNNode(geometry: box)
boxNode.rotation = SCNVector4(x: randomCGFloat(), y: randomCGFloat(), z: randomCGFloat(), w: CGFloat(M_PI));
boxNode.runAction(SCNAction.repeatActionForever(
SCNAction.rotateByAngle(
CGFloat(M_PI * 2.0),
aroundAxis: SCNVector3(x: randomCGFloat(), y: randomCGFloat(), z: randomCGFloat()),
duration: NSTimeInterval(randomCGFloat() * 10.0 + 10.0))))
scene.rootNode.addChildNode(boxNode)
}
// タイムラインに表示
XCPShowView("View", view)
このスケッチを、OS Xのアプリ化したい。最近のXcodeには、便利なテンプレートがいろいろ用意されていて、これを利用していきたい。SceneKitを使用した3Dのプログラムは、“Game”というテンプレートをつかうと簡単にできるみたいなので、活用してみる。
まずは、Xcodeで、メニューから File > New > Project... で新規プロジェクトを作成する。テンプレートを選択する画面から、OS X > Application > Game を選択。
“Next”ボタンを押して次に進むと、プロジェクトの詳細設定画面になる。ここで、Product Nameなどは適当な名前をつけて、LanguageをSwiftに、Game TechnologyをSceneKitに設定する。
さらに“Next”ボタンを押すと、Projectを保存する場所を聞いてくるので、適当な場所を選択して保存すると、新規プロジェクトが生成される。
生成されたプロジェクトのファイルリストをみると、いろいろなファイルが自動的に生成されてる。試しにこのままビルドしてみる。ちょっとチープな戦闘機が、くるくる回転するアプリケーションが出来上がる。このアプリを改造して、先程Playgroundで作成したアプリを移植したい。
ファイルリストの中で、変更するファイルは “GameViewController.swift” のみ。早速開いてみると、以下のような内容。
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
@IBOutlet weak var gameView: GameView!
override func awakeFromNib(){
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.dae")
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light.type = SCNLightTypeAmbient
ambientLightNode.light.color = NSColor.darkGrayColor()
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNodeWithName("ship", recursively: true)
// animate the 3d object
ship.runAction(SCNAction.repeatActionForever(SCNAction.rotateByX(0, y: 2, z: 0, duration: 1)))
let animation = CABasicAnimation(keyPath: "rotation")
animation.toValue = NSValue(SCNVector4: SCNVector4(x: CGFloat(0), y: CGFloat(1), z: CGFloat(0), w: CGFloat(M_PI)*2))
animation.duration = 3
animation.repeatCount = MAXFLOAT //repeat forever
ship.addAnimation(animation, forKey: nil)
// set the scene to the view
self.gameView!.scene = scene
// allows the user to manipulate the camera
self.gameView!.allowsCameraControl = true
// show statistics such as fps and timing information
self.gameView!.showsStatistics = true
// configure the view
self.gameView!.backgroundColor = NSColor.blackColor()
}
}
このコードが、飛行機の3Dモデルを読み込んでSCNSceneで表示しているようだ。ここを、先程のPlaygroundのコードに差し替える。View(SCNView)に関しては、“GameView.swift”で定義されているので、SCNSeneに関する部分のみを移植する。
こんな感じになった。
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
@IBOutlet weak var gameView: GameView!
override func awakeFromNib(){
// シーン生成
let scene = SCNScene()
// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)
scene.rootNode.addChildNode(cameraNode)
// 0.0〜1.0の範囲でランダムなCGFloat生成
func randomCGFloat() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
// 立方体を100個回転
for var i = 0; i < 100; i++ {
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.03)
box.firstMaterial.diffuse.contents = NSColor(
calibratedHue: randomCGFloat(),
saturation: randomCGFloat() * 0.5 + 0.5, brightness: 2.0, alpha: 0.75)
box.firstMaterial.specular.contents = NSColor.whiteColor()
var boxNode = SCNNode(geometry: box)
boxNode.rotation = SCNVector4(x: randomCGFloat(), y: randomCGFloat(), z: randomCGFloat(), w: CGFloat(M_PI));
boxNode.runAction(SCNAction.repeatActionForever(
SCNAction.rotateByAngle(
CGFloat(M_PI * 2.0),
aroundAxis: SCNVector3(x: randomCGFloat(), y: randomCGFloat(), z: randomCGFloat()),
duration: NSTimeInterval(randomCGFloat() * 10.0 + 10.0))))
scene.rootNode.addChildNode(boxNode)
}
// シーンをビューに設定
self.gameView!.scene = scene
// 複数のカメラを使用できるように
self.gameView!.allowsCameraControl = true
// FPSなどの統計情報を表示
self.gameView!.showsStatistics = true
// 背景色を黒に
self.gameView!.backgroundColor = NSColor.blackColor()
}
}
早速ビルドしてみると、最初のPlaygroundのスケッチが、ネイティブアプリとしてウィンドウで表示された! 表示速度も速い!
しかし、Swift以降、OS Xのアプリケーション開発のハードルも、だいぶ下がったんじゃなかろうか。Playgroundとあわせて、いろいろ試していきたい。
注意: Swiftのソースは全てXcode 6 Beta 6で確認しています。それ以前のバージョンでの動作は確認していません。また今後のXcodeのバージョンで動く保証もありませんので、ご了承ください。
Generative 3D with Swift Playground from Atsushi Tadokoro on Vimeo.
「Swift + Playgraoundメモ 1 – SpriteKitでアニメーション」、「Swift + Playgraoundメモ 2 – SpriteKitで物理シミュレーション」に続く、Swift + Playgroundシリーズ第3弾。今回は、3DCGに挑戦してみた。
2Dのグラフィクスの表示やアニメーションには、SprikeKitをつかったのだけれど、3Dではその代わりにSceneKitというフレームワークを使うらしい。説明を読むと、OpenGLなどと比べると、より上位のレベルで、シーンの中の物体とその動きを記述できるとのこと。早速、Swift+Playgroundの環境で使ってみたい。
まずは、SpriteKitのときと同様に、Viewを定義して、そこにSceneに追加する。3DのSceneには、SCNSceneを使用する。
import SceneKit
import XCPlayground
let width:CGFloat = 400
let height:CGFloat = 400
// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene
ここにまずは背景色と照明(ライティング)の設定をしたい。こんな感じでコードを追加。Playgroundのタイムラインでの表示のためのコードも追加している。
import SceneKit
import XCPlayground
let width:CGFloat = 400
let height:CGFloat = 400
// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene
// 背景の設定
view.backgroundColor = NSColor.blackColor()
// ライティング
view.autoenablesDefaultLighting = true
// タイムラインに表示
XCPShowView("View", view)
次にカメラを配置する。3DCGは、三次元の情報を二次元平面に投影して、あたかも奥行があるように見せているものなので、どこから物体を見るのかという「視点」の設定が必須となる。
SceneKitでは、まずカメラを生成したあと、それをシーンのノードとして登録して追加するという手順を踏むようだ。こんな感じ。
import SceneKit
import XCPlayground
let width:CGFloat = 400
let height:CGFloat = 400
// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene
// 背景の設定
view.backgroundColor = NSColor.blackColor()
// ライティング
view.autoenablesDefaultLighting = true
// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
これで、環境の準備ができた。さっそく3Dの物体を表示してみたい。まずは、シンプルに立方体を置いてみる。立体感がわかるよう配置したあと、ちょっと回転させて斜めから見るようにしている。
import SceneKit
import XCPlayground
let width:CGFloat = 400
let height:CGFloat = 400
// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene
// 背景の設定
view.backgroundColor = NSColor.blackColor()
// ライティング
view.autoenablesDefaultLighting = true
// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
// 立方体のジオメトリを生成
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.1)
// ノードに追加
var boxNode = SCNNode(geometry: box)
// ちょっと回転
boxNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: CGFloat(M_PI * 0.25));
// ノードをシーンに追加
scene.rootNode.addChildNode(boxNode)
// タイムラインに表示
XCPShowView("View", view)
これで、既にリアルな物体として表示される。マテリアル(質感)はデフォルトでも既に設定されているようだ。
次にこのデフォルトのマテリアルから独自なものに変更してみる。
import SceneKit
import XCPlayground
let width:CGFloat = 400
let height:CGFloat = 400
// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene
// 背景の設定
view.backgroundColor = NSColor.blackColor()
// ライティング
view.autoenablesDefaultLighting = true
// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
// 立方体のジオメトリを生成
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.1)
// ノードに追加
var boxNode = SCNNode(geometry: box)
// ちょっと回転
boxNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: CGFloat(M_PI * 0.25));
// ノードをシーンに追加
scene.rootNode.addChildNode(boxNode)
// マテリアルを変更
box.firstMaterial.diffuse.contents = NSColor.blueColor();
box.firstMaterial.specular.contents = NSColor.whiteColor();
// タイムラインに表示
XCPShowView("View", view)
最後にこの立方体にアニメーションを適用してみる。アニメーションにはCABasicAnimationクラスを使用する。いろいろな動きの定義が可能なのだけれど、ここでは、クルクルと回転させる動きにしてみることに。
import SceneKit
import XCPlayground
let width:CGFloat = 400
let height:CGFloat = 400
// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene
// 背景の設定
view.backgroundColor = NSColor.blackColor()
// ライティング
view.autoenablesDefaultLighting = true
// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
// 立方体のジオメトリを生成
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.1)
// ノードに追加
var boxNode = SCNNode(geometry: box)
// ちょっと回転
boxNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: CGFloat(M_PI * 0.25));
// ノードをシーンに追加
scene.rootNode.addChildNode(boxNode)
// マテリアルを変更
box.firstMaterial.diffuse.contents = NSColor.blueColor();
box.firstMaterial.specular.contents = NSColor.whiteColor();
// アニメーション
var spin = CABasicAnimation(keyPath: "rotation")
spin.toValue = NSValue(SCNVector4:SCNVector4(x: 0.5, y: 1, z: 0.1, w: CGFloat(M_PI) * 2.0))
spin.duration = 5
spin.repeatCount = HUGE
boxNode.addAnimation(spin, forKey: "spin")
// タイムラインに表示
XCPShowView("View", view)
これで、アニメーションつきの3Dグラフィクスプログラミングの完成!! 簡単!!
最後にすこし応用的なサンプル。3Dの図形を立方体からトーラスに変更し、for文でくりかえして大量に描画する。さらに回転するアニメーションの速度を乱数で変化させて、いろいろな速度で回転するようにしてみた。
import SceneKit
import XCPlayground
let width:CGFloat = 600
let height:CGFloat = 600
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
var scene = SCNScene()
view.scene = scene
view.backgroundColor = NSColor.blackColor()
view.autoenablesDefaultLighting = true
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)
for var i = 0; i < 40; i++ {
var torus = SCNTorus(
ringRadius: CGFloat(arc4random_uniform(150)) / 100.0,
pipeRadius: 0.05)
var torusNode = SCNNode(geometry: torus)
scene.rootNode.addChildNode(torusNode)
torus.firstMaterial.diffuse.contents = NSColor(
calibratedHue: CGFloat(arc4random_uniform(100)) / 300.0 + 0.3,
saturation: 0.5,
brightness: 1.2,
alpha: 0.9)
torus.firstMaterial.specular.contents = NSColor.yellowColor()
var spin = CABasicAnimation(keyPath: "rotation")
spin.toValue = NSValue(SCNVector4:SCNVector4(
x: CGFloat(random()),
y: CGFloat(random()),
z: CGFloat(random()),
w: CGFloat(M_PI) * 2.0))
spin.duration = NSTimeInterval(arc4random_uniform(10) + 10)
spin.repeatCount = HUGE
torusNode.addAnimation(spin, forKey: "spin")
}
XCPShowView("View", view)
たったこれだけのコードで複雑な3D図形が生成された。楽しい!!
注意: 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楽しいなあ。しかもライブコーディング。
注意: Swiftのソースは全てXcode 6 Beta 6で確認しています。それ以前のバージョンでの動作は確認していません。また今後のXcodeのバージョンで動く保証もありませんので、ご了承ください。
最近は、XcodeのPlaygroundのようなライブコーディング環境が、これからのプログラミングの姿になっていくのではと勝手に期待しているところもあり、少しずつ勉強していきたいと考えているところ。こうやってブログにメモ的に残すことで自分の知識も整理されるかも、ということで、まだまだ試行錯誤中なのだけれど、現状でわかってきたところなど書いてみる。
Swiftの基本文法は、現状で一番まとまっているのは、やはり本家Appleの資料みたいだ。
現状は、ここ資料を見つつ、あとはStackoverflowなどでググりながら、いろいろ探っている段階。
まずは、SwiftとPlaygroundで、Processing的なアニメーションをつくってみたいなあと思ってちょっと調べてみると、iOSやOS XのSDKには、SpriteKitというのがあって、これを使っていけばSDKの機能を利用しながら効率的にアニメーションが作れそうな感じ。
とりあえず、やってみた。
まずは、Xcodeで新規にPlaygroundプロジェクトを作成。そこに必要なライブラリをimport。SpriteKitとあわせて、Playground用のライブラリXCPlaygroundも入れておく。
import SpriteKit
import XCPlayground
ここにまずビュー(View)というのを生成する必要があるみたい。SpriteKitでは、SKViewというクラスで実装されている。Processingでいうところの size() 的な感じで、幅と高さを指定してSKViewを生成する。
import SpriteKit
import XCPlayground
// あとで使い回せるように幅と高さを定数に
let width:CGFloat = 640
let height:CGFloat = 480
// SpriteKitのViewを生成
let view:SKView = SKView(frame: CGRectMake(0, 0, width, height))
// PlaygroundのTimelineに表示
XCPShowView("Live View", view)
これで、もう画面が生成されている。楽ちんだ!
ビューを生成したら、ここにシーンを追加してあげる必要があるようだ。シーンはSKSceneで定義する。ここで背景の色なども設定可能。背景を黒にして追加してみる。
import SpriteKit
import XCPlayground
// あとで使い回せるように幅と高さを定数に
let width:CGFloat = 640
let height:CGFloat = 480
// 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)
これで背景色黒のシーンが生成できた。いよいよ形を描いてみる。形を追加するにはSKSpriteNodeというクラスで形を定義して、最後にシーンにaddChild()すると良いらしい。ちょっとActionScript3風味。
import SpriteKit
import XCPlayground
// あとで使い回せるように幅と高さを定数に
let width:CGFloat = 640
let height:CGFloat = 480
// SpriteKitのビューを生成
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)
// 青い四角を追加
let box:SKSpriteNode = SKSpriteNode(color:SKColor.blueColor(), size:CGSizeMake(200, 200))
box.position = CGPointMake(width/2.0, height/2.0)
scene.addChild(box)
これで青い四角形が画面中央に配置される。文法もさほど難しくない感じ。
この形をアニメーションしてみたい。SpriteKitに配置したSKSpriteNodeは、SKActionクラスをつかうとあらかじめ用意された動きを簡単に付加できるようだ。試しに四角をぐるぐる回してみる。
まずは、1秒で1回転させてみる。こんな感じ。
import SpriteKit
import XCPlayground
// あとで使い回せるように幅と高さを定数に
let width:CGFloat = 640
let height:CGFloat = 480
// SpriteKitのビューを生成
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)
// 青い四角を追加
let box:SKSpriteNode = SKSpriteNode(color:SKColor.blueColor(), size:CGSizeMake(200, 200))
box.position = CGPointMake(width/2.0, height/2.0)
scene.addChild(box)
// 1秒で1回転
let pi:CGFloat = 3.1415926
box.runAction(SKAction.rotateByAngle(pi * 2.0, duration: 1))
ずっと回転させ続けるには、SKActionのrepeatActionForeverというメソッドをつかうと良いみたい。
import SpriteKit
import XCPlayground
// あとで使い回せるように幅と高さを定数に
let width:CGFloat = 640
let height:CGFloat = 480
// SpriteKitのビューを生成
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)
// 青い四角を追加
let box:SKSpriteNode = SKSpriteNode(color:SKColor.blueColor(), size:CGSizeMake(200, 200))
box.position = CGPointMake(width/2.0, height/2.0)
scene.addChild(box)
// 1秒で1回転のペースで、ずっと回転
let pi:CGFloat = 3.1415926
box.runAction(SKAction.repeatActionForever(SKAction.rotateByAngle(pi * 2.0, duration: 1)))
これで、永遠にぐるぐる回る青い四角形のアニメーションが完成。まだまだ、手探り状態だけれど、なんとなく雰囲気はつかめてきた。
(たぶん)つづく…
テキストエディター何を使うかという問題、常に議論(炎上)の種になりがちなテーマでいろいろ断言すると反応が怖いのだけれど、ここしばらく使用してみてGithubで開発されたAtomが素晴しいということで、紹介記事を書いてみる。
これまでのエディター遍歴を思い返してみると、最初に習った本格的なテキストエディターがEmacsだったので、かなり長いことEmacs派として過ごしてきた。今でもEmacsキーバインドが使えない環境にはストレス感じる。かな漢字変換もSKKに無理矢理馴染んでみたので、他の変換を使えない体になってしまった。
そういう事情もあり、個人的にテキストエディタを選ぶ基準として、
- できるだけオリジナルに忠実なEmacsキーバインドが使える
- SKK系のかな漢字変換が使用可能
また、最近になって構造的な文章を書く際はMarkdownで記述することが多いので
- Markdownモードがあり、簡単にプレビューできる
という基準が必須となりつつある。
そういった基準を満たすエディターとして、Sublime Textは良くできてると思う。ただし、有料なので大学の授業などで一斉に採用というのもちょっと難しい。あと、Aqua SKKとの相性がちょっと微妙な感じもした。
で、最近登場したAtomに乗り換えてみたのだけれど、最初のうちはちょっと設定が落ちつかず四苦八苦することもあったのだが、ここにきて大分落ち着いてきた。個人的な感想としては、Sublime Textの代替として十分使用可能なんじゃないかと感じてる。
さらに、Sublime Textと比較しても良いかと感じたのは
- フリーソフトウェア (MIT License)
- GitHub Markdownとの相性が良い(Github純正なので)
- Emacsキーバインドをかなり忠実に再現可能
- パッケージの管理が楽
といったあたりか。
いろいろ設定してみて、個人的に必須かと思ったパッケージはデフォルトで同梱されるパッケージを除くと
ここらへん入れると、だいぶ快適になった。
ちなみにキーマップは
'.editor':
'ctrl-i': 'editor:auto-indent'
'.editor:not(.mini)':
'ctrl-j': 'none'
こういう設定で、Xcode風にControl+iで自動インデントをするように。また、Control+jの機能を殺すことで、SKKとのバッティングを防いでみた。
そんな感じでAtom素晴しいので、これからもいろいろ試していきたい!
ちなみに、この投稿の原稿もAtomで作成。Markdownで書いたのを、HTMLに書き出してWordpressにコピペした。らくちん!