いまさらFlappyBirdを作ってみる②

前回主人公の鳥を画面に表示してタップで飛ぶところまで作りました。

今回はフィールド周りを作成していこうと思います。

流れる床を作る

まずはフィールドを動かすことが必要になってきます。

まずはコードを貼ってしまいます、コメントを多めに入れていますが、だいぶややこしいので分解して説明していきます。(※前回の鳥を表示するコードは読みにくくなるので省いています。)

    override func didMove(to view: SKView) {
     // SpriteKitの原点を 変更する
     self.anchorPoint = CGPoint(x:0, y:0)
   
     //基盤となるNode
        let moving = SKNode()
        self.addChild(moving)
        
     //検証でわかりやすいようにいったん鳥の画像を入れています(最終的に直します)
        let groundTexture = SKTexture(imageNamed: "bird-1")
        groundTexture.filteringMode = .nearest
        
        let twiceGroundTextureWidth = groundTexture.size().width * 2.0

        //背景をdurationの時間をかけて動かすよ
        let moveGroundSprite = SKAction.moveBy(x: -twiceGroundTextureWidth, y: 0, duration: TimeInterval(0.02 * twiceGroundTextureWidth))
        //アニメーションで動いた分位置を戻すdurationが0なので一瞬
        let resetGroundSprite = SKAction.moveBy(x: twiceGroundTextureWidth, y: 0, duration: 0.0)
        let moveGroundSpritesForever = SKAction.repeatForever(SKAction.sequence([moveGroundSprite,resetGroundSprite]))
        
        let sprite = SKSpriteNode(texture: groundTexture)
        sprite.position = CGPoint(x: 100, y: 100)

        let deviceFrameWidth = self.frame.size.width
        
        //端末の横幅と画像の横幅で計算をして動く道を敷き詰める
        for i in 0 ..< 2 + Int(deviceFrameWidth / twiceGroundTextureWidth) {
            let i = CGFloat(i)
            let sprite = SKSpriteNode(texture: groundTexture)
            sprite.setScale(2.0)
            sprite.position = CGPoint(x: i * sprite.size.width, y: sprite.size.height / 2.0)
            sprite.run(moveGroundSpritesForever)
            moving.addChild(sprite)
        }
    }

SpriteKitの原点

SpriteKitの原点は昔は以下の画像のように左下が(X:0, Y:0) の形だったんですが、いつからか画面の真ん中が原点になってしまったので、昔からSpriteKitで開発をしている開発者には恐ろしく使いにくくなりました。

UIViewの座標とSKNodeの座標の違い

そのため以下のコードで原点を左下に変更して昔のSpriteKitのように使えるようにしています。

self.anchorPoint = CGPoint(x:0, y:0)

フィールドの基盤について

まず前提として、SpriteKitのオブジェクトは以下のような関係図を持っています。

SKNode Class

背景を作るために、FlappyBirdでは、「床」、「土管」、「背景のビル」この3つを組み合わせる必要があるのですが、一括管理をしておくと楽なので、moveというNodeを作成してそこに3つの要素を追加していきます。

let moving = SKNode()
self.addChild(moving)

アニメーション

アニメーションでは2つの動きをくっつけています。

let twiceGroundTextureWidth = groundTexture.size().width * 2.0
//背景をdurationの時間をかけて動かす
let moveGroundSprite = SKAction.moveBy(x: -twiceGroundTextureWidth, y: 0, duration: TimeInterval(0.02 * twiceGroundTextureWidth))
//アニメーションで動いた分位置を戻すdurationが0なので一瞬
let resetGroundSprite = SKAction.moveBy(x: twiceGroundTextureWidth, y: 0, duration: 0.0)
let moveGroundSpritesForever = SKAction.repeatForever(SKAction.sequence([moveGroundSprite,resetGroundSprite]))

moveGroundSpriteでは、TimeInterval(0.02 * twiceGroundTextureWidth)秒をかけて床を左に移動させます。

そして、resetGroundSpriteで0秒で元の位置に戻す!それをmoveGroundSpritesForeverでずっと繰り返して床が動くアニメーションを作成しています。

床を敷き詰める

ここが今回の山場です!

正直いきなり全部理解するのは難しいと思いますが、噛み砕いて小く考えれば理解できるはず!

let twiceGroundTextureWidth = groundTexture.size().width * 2.0
let deviceFrameWidth = self.frame.size.width
        
//端末の横幅と画像の横幅で計算をして動く道を敷き詰める
for i in 0 ..< 2 + Int(deviceFrameWidth / twiceGroundTextureWidth) {
    let i = CGFloat(i)
    let sprite = SKSpriteNode(texture: groundTexture)
    sprite.setScale(2.0)
    sprite.position = CGPoint(x: i * sprite.size.width, y: sprite.size.height / 2.0)
    sprite.run(moveGroundSpritesForever)
    moving.addChild(sprite)
}

まずコードの元に定義してある2つの変数ですが、以下のような関係性になっています。

forで回すとiの値が変化していくので、i * sprite.size.widthで床Spriteのx座標の位置をずらして画面に敷き詰めています。

実機で動かしてみたのがこちらです。本物の床でやると切り目がわからず繰り返されているのかわかりにくいので鳥に画像を置き換えています。

背景のビル追加

背景のビルに関しては床で解説したこととほぼ同じことをしています。

一部計算が床の横幅を計算に使ったり、アニメーションが違いますが、流れる床の理解確認で何をしているかコードを追ってみてください。わからないことがあれば1つ前の章を読みかしてみてください。

override func didMove(to view: SKView) {
        self.anchorPoint = CGPoint(x:0, y:0)
        
        let moving = SKNode()
        self.addChild(moving)
        
        let groundTexture = SKTexture(imageNamed: "land")
     groundTexture.filteringMode = .nearest

        let twiceGroundTextureWidth = groundTexture.size().width * 2.0

...省略〜

        let skyTexture = SKTexture(imageNamed: "sky")
        skyTexture.filteringMode = .nearest
        
        let twiceSkyTextureWidth = skyTexture.size().width * 2.0
        
        let moveSkySprite = SKAction.moveBy(x: -twiceSkyTextureWidth, y: 0, duration: TimeInterval(0.1 * twiceSkyTextureWidth))
        let resetSkySprite = SKAction.moveBy(x: twiceSkyTextureWidth, y: 0, duration: 0.0)
        let moveSkySpritesForever = SKAction.repeatForever(SKAction.sequence([moveSkySprite,resetSkySprite]))
        
        for i in 0 ..< 2 + Int(deviceFrameWidth / twiceSkyTextureWidth) {
            let i = CGFloat(i)
            let sprite = SKSpriteNode(texture: skyTexture)
            sprite.setScale(2.0)
            sprite.zPosition = -20
            sprite.position = CGPoint(x: i * sprite.size.width, y: sprite.size.height / 2.0 + groundTexture.size().height * 2.0)
            sprite.run(moveSkySpritesForever)
            moving.addChild(sprite)
        }
    }

床の画像を正しいものに置き換えてビルドすると以下のような画像になっていれば成功です。アニメーションもしているはず!

土管の配置はちょっと複雑なので今回はここまで!

その3に続く: いまさらFlappyBirdを作ってみる③