Blog
Swiftメモ – PlaygroundのスケッチをOS Xのネイティブアプリへ
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とあわせて、いろいろ試していきたい。