今回のゴール
- なんとなく寂しいのでBGMと効果音を鳴らすことができるようにする
- BGMを管理するクラスはシングルトンで実装してみる
今回のキーワード
- シングルトン
- 音声ファイル
まず初めに
前回やった修正をdevelopmentにマージしようとしたらなんかコンフリクトしてて、プロジェクトファイルは読み込めないしやべえよやべえよ状態だったけど、ファイルの差分見てたらプロジェクトファイル自体がコンフリクトしててなんとかvimで編集してあれこれして事なきを得る。(gitのコミットログがものすごい汚いことになってしまった・・・)
さてそんなことは置いておいて今日のゴールを目指してガシガシコード書くよ
やったこと
まずはBGMをプロジェクトに追加するよ
- 今回はBGMと何かをタップした時の音を実装するよ
- クローバーラボさんのありがたいご厚意によりBGMも素材として提供されているよ
- 何かを選択した時の効果音は無料音源を探すのに最適な「魔王魂」さんから拝借します(ありがたや)
- 「hometown.m4a」、「select.mp3」の2つのファイルをプロジェクトに追加するよ(ma4とmp3なのは特に意味はないよ)
- ついでにいらなくなったcharacter.gifを消して、SupportingFilesを作成、ImageとSoundにディレクトリを分けて整理するよ
とりあえず音を流してみるよ
- 起動時にホームタウン.m4a(分かりやすいようにhometown.m4aにリネームしたよ)が流れるように実装してみるよ
- 検索ワード「Swift BGM」
- いくつか良さげな記事があったけど、この記事を参考にさせてもらうよ(音楽再生する簡単な方法)
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を作成するよ
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大事😂
- そろそろゲームの根幹のシステムを考えないと・・!
- 今日の作業ブランチはこちら
次回
最後に
ご指摘・リファクタ大歓迎です