【Swift】iOSで放置型育成ゲームを作るよ(4) ~BGMなど効果音・環境音関係を管理するクラスを作る~

## 今回のゴール
- なんとなく寂しいのでBGMと効果音を鳴らすことができるようにする
- BGMを管理するクラスはシングルトンで実装してみる

## 今回のキーワード
- シングルトン
- 音声ファイル

## まず初めに
前回やった修正をdevelopmentにマージしようとしたらなんかコンフリクトしてて、プロジェクトファイルは読み込めないしやべえよやべえよ状態だったけど、ファイルの差分見てたらプロジェクトファイル自体がコンフリクトしててなんとかvimで編集してあれこれして事なきを得る。(gitのコミットログがものすごい汚いことになってしまった・・・)
さてそんなことは置いておいて今日のゴールを目指してガシガシコード書くよ

## やったこと
### まずはBGMをプロジェクトに追加するよ
- 今回はBGMと何かをタップした時の音を実装するよ
- クローバーラボさんのありがたいご厚意によりBGMも素材として提供されているよ
- 何かを選択した時の効果音は無料音源を探すのに最適な「[魔王魂](http://maoudamashii.jokersounds.com/)」さんから拝借します(ありがたや:pray:)
- 「hometown.m4a」、「select.mp3」の2つのファイルをプロジェクトに追加するよ(ma4とmp3なのは特に意味はないよ)
- ついでにいらなくなったcharacter.gifを消して、SupportingFilesを作成、ImageとSoundにディレクトリを分けて整理するよ
<img width="1277" alt="スクリーンショット 2017-07-01 18.24.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/115590/c7524e7d-d9b7-02fb-a057-654230d1d284.png">

### とりあえず音を流してみるよ
- 起動時にホームタウン.m4a(分かりやすいようにhometown.m4aにリネームしたよ)が流れるように実装してみるよ
- 検索ワード「Swift BGM」
- いくつか良さげな記事があったけど、この記事を参考にさせてもらうよ([音楽再生する簡単な方法](http://qiita.com/yuutetu/items/c0f0c28bcec37cbda07f))

```ViewContoller.swift
import AVAudioPlayer
~~~省略~~~
override func viewDidLoad() {
~~~省略~~~
        do {
            // 音楽ファイルが"howntown.m4a"の場合
            let filePath = Bundle.main.path(forResource: "hometown", ofType: "m4a")
            let audioPath = NSURL(fileURLWithPath: filePath!)
            player = try AVAudioPlayer(contentsOf: audioPath as URL)
            player.prepareToPlay()
        } catch {
            print("Error")
        }
        
        player.play()
    }
```
とりあえず鳴ったー！ (qiitaにはmovがアップデートできないみたいなので実際に音が出てるかが分からないけど・・)

### サウンドを管理するデザインパターン
- サウンドを管理するインスタンスはプロジェクトの中で1つのみでいいのでシングルトンのデザインパターンが使えないかと考えたよ
- こんな感じ
    - SoundMangerのsharedInstanceは複数のAVAudioPlayerをプロパティとして持つ
    - 利用する側(ViewControllerなど)ではサウンドのタイプを引数に各AVAudioPlayerの再生や停止の処理を呼び出す
- とりあえず色んな記事を参考にしつつ、SoundManagerを作成するよ
    - [【Swift】シングルトンクラス作成【Singleton】](http://tefunamu.com/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/%E3%80%90swift3%E3%80%91swift%E3%81%A7%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%88%E3%83%B3%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9/)
    - [swift3でシングルトンクラスを作ってみる](http://haguhoms.hateblo.jp/entry/2017/02/04/010343)
    - [Swiftで簡単なシングルトンの実装方法](http://qiita.com/codelynx/items/a936afe0a45d4cf5abfb)

```SoundManger.swift
import Foundation
import AVFoundation

final class SoundManager {
    
    enum type: String {
        case BGM
        case Select
        
        func path() -> URL {
            switch self {
            case .BGM:
                return URL(fileURLWithPath: Bundle.main.path(forResource: "hometown", ofType: "m4a")!)
            case .Select:
                return URL(fileURLWithPath: Bundle.main.path(forResource: "select", ofType: "mp3")!)
            }
        }
    }
    
    static let shared = {
        return SoundManager()
    }()
    
    private init() {
        do {
            try self.bgmPlayer = AVAudioPlayer(contentsOf: type.BGM.path())
            try self.selectPlayer = AVAudioPlayer(contentsOf: type.Select.path())
        } catch {
            print("Failed To Initialize SoundPlayer")
            self.bgmPlayer = AVAudioPlayer()
            self.selectPlayer = AVAudioPlayer()
        }
        
        self.setupPlayer()
    }
    
    private var bgmPlayer: AVAudioPlayer!
    private var selectPlayer: AVAudioPlayer!
    
    private func setupPlayer() {
        bgmPlayer.numberOfLoops = -1
        
        // 起動してすぐにBGMが鳴り出して違和感があるので一旦prepareはしないでおく
        // bgmPlayer.prepareToPlay()
        selectPlayer.prepareToPlay()
    }
    
    func play(_ type: SoundManager.type) {
        switch type {
        case .BGM:
            bgmPlayer.play()
        case .Select:
            selectPlayer.play()
        }
    }
    
    func pause(_ type: SoundManager.type) {
        switch type {
        case .BGM:
            bgmPlayer.pause()
        case .Select:
            selectPlayer.pause()
        }
    }
    
    func stop(_ type: SoundManager.type) {
        switch type {
        case .BGM:
            bgmPlayer.stop()
        case .Select:
            selectPlayer.stop()
        }
    }
    
    func stopAll() {
        bgmPlayer.stop()
        selectPlayer.stop()
    }
}
```
こんな感じかしら・・・　AVAudioPlayerをtrycatchで囲わなきゃいけないので、error時にとりあえずインスタンス作ってプロパティに代入してるけどなんかものすごい気持ち悪い実装な気がする
まぁとりあえず動くのでこれでいいか！(毎度のこと)

```ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // キャラクターのインスタンスを生成してViewControllerのプロパティに設定
        valkyrie = Character(name: "【戦乙女】ヴァルキリー", charaImage: UIImage(named: "sozai.png"))
        
        // imageViewのimageにキャラのimageを設定
        imageView.image = valkyrie?.image
        name.text = valkyrie?.name
        name.adjustsFontSizeToFitWidth = true
        
        strLabel.text = "攻撃力: \(String(describing: valkyrie!.status.str))"
        defLabel.text = "防御力: \(String(describing: valkyrie!.status.def))"
        lucLabel.text = "天運: \(String(describing: valkyrie!.status.luc))"

        SoundManager.shared.play(.BGM)
    }
```

使う側はSoundManger.sharedで再生したいBGMのTypeを指定してあげればよいので、これはこれで使いやすい気もする

## 結論
- BGM大事😂
- そろそろゲームの根幹のシステムを考えないと・・！
- 今日の作業ブランチはこちら
    - https://github.com/nasteng/Dejikore/tree/update-bgm

### 次回
[アプリの起動画面の設定とゲームのシステムについて考えるよ](http://qiita.com/nasteng/items/dd8d58469eb0609986d6)

### 最後に
ご指摘・リファクタ大歓迎です:pray: