SpriteKitでクリスマスツリーをつくろう

株式会社スマートテック・ベンチャーズの意識最下層系の@omotesandoです。
さて12月といえば、リア充歓喜のイベントであるクリスマスがあります。クリスマスといえば、クリスマスツリーですね。
ということで、童心に戻りクリスマスツリーでも飾り付けしましょう。
どうぞよろしくお願いいたします。

#材料
- Xcodeさん（8.2にアップデートするのに3時間くらいかかりました。解せない。）
- Swift3さん（ころころ変わりたいお年頃だそうです。若いですね。）
- UIKitさん（iOSアプリを開発する上でなくてはならない方です。）
- SpriteKitさん（2Dゲームを作れるAppleのフレームワークです。接頭辞はSK。物理演算もできるってばよ。）
- イラスト素材（この記事のために自作しました。Photoshop様様。）

#それではやってみませう
##1. ツリーと飾りの準備
ツリーを表示するViewを用意します。
新規プロジェクトファイルを作るなら必ずお目にかかるこの画面ですが、ここで「Game」を選択すると、いかにもゲーム作れますよ的なプロジェクトが作れます。
**が！**今回はゲーム性のあるものを作るわけでもないので、「Single View Application」でいきましょう。（前にGameから作って訳わからん状態になったので）
![skcap01.png](https://qiita-image-store.s3.amazonaws.com/0/102673/1252fe8a-9603-1100-cf11-10d2d51631bd.png)

プロジェクトファイルが作れたら、使う画像を追加しておきます。ぽいっと。
あと、プロジェクトにSpriteKit.frameworkを追加してください。
![skcap14.png](https://qiita-image-store.s3.amazonaws.com/0/102673/490f7faa-e1fa-11fe-fe0c-98446f816527.png)


次に、新しいファイルを用意します。
```File``` > ```New``` > ```File```で現れるウィンドウを下へスクロールすると見つかる**SpriteKit Scene**さんを選択します。
![skcap03.png](https://qiita-image-store.s3.amazonaws.com/0/102673/b565c5b8-a850-61c3-fdfb-21ad5763f25c.png)
これは**シーン**というゲームの１場面？のようなものを作るファイルになります。拡張子は**.sks**です。
作ったファイルを開くと、こんな感じの画面になります。Storyboardのような画面に。
![skcap04.png](https://qiita-image-store.s3.amazonaws.com/0/102673/59906e0e-a4e8-8987-a576-a942388c77f1.png)
画面中央付近にある白枠の中が、画面での表示領域となります。

次に、**ノード**と呼ばれる、ゲームを構成する要素のようなものを作ります。
今回用意するノードは、背景ノード、ツリーノード、雪ノード、飾りノードをいくつか。
![skcap05.png](https://qiita-image-store.s3.amazonaws.com/0/102673/b1dfbc9a-d3f4-5df6-7367-1f656def1ea8.png)
右下にノードやらがずらずら用意されているので、１番上にある「Color Sprite」を画面内までドラッグ＆ドロップしましょう。StoryboardでUILabelなどを貼り付けるのと同じ感じです。
これでシーン上にノードが用意されました。デフォルトでは赤い四角かと思います。
ノードを選択していると、これまたStoryboardのようにウィンドウ右側に、設定項目が表示されます。「Name」でノードの名前を、「Texture」で、ノードに使う画像を設定します。
![skcap17.png](https://qiita-image-store.s3.amazonaws.com/0/102673/fe8e3750-ea7e-2e92-aedc-3f0ea8b9d2d1.png)
例えば、ノードのTextureに「bg.png」を設定すると、こんな感じになります。
![skcap07.png](https://qiita-image-store.s3.amazonaws.com/0/102673/f73be50d-46a3-3f02-7bc7-6d0b3f7daecd.png)
こんな感じで、ノードにテクスチャを適当に当てて、とりあえず準備完了。
![skcap10.png](https://qiita-image-store.s3.amazonaws.com/0/102673/36ee515b-f151-e4e3-364f-6464e3ae8483.png)
すでにクリスマスツリー感あります？絵が下手とかそういうのは気にしてはいけません。
ノードのサイズはあとで調整します。

##2. ドラッグイベントの実装
いよいよ実機orシミュレータさんで表示して、ノードを動かせるようにします。

さて、やはりGUI的なアレでは実現できないことも多々あるため、このsksファイルに対応するクラスを用意しましょう。
`File` > `New` > `File`で、今度は普通に「Cocoa Touch Class」でファイルを作ります。
![skcap11.png](https://qiita-image-store.s3.amazonaws.com/0/102673/c4d22861-7c22-1c08-7e50-d31cd821d487.png)
「Subclass...」に「SKScene」クラスを指定します。
作成したファイルを開いたら、SpriteKitをインポートします。

```swift:XmasScene.swift
import SpriteKit
```
で、クラス内で`didMove(to view: SKView)`メソッドさんを呼び出します。このメソッドは、シーンが呼び出された際に必ず呼ばれるものです。`viewDidLoad()`のようなものでしょうか。

```swift:XmasScene.swift
import SpriteKit

class XmasScene: SKScene {
    
    override func didMove(to view: SKView) {
        super.didMove(to: view)
        
    }

}
```
この時点では、XmasScene.sksとXmasSceneクラスはひも付いていません。
ということで次。
StoryboardさんにあるViewControllerさんのviewのクラスをSKViewに変えます。
![skcap15.png](https://qiita-image-store.s3.amazonaws.com/0/102673/77e85f95-2e1d-ad71-d150-2e0281c802c6.png)

ViewControllerクラスさんに、このシーンを表示してくれるようにお願いします。

```swift:ViewController.swift

import UIKit
//SpriteKitをインポート
import SpriteKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //XmasSceneクラスとXmasScene.sksをひも付けて取得
        let xmasScene = XmasScene(fileNamed: "XmasScene")
        
        //self.viewをSKViewにキャスト
        //StoryboardでviewのクラスをSKViewと指定しないと、キャストできず落ちます
        let xmasView = self.view as! SKView
        
        //シーンさんを表示します
        xmasView.presentScene(xmasScene)
        
}
```
これでビルドすると、とりあえずシーンさんが実機orシミュレータに表示されると思いますが、なんじゃこりゃあ。
![skcap19.png](https://qiita-image-store.s3.amazonaws.com/0/102673/112a6d6c-71c8-faec-dfa6-57b7c0713de4.png)

それではノードさんを動かせるようにしましょう。
XmasScene.swiftで、sksファイルで用意したノードを拾います。

```swift:XmasScene.swift
import SpriteKit

class XmasScene: SKScene {
    
    var bgNode:SKSpriteNode?    //背景
    var snowNode:SKSpriteNode?  //雪
    var treeNode:SKSpriteNode?  //ツリー
    var starNode:SKSpriteNode?  //★
    var ribonNode:SKSpriteNode? //リボン
    var bellNode:SKSpriteNode?  //ベル
    var obe1Node:SKSpriteNode?  //なんか丸いやつ（赤）
    var obe2Node:SKSpriteNode?  //なんか丸いやつ（水色）
    var obe3Node:SKSpriteNode?  //なんか丸いやつ（白）
    
    override func didMove(to view: SKView) {
        super.didMove(to: view)
    
        //シーンに配置したノードを取得
        //引数withNameには、sksファイルで各ノードの設定した「Name」を指定
        //sksファイルで作ったノードはすべてSKSpriteNode型で作っていますが、
        //childNode(withName:)でそのまま取得すると、SKNode型のため、キャストします
        bgNode = self.childNode(withName: "bg") as? SKSpriteNode
        snowNode = self.childNode(withName: "snow") as? SKSpriteNode
        treeNode = self.childNode(withName: "tree") as? SKSpriteNode
        starNode = self.childNode(withName: "star") as? SKSpriteNode
        ribonNode = self.childNode(withName: "ribon") as? SKSpriteNode
        bellNode = self.childNode(withName: "bell") as? SKSpriteNode
        obe1Node = self.childNode(withName: "obe1") as? SKSpriteNode
        obe2Node = self.childNode(withName: "obe2") as? SKSpriteNode
        obe3Node = self.childNode(withName: "obe3") as? SKSpriteNode
        
        //サイズをシーンに合わせる（今回は背景、雪、ツリーの画像は同一サイズ）
        bgNode?.size = self.size
        snowNode?.size = self.size
        treeNode?.size = self.size
        
    }

}
```
これでコード上でノードを自由に扱えます。
さて先ほどは無様な画面をお見せしてしまったので、諸々調整していきましょう。
背景、雪、ツリーノードさんは画像が同サイズで画面いっぱいに表示する予定なので、位置を表す`position`というプロパティをシーンと同じ値にしましょう。
これを指定すると、`anchorPoint`という、ノードを回転させたりする際の基準点となる座標を指定するプロパティが`position`の位置になります。

`position`

```swift:XmasScene.swift

        //サイズと表示位置をシーンに合わせる（今回は背景、雪、ツリーの画像は同一サイズ）
        bgNode?.size = self.size
        bgNode?.position = self.position
        
        snowNode?.size = self.size
        snowNode?.position = self.position
        
        treeNode?.size = self.size
        treeNode?.position = self.position

        //飾りノードの表示位置を左下に
        let xOrigin = self.frame.origin.x
        let yOrigin = self.frame.origin.y
        starNode?.position = CGPoint(x: xOrigin + (starNode!.size.width / 2), y: yOrigin + (starNode!.size.height / 2))
        ribonNode?.position = CGPoint(x: xOrigin + (ribonNode!.size.width / 2), y: yOrigin + (ribonNode!.size.height / 2))
        bellNode?.position = CGPoint(x: xOrigin + (bellNode!.size.width / 2), y: yOrigin + (bellNode!.size.height / 2))
        obe1Node?.position = CGPoint(x: xOrigin + (obe1Node!.size.width / 2), y: yOrigin + (obe1Node!.size.height / 2))
        obe2Node?.position = CGPoint(x: xOrigin + (obe2Node!.size.width / 2), y: yOrigin + (obe2Node!.size.height / 2))
        obe3Node?.position = CGPoint(x: xOrigin + (obe3Node!.size.width / 2), y: yOrigin + (obe3Node!.size.height / 2))
```
てな感じで設定した画面がこちらになります。
![skcap20.png](https://qiita-image-store.s3.amazonaws.com/0/102673/70420b44-d3b3-a64d-5dda-8d63bdcaffd4.png)

では、ノードをドラッグして、ツリーを飾れるようにしましょう！
ドラッグイベントを検知してくれるのは、おなじみのアレです。

```swift:XmasScene.swift

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        //ドラッグイベント
        
        for touch in touches{
            //タップした座標を取得
            let location = touch.location(in: self)
            //タップした座標にあるノード（配列）を取得
            let nodes = self.nodes(at: location) as? [SKSpriteNode]
            //指定した座標までの移動アクションです
            let moveAction = SKAction.move(to: location, duration: 0)
            
            //ノードにアクションを適応させます
            let node = nodes?[0]
            //背景、ツリー、雪ノード以外であれば
            if node != bgNode && node != treeNode && node != snowNode{
                node?.run(moveAction)
            }
            
        }
    }
```

最後に、シーンに対してノードが大きすぎるので、調整しましょう。
ノードには`size`プロパティがありますが、下手に動かすとアスペクト比が合わなくなるので、ここは`Xscale`、`Yscale`プロパティをいじることにしましょう。
![skcap12.png](https://qiita-image-store.s3.amazonaws.com/0/102673/6a4f884e-f0b4-ca69-5f2b-21b077d04c86.png)
ということで、ノードのサイズを調整した完成品がこちらになります。
![qiitaSKcap.gif](https://qiita-image-store.s3.amazonaws.com/0/102673/e6c0aade-e6dc-174b-428a-5d83273bfe28.gif)



#おわりに
作り終わって気付きましたが、SpriteKitらしさが皆無ですね。すみません。



##参考
[SpriteKit入門](http://spritekit.senchan-office.com/index.php/)
[はじはじアプリ体験記](http://hajihaji-lemon.com/smartphone/)