yoppa.org


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 を選択。

screenshot_250

“Next”ボタンを押して次に進むと、プロジェクトの詳細設定画面になる。ここで、Product Nameなどは適当な名前をつけて、LanguageをSwiftに、Game TechnologyをSceneKitに設定する。

screenshot_254

さらに“Next”ボタンを押すと、Projectを保存する場所を聞いてくるので、適当な場所を選択して保存すると、新規プロジェクトが生成される。

生成されたプロジェクトのファイルリストをみると、いろいろなファイルが自動的に生成されてる。試しにこのままビルドしてみる。ちょっとチープな戦闘機が、くるくる回転するアプリケーションが出来上がる。このアプリを改造して、先程Playgroundで作成したアプリを移植したい。

screenshot_252

ファイルリストの中で、変更するファイルは “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のスケッチが、ネイティブアプリとしてウィンドウで表示された! 表示速度も速い!

screenshot_253

しかし、Swift以降、OS Xのアプリケーション開発のハードルも、だいぶ下がったんじゃなかろうか。Playgroundとあわせて、いろいろ試していきたい。