SingleViewControllerにSpriteKitを組み込んで、タッチイベントや物理演算を実装してみた

SpriteKitを利用する場合には、XcodeのGameテンプレートを使えばそのまま利用できます。
しかしながら、それではSpriteKitの仕組みがブラックボックスになってしまい、またGameテンプレートを使用していない既存のアプリに組み込むことはできません。

今回、SpriteKitの理解という目的もあり、Gameテンプレートを使わず１からSpriteKitを組み込む方法を試したいと思います。


本記事では、最終的には以下のような簡単なアプリを作成します。
機能としては、画面内に文字列を表示し、画面内をタッチすると物理演算されたオブジェクトが出現する、というものです。
→[完成版コード](https://github.com/soymsk/SpriteLearn)


![iOS Simulator Screen Shot 2015.01.29 20.39.40.png](https://qiita-image-store.s3.amazonaws.com/0/67687/ce27d1ba-36c2-f5d6-ffd5-baafe25d86ed.png "iOS Simulator Screen Shot 2015.01.29 20.39.40.png")


## プロジェクトの作成
今回はSigleView Applicationでプロジェクトを作成します。
※ViewControllerを自分で配置すればいいだけですので、空のプロジェクトから始めてもかまいません。

## SpriteKitの組み込み
SpriteKitは、ViewController->ViewにSpriteKit用のViewクラスとして **SKView** クラスを設定することで利用できます。
このSKView上に、ゲームでの1画面に相当する **SKScene** を配置することで画面上に表示する、という仕組みです。
ゲーム内で画面の切替をする場合には、SKView上で複数のSKSceneを切り替えることになります。


### ストーリーボード上でViewのClassを"SKView"に設定
まずはSKView, SKSceneを導入してみます。


Xcodeのストーリーボードの設定でViewのカスタムクラスをSKViewに設定します。
![スクリーンショット 2015-01-29 17.15.54.png](https://qiita-image-store.s3.amazonaws.com/0/67687/9f689dd0-1751-263f-e12a-8780569c2cf4.png "スクリーンショット 2015-01-29 17.15.54.png")


これでViewController上のViewがSKViewのインスタンスとして取得できるようになります。

### ViewControllerにSKView用のコードを追加
```ViewController.swift
/*　省略 */
import UIKit
import SpriteKit // 追加
/*　省略 */

    override func viewWillAppear(animated: Bool) {
        let skView = self.view as SKView
        // 画面サイズと同じ大きさのscene作成
        let scene = GameScene(size: skView.bounds.size) // ストーリーボードでviewのクラスをSKViewに設定しているので、SKViewのインスタンスとして取得できる
        skView.presentScene(scene) // SKView上にsceneを設定
    }
        
    override func viewDidLoad() {
        super.viewDidLoad()
        let skView = self.view as SKView
        skView.showsFPS = true // FPS表示
        skView.showsNodeCount = true //画面内要素数表示
    }
```

### SKSceneを継承したクラスを作成
上のコードでGameSceneというクラスが出てきますが、これはSKSceneを継承した独自のクラスになります。
GameScene.swiftファイルを作成して、以下のコードを追加します。


```GameScene.swift
import SpriteKit

class GameScene : SKScene {
}
```
これでSpriteKitを使用する準備は完了です。

#### ビルド結果
ここでビルドしてみるとFPSとノード数が画面に表示されるだけの画面が確認できます。

![スクリーンショット 2015-01-29 17.33.08.png](https://qiita-image-store.s3.amazonaws.com/0/67687/9e9e041f-5a60-0b8e-1f8c-a6a3c95c8c75.png "スクリーンショット 2015-01-29 17.33.08.png")


今回は1画面なのでGameSceneだけですが、複数画面用意する場合はそれぞれクラスを作成する形になります。

それではこのGameSceneに要素を追加していきます。

### ラベルを表示
まずは基本、テキストラベルを表示させてみます。

```swift
class GameScene : SKScene {
    var initiated: Bool = false;

    /* sceneがSKView上に表示される度に呼ばれる関数 */
    override func didMoveToView(view: SKView) {
        if ( !initiated ) { //初期化時のみ実行
            self.initContent()
            self.initiated = true
        }
    }
    
    func initContent() {
        self.backgroundColor = SKColor.blueColor() // 背景色設定
        var label = self.newHelloNode() // テキストノードの作成
        self.addChild(label) // sceneにテキストノードを追加
    }
    
    func newHelloNode(Void) -> SKLabelNode {
        var helloNode = SKLabelNode(text: "Hello, World!")
        helloNode.fontColor = UIColor(red: 1.0, green:1.0, blue: 1.0, alpha: 1)
        helloNode.fontSize = 60
        helloNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))

        return helloNode
    }
}
```
didMoveToViewはSceneがSKView上に表示される度に呼ばれます。
要素の追加は初回だけ行いたいので、フラグ管理しています。
そもそも初期化用の関数とかありそうですが、探したサンプルではだいたいこうなっていました。

#### ビルド結果

![スクリーンショット 2015-01-29 17.38.40.png](https://qiita-image-store.s3.amazonaws.com/0/67687/0261a4f1-84f8-6d44-ee7b-9b475d9ac67a.png "スクリーンショット 2015-01-29 17.38.40.png")


背景色が設定され、ラベルが表示されていることが確認できます。

### オブジェクトの配置とタッチイベントの取得
画面上をタッチして、タッチした場所にゲーム上のオブジェクトを設置してみましょう。

#### オブジェクトの配置
GameScene.swift内に次の関数を追加します。

```GameScene.swift
    func addShape(location: CGPoint) {
        var size = self.frame.width/100.0
        let shape = SKShapeNode(circleOfRadius: size)
        shape.fillColor = UIColor.whiteColor() // 塗りつぶし設定
        shape.position = location //表示位置設定
        
        self.addChild(shape) //画面へのオブジェクト追加
    }
```
表示位置としてlocationを受け取り、そこに円オブジェクトを配置する関数です。
シェイプ（図形)オブジェクトを生成しているのは```SKShapeNode```です。引数の渡し方で四角だったり、円だったり、形を変更できます。サイズは画面サイズから適当に計算しています。

これを実行すれば画面上に白い円が表示されることになります。

#### タッチイベントの取得
SKSceneクラスにはタッチイベント発生時に呼び出される関数が定義されています。
タッチイベントにはいくつかありますが、ここではタッチ開始時に呼び出される```touchesBegan```を使います。

```GameScene.swift
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        let touch = touches.allObjects[0] as UITouch // タッチオブジェクトの取得
        let location = touch.locationInNode(self) //　タッチ位置の取得
        
        // 図形の追加
        addShape(location)
    }
```
最初の2行でタッチ位置がCGPointオブジェクトとして取得できます。
これを上で作成したaddShape関数に渡してやるだけです。

###　物理演算の追加
さてこのままだと面白く無いので物理演算を追加してみましょう。
物理演算の追加自体はとても簡単で、シェイプオブジェクトのプロパティをちょっと修正するだけです。
まずは円オブジェクト

```GameScene.swift
    func addShape(location: CGPoint) {
    	/* 省略 */
        
        // 物理演算の追加
        shape.physicsBody = SKPhysicsBody(circleOfRadius: size)
        shape.physicsBody?.dynamic = true // このオブジェクト自体が物理演算によって動くように指定
        self.addChild(shape)
    }
```

シェイプオブジェクトのphysicsBodyプロパティに、SKPhysicsBodyオブジェクトを設定するだけです。
また引数として当たり判定の範囲を指定します。ここでは図形と同じものを設定しています。
また、SKPhysicsBody.dynamicプロパティはそのオブジェクト自体が物理演算に沿って動くかどうかを設定します。falseでは周りのオブジェクトと衝突するだけで、オブジェクト自体は画面内に固定となります。
イメージとしてはマリオはdynamic = trueでブロックはdynamic=falseですね。


ついでにラベルもdynamic=falseで設定します。

```GameScene.swift
    func newHelloNode(Void) -> SKLabelNode {
    	/* 省略 */

        // 物理演算の追加
        helloNode.physicsBody = SKPhysicsBody(rectangleOfSize: helloNode.frame.size)
        helloNode.physicsBody?.dynamic = false
        return helloNode
    }
```

さて、これで物理演算の設定は終了です。
ただし、ビルドすると分かる通り、このままだと画面外にボールがこぼれてしまいます。
ボールを画面内に留めるために、画面の周囲に **エッジ** を追加しましょう。
エッジは幅を持たないオブジェクトで、境界線として機能します。

```GameScene.swift
    override func didMoveToView(view: SKView) {
        if ( !initiated ) { //初期化時のみ実行
            self.initContent()
            /* 追加 */
            self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
            /* /追加 */
            self.initiated = true
        }
    }
```
やり方はこれまでの物理演算引数にedegeLoopFromRectオプションを渡せばいいだけです。ここでは画面一杯にエッジを置きたいので、self.frameを渡しています。

これで、ボールが画面の枠で止まるようになったはずです。ビルドして確認してみます。


#### ビルド結果
タッチすると白いボールが描画されて、それが画面内に降り積もるようになりましたね！


![iOS Simulator Screen Shot 2015.01.29 20.39.40.png](https://qiita-image-store.s3.amazonaws.com/0/67687/38658e89-3e5c-cab9-c14e-876db91a5fd8.png "iOS Simulator Screen Shot 2015.01.29 20.39.40.png")


#まとめ
以上、SKViewをSingleViewApplicationから作ってみました。

もともとXcodeにはGameプロジェクトテンプレートが用意されているので、それを使えばいいかと思います。
一方で、Gameテンプレートは余計なものまで作成されるので、シンプルなものを作りたい場合や、アプリの一部にSpriteKitを使いたい場合は自分で組み込む事になるかと思います。





### 参考
[SpriteKit Programming Guid](https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/GettingStarted/GettingStarted.html#//apple_ref/doc/uid/TP40013043-CH2-SW1)