Swiftで反射神経を鍛えるゲームを作ってみた

#はじめに
はじめまして。初投稿です。
趣味でプログラミングしてて、友人にどうせだったらQiitaとかGithubに公開すればいいのにと言われ、確かにその通りだと思ったのでこれから随時投稿していこうと思います。
よろしくお願いします。

#概要
Swiftで反射神経を鍛えるゲームを作りました。Swiftで初めてちゃんと最後まで作ったアプリなので、結構思い入れがあります。1から10まで小さい数から順番にタップしていくシンプルなゲームです。
プレイ画面はこんな感じ。↓
![game2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/623917/62d6e73a-d716-6f52-67f6-cd9a33702a3e.gif)

#開発環境
・ iOS 13.3.1
・ Xcode 11.3.1

#ソースコード
簡単にソースコードの説明をします。
全部載せるととても長くなってしまうので、スコアシーンなどは割愛します。
全てのコードは[Github](https://github.com/YumaSato-ubi/QuickPush)に置いておきます。

##TitleScene
タイトル画面で表示されます。
背景に表示される数字の傾きや大きさが毎回ランダムに変わるというちょっとした遊び心（？）も入れてます。

```swift:TitleScene.swift
import SpriteKit
import GameplayKit

var i = 1

class TitleScene: SKScene {
    
    let Start = SKLabelNode(fontNamed: "Chalkboard SE")

    
    override func didMove(to view: SKView) {
        self.backgroundColor = .white
        let QuickTap = SKLabelNode(fontNamed: "Chalkboard SE")
        QuickTap.text = "Quick Tap!"
        QuickTap.fontColor = .red
        QuickTap.fontSize = 110
        QuickTap.position = CGPoint(x: self.frame.midX, y:(self.frame.midY) + 150 )
        self.addChild(QuickTap)

        for i in 1...10 {
        let num = SKLabelNode(fontNamed: "Chalkboard SE")
        let angle:CGFloat = CGFloat(Int.random(in : 0...359))
            
        num.text = "\(i)"
        num.fontColor = .gray
        num.fontSize = CGFloat(Int.random(in : 100...500))
        num.position = CGPoint(x: Int.random(in: -100...600), y: Int.random(in: -200...800))
        num.zRotation = angle
        num.alpha = 0.2
        self.addChild(num)
        }
        
        Start.text = "Start"
        Start.fontColor = .black
        Start.fontSize = 100
        Start.position = CGPoint(x: self.frame.midX, y:(self.frame.midY) - 250 )
        self.addChild(Start)
    }

    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    
        for obj in touches {
                let touch = obj
                let location = touch.location(in: self)
                let node = self.atPoint(location)
                if node == self.Start {
                    let secondScene = GameScene(size: self.size)
                    secondScene.scaleMode = SKSceneScaleMode.aspectFill
                    self.view?.presentScene(secondScene)
            }
        }

    }
    override func update(_ currentTime: CFTimeInterval){}
}
```

##GameScene
ゲームシーンって書いてますが、ただのカウントダウンが表示される場面です。
ここのカウントダウンと効果音を一緒に出すやり方はもっと効率のいい方法があると思います。無理やり効果音を加工しました。

```swift:GameScene.swift
import SpriteKit
import GameplayKit


class GameScene: SKScene {

    override func didMove(to view: SKView) {
        let countdownSound = SKAudioNode(fileNamed: "countdown.mp3")
        self.backgroundColor = .white
        let count = SKLabelNode(fontNamed: "Gill Sans")
        count.fontColor = .systemRed
        count.fontSize = 200
        count.position = CGPoint(x : self.frame.midX, y : self.frame.midY)
        self.addChild(count)

        let inter = SKAction.wait(forDuration: 1.0)
        let hidden = SKAction.run ({
            count.isHidden = true
        })
        let three = SKAction.run ({
            count.text = "3"
        })
        let two = SKAction.run ({
            count.text = "2"
        })
        let one = SKAction.run ({
            count.text = "1"
        })
        
        self.run(SKAction.sequence([three,inter,two,inter,one,inter,hidden]))
        self.addChild(countdownSound)
        _ = Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.NextScene), userInfo: nil, repeats: false)
    }
  
    @objc func NextScene(){
        let thirdScene = GamePlayScene(size: self.size)
        thirdScene.scaleMode = SKSceneScaleMode.aspectFill
        self.view?.presentScene(thirdScene)
    }

    func touchMoved(toPoint pos : CGPoint) {
    }

    func touchUp(atPoint pos : CGPoint) {
    }

    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
    }

}
```

##GamePlayScene
実際に1から順に消していく画面です。
いらないコードを途中で消したり、必要なコードを途中で足したりしたので、かなり歪な書き方になってます、、、本当はもっと綺麗な書き方あると思います。。
数字の配置と大きさ、色はランダムになるようになってます。

```swift:GamePlayScene
import SpriteKit
import GameplayKit
import AVFoundation

    
    var timeTotal: Double = 0
    var timer1: Timer?
    let StartSound = SKAudioNode(fileNamed: "pi.mp3")

class GamePlayScene: SKScene {
    var timer: Timer?
    
    var array1:Set = [100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600]
    var array2:Set = [100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000]

    var TrueInstanse: AVAudioPlayer! = nil
    var FalseInstanse: AVAudioPlayer! = nil

    @objc func handleTimer(_ timer: Timer) {
        timeTotal += timer.timeInterval
        print("timer fired. total : \(timeTotal)")
    }
    @objc func StopStart() {
        let stopAction = SKAction.stop()
        StartSound.run(stopAction)
    }
    

 override func didMove(to view: SKView) {
    timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(handleTimer), userInfo: nil, repeats: true)
        let TrueSoundPath = Bundle.main.path(forResource: "true", ofType: "mp3")!
        let FalseSoundPath = Bundle.main.path(forResource: "false", ofType: "mp3")!
        let TrueSound = URL(fileURLWithPath: TrueSoundPath)
        let FalseSound = URL(fileURLWithPath: FalseSoundPath)
    do {
        TrueInstanse = try AVAudioPlayer(contentsOf: TrueSound, fileTypeHint: nil)
    } catch {
        print("TrueSound is error.")
    }
    do {
        FalseInstanse = try AVAudioPlayer(contentsOf: FalseSound, fileTypeHint: nil)
    } catch {
        print("FalseSound is error.")
    }
    TrueInstanse.prepareToPlay()
    FalseInstanse.prepareToPlay()
    
        self.addChild(StartSound)
        self.backgroundColor = .white
    timer1 = Timer.scheduledTimer(timeInterval: 1.8, target: self, selector: #selector(self.StopStart), userInfo: nil, repeats: false)
        
        for i in 0...9{
        let randomSize = Int.random(in : 100...300)
        let randomSizeDouble1 = array1.randomElement()
        let randomSizeDouble2 = array2.randomElement()
        array1.remove(randomSizeDouble1!)
        array2.remove(randomSizeDouble2!)
        let num = SKLabelNode(fontNamed: "Chalkboard SE")
        num.position = CGPoint(x: randomSizeDouble1!, y: randomSizeDouble2!)
        num.fontSize = CGFloat(randomSize)
        num.text = "\(10 - i)"
            
        switch(Int.random(in : 0...9)) {
        case 0:
            num.fontColor = .systemPink
        case 1:
            num.fontColor = .systemYellow
        case 2:
            num.fontColor = .systemGreen
        case 3:
            num.fontColor = .systemBlue
        case 4:
            num.fontColor = .systemGray
        case 5:
            num.fontColor = .systemTeal
        case 6:
            num.fontColor = .systemIndigo
        case 7:
            num.fontColor = .systemPurple
        case 8:
            num.fontColor = .systemRed
        default:
            num.fontColor = .systemOrange
        }
            
        self.addChild(num)
        }
    }
    
    var TenArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 888]
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for obj in touches {
                let touch = obj
                let location = touch.location(in: self)
                let node = self.atPoint(location)
            if let label = node as? SKLabelNode {
                if label.text == "\(TenArray.min()!)"{
                    TrueInstanse.currentTime = 0
                    TrueInstanse.play()
                    label.removeFromParent()
                    TenArray.removeFirst()
                    if TenArray[0] == 888 {
                        timer!.invalidate()
                        let scoreScene = ScoreScene(size: self.size)
                        scoreScene.scaleMode = SKSceneScaleMode.aspectFill
                        self.view?.presentScene(scoreScene)
                        
                    }
                } else if label.text != "\(TenArray.min()!)"{
                    FalseInstanse.currentTime = 0
                    FalseInstanse.play()
                } else {
                    continue
                }
                
            }
        
        }

    }

    func touchMoved(toPoint pos : CGPoint) {
    }

    func touchUp(atPoint pos : CGPoint) {
    }

    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
    }

}

```

##その他
各シーンごとに同名のsksファイルを一緒に作りました。あと、main.storyboardには今回、何も手を加えてません。全部コードで書きました。

#最後に
ここまでご覧いただきありがとうございました。ちなみに　このゲームの私の最高記録は1.8sです（自分でもなかなかすごいと思ってます）。私の中でアプリといえば真っ先に思いつくのがゲームなんですよね。実際、ゲーム作るのすごく楽しいですし。まあこれからはいろんなアプリを勉強しながら作っていきたいと思ってます。