今更ながら SpriteKit を触ってみたのでメモ。
とりあえずの習作として選んだのは 15パズル。
後でコードを見て「ん?」ってなりそうな部分についての覚書。
- Flip(裏表がクルっと回転するアニメーション)
- Array2D(2次元配列)
- 15パズルの初期配置
- SpriteKit と MVC
ソースコードはここから github: puzzle15
パズルが完成したら左上から順番に駒をひっくり返して別の絵が表示されるようにしてある。ここで駒をひっくり返す部分に Flipアニメーションを使用している。
Flipアニメーションは SpriteKit に標準で用意されているアニメーションではないので既存のものを組み合わせる必要がある。
以下、その手順
・SKAktion.scaleX(to: 0, duration:) でスプライトの幅を 0にするアニメーションを起動・スプライトのテクスチャを裏面のテクスチャへ変更
・SKAnimation.scaleX(to: 1, duration:) でスプライトの幅を 1にするアニメーションを起動
これをコードにするとこんな感じ。
let faceHalfFlip = SKAction.scaleX(to: 0.0, duration: 0.5)
let backHalfFlip = SKAction.scaleX(to: 1.0, duration: 0.5)
sprite.run(faceHalfFlip, completion: {
sprite.texture = backTexture
sprite.run(backHalfFlip)
})
実際のコードでは wait を入れたりして使用している。
15パズルは 4×4 の駒をスライドさせて遊ぶパズルなので、なんとなく2次元配列があると良いかな?と思って作ったクラス。
名前は 2次元配列そのまんま。(この時 matrix は思い浮かばなかった…)
結局、subscribe を 1次元、2次元の両方用意してどちらでもアクセス可能にしたり、Sequence Protocol で for in で使えるようにしたり、正直「2次元配列で作る必要なかったな」とは思う。
func makeIterator() -> AnyIterator<T?> {
var index = 0
return AnyIterator {
defer { index += 1 }
guard index < self.array.count else { return nil }
return self.array[index]
}
}
makeIterator() の部分はオリジナルの Array の Iterator を返すとかできそうだけど…
当初は正解の状態から 15番目の駒を外して、ここから 5000回ほどランダムに駒をタップして問題としていた。(「駒をタップ」は動かせない駒をタップすることもあるので、実際に駒は 5000回動いてない)
private func shuffleByMove() {
for _ in 0..<5000 {
let x = Int(arc4random_uniform(4))
let y = Int(arc4random_uniform(4))
if canMoveFrom(colmn: x, row: y) {
let _ = moveFrom(colmn: x, row: y)
}
}
}
けれど、実際に駒が動く様子が見られるならいざ知らず、そうでないのにここで 5000回もループするのはどんなもんか?と思い、ランダムに駒を配置する方法に変更した。
が、全くランダムに配置すると絶対に解けない配置になることがあるので、ランダムに配置した後でそれが解ける配置か解けない配置かを確認する必要がある。
それが canSolve() 関数。
private func canSolve(_ gameBoard: Array2D<PuzzlePiece>) -> Bool {
var count = 0
var table = gameBoard.map {$0?.number}
for (i, number) in table.enumerated() {
if i == 15 { break }
if i == number { continue }
for j in i+1...15 {
if table[j] == i {
table[j] = table[i]
table[i] = i
count += 1
break
}
}
}
return count % 2 == 0 ? true : false
}
以下、簡単に説明
1. 最初に添字 0-14までの 15マスにランダムに配置された駒の番号の table を作成。2. その table の添字 X番目に同じ番号が入っているかどうかを確認
3. 違っていたらそれより大きな添字の中からX番目に入るべき番号が入っているものを探す。
4. 見つかったらそれと X番目を交換して、count を1つ増やす。
5. こうして 2-4 を繰り返して table が [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] になったら終了。
6. この時点で count が偶数ならパズルは解ける、奇数なら解けない。
詳細は以下のページに詳しく解説されている。
最後に SpriteKit で MVC を意識すると ViewController と SKScene はどういう関係になるのだろう?という疑問。
ViewController がいらなくなるというか、SKScene(SKView) が VC になってる気がするんだけど。
今回わざわざ PuzzleGame と GameScene の間に ViewController 入れてメッセージ送っているけど ViewController ない方がいい感じになりそう。正直 ViewController は Start ボタンのタップを受け取ってるだけだし。
ただ、規模が大きくなるとどんどん SKScene が肥大化していってこれまた大変になりそう…
もう少しゲームっぽいものを作ってみようということで、最近ちょっと縁のあったインベーダーゲームを作ってみる予定。