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

More than 1 year has passed since last update.

株式会社スマートテック・ベンチャーズの意識最下層系の@omotesandoです。

さて12月といえば、リア充歓喜のイベントであるクリスマスがあります。クリスマスといえば、クリスマスツリーですね。

ということで、童心に戻りクリスマスツリーでも飾り付けしましょう。

どうぞよろしくお願いいたします。


材料


  • Xcodeさん(8.2にアップデートするのに3時間くらいかかりました。解せない。)

  • Swift3さん(ころころ変わりたいお年頃だそうです。若いですね。)

  • UIKitさん(iOSアプリを開発する上でなくてはならない方です。)

  • SpriteKitさん(2Dゲームを作れるAppleのフレームワークです。接頭辞はSK。物理演算もできるってばよ。)

  • イラスト素材(この記事のために自作しました。Photoshop様様。)


それではやってみませう


1. ツリーと飾りの準備

ツリーを表示するViewを用意します。

新規プロジェクトファイルを作るなら必ずお目にかかるこの画面ですが、ここで「Game」を選択すると、いかにもゲーム作れますよ的なプロジェクトが作れます。

が!今回はゲーム性のあるものを作るわけでもないので、「Single View Application」でいきましょう。(前にGameから作って訳わからん状態になったので)

skcap01.png

プロジェクトファイルが作れたら、使う画像を追加しておきます。ぽいっと。

あと、プロジェクトにSpriteKit.frameworkを追加してください。

skcap14.png

次に、新しいファイルを用意します。

File > New > Fileで現れるウィンドウを下へスクロールすると見つかるSpriteKit Sceneさんを選択します。

skcap03.png

これはシーンというゲームの1場面?のようなものを作るファイルになります。拡張子は.sksです。

作ったファイルを開くと、こんな感じの画面になります。Storyboardのような画面に。

skcap04.png

画面中央付近にある白枠の中が、画面での表示領域となります。

次に、ノードと呼ばれる、ゲームを構成する要素のようなものを作ります。

今回用意するノードは、背景ノード、ツリーノード、雪ノード、飾りノードをいくつか。

skcap05.png

右下にノードやらがずらずら用意されているので、1番上にある「Color Sprite」を画面内までドラッグ&ドロップしましょう。StoryboardでUILabelなどを貼り付けるのと同じ感じです。

これでシーン上にノードが用意されました。デフォルトでは赤い四角かと思います。

ノードを選択していると、これまたStoryboardのようにウィンドウ右側に、設定項目が表示されます。「Name」でノードの名前を、「Texture」で、ノードに使う画像を設定します。

skcap17.png

例えば、ノードのTextureに「bg.png」を設定すると、こんな感じになります。

skcap07.png

こんな感じで、ノードにテクスチャを適当に当てて、とりあえず準備完了。

skcap10.png

すでにクリスマスツリー感あります?絵が下手とかそういうのは気にしてはいけません。

ノードのサイズはあとで調整します。


2. ドラッグイベントの実装

いよいよ実機orシミュレータさんで表示して、ノードを動かせるようにします。

さて、やはりGUI的なアレでは実現できないことも多々あるため、このsksファイルに対応するクラスを用意しましょう。

File > New > Fileで、今度は普通に「Cocoa Touch Class」でファイルを作ります。

skcap11.png

「Subclass...」に「SKScene」クラスを指定します。

作成したファイルを開いたら、SpriteKitをインポートします。


XmasScene.swift

import SpriteKit


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


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

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


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

それではノードさんを動かせるようにしましょう。

XmasScene.swiftで、sksファイルで用意したノードを拾います。


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


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

では、ノードをドラッグして、ツリーを飾れるようにしましょう!

ドラッグイベントを検知してくれるのは、おなじみのアレです。


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プロパティがありますが、下手に動かすとアスペクト比が合わなくなるので、ここはXscaleYscaleプロパティをいじることにしましょう。

skcap12.png

ということで、ノードのサイズを調整した完成品がこちらになります。

qiitaSKcap.gif


おわりに

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


参考

SpriteKit入門

はじはじアプリ体験記