LoginSignup
1
3

More than 3 years have passed since last update.

【入門】iOS アプリ開発 #3【Sound を再生する】

Last updated at Posted at 2020-08-11

Sound

今回はゲーム中のサウンドを流す処理を作成したい。SpriteKit は簡単なサウンド制御も提供している。「パックマン」程度であれば、この範囲で実現することができそうだ。

サウンドは効果音(Sound Effect) と BGM(BackGround Music) がある。この2種類を実現するクラスを作成する。

Sound Manager class の作成

効果音を再生する API は、

playSE(.EatDot)

とする。

BGMを再生する API は、

playBGM(.BgmPower)

とする。

このような API を持つ、次の CgSoundManager クラスを作成する。

CgSoundManager class

/// Sound management class plays sound with SpriteKit.
class CgSoundManager {

    // Kind of sound items to play back.
    enum EnKindOfSound: Int {
        case EatDot = 0
        case EatFruit
        // ・・・略・・・
        case Intermission
    }

    // List of sound files to load.
    private let table_urls: [[(resourceName: String, typeName: String, interval: Int)]] = [
        [ ("16_pacman_eatdot_256ms", "wav", 256) ],
        [ ("16_pacman_eatfruit_438ms", "wav", 438) ],
        // ・・・略・・・
        [ ("16_pacman_intermission_5200ms", "wav", 5200) ]
    ]
    private var view: SKScene?
    private var actions: [SKAction] = []
    private var table_playingTime: [Int] = []

    // Adjustment time for processing to play sound.
    private let triggerThresholdTime: Int = 16 //ms

    /// Create and initialize a sound manager object.
    /// - Parameters:
    ///   - view: SKScene object that organizes all of the active SpriteKit content.
    init(view: SKScene) {
        self.view = view
        table_playingTime = Array<Int>(repeating: 0, count: table_urls.count)

        for t in table_urls {
            appendSoundResource(resourceName: t[0].resourceName, typeName: t[0].typeName)
        }
        // ・・・略・・・
    }

    /// Append sound resources to SpriteKit.
    /// - Parameters:
    ///   - resourceName: File name for sound resource.
    ///   - typeName: Type name for sound resource.
    private func appendSoundResource(resourceName: String, typeName: String) {
        let fileName = resourceName+"."+typeName
        let sound: SKAction = SKAction.playSoundFileNamed(fileName, waitForCompletion: false)
        actions.append(sound)
    }

    /// Play back a specified sound.
    /// If the specified item is playing back, it will not be played back.
    /// - Parameter number: Kind of sound items to play back.
    func playSE(_ number: EnKindOfSound) {
        guard soundEnabled && number.rawValue < actions.count else { return }

        let _number = number.rawValue
        if table_playingTime[_number] <= triggerThresholdTime {
            let table = table_urls[_number]
            table_playingTime[_number] = table[0].interval
            view?.run(actions[_number])
        }
    }

enum で定義された値と、再生するサウンド・ファイルの table_urls テーブルを対応させておく。

クラス初期化時に、これらのファイルを SKActionのオブジェクトとして生成しておく。

またサウンド・ファイルの再生時間を値として管理しておく。これは同じ効果音が複数重ならないようにするためで、ある効果音が再生中に同じものを再生する場合は再生しないようにする。

また BGM を再生するときには、指定したサウンド・ファイルの再生が終了したことを知り、同じものを繰り返し再生するために使用する。自動で開始するためのトリガーは、updateメソッド内で行う。

サウンド・ファイル

今回のパックマンに使うサウンド・ファイルは 15個で、フォーマットは WAV形式、16bit、モノラル、サンプリング・レート 22050Hz で作成した。

8bit だと、少し音がこもる感じになるので、16bit にして処理の重さを気にしてサンプリング・レートを 22050Hzへ落とした。

再生でプチプチ・ノイズが出ないように、先頭は FadeIn、終端は FadeOut処理をしておく。

サウンド・ファイルの再生時間は編集ツールで確認しておく。

[Sound Files]
* 16_pacman_eatdot_256ms.wav 11,372bytes
* 16_pacman_eatfruit_438ms.wav 19,360bytes
* 16_pacman_eatghost_544ms.wav 24,040bytes
* 16_pacman_miss_1536ms.wav 67,788bytes
* 16_pacman_extrapac_1952ms.wav 86,166bytes
* 16_credit_224ms.wav 9,634bytes
* 16_BGM_normal_400ms.wav 17,852bytes
* 16_BGM_power_400ms.wav 17,750bytes
* 16_BGM_return_528ms.wav 23,366bytes
* 16_BGM_spurt1_352ms.wav 15,592bytes
* 16_BGM_spurt2_320ms.wav 14,212bytes
* 16_BGM_spurt3_592ms.wav 26,272bytes
* 16_BGM_spurt4_512ms.wav 23,018bytes
* 16_pacman_beginning_4224ms.wav 187,054bytes
* 16_pacman_intermission_5200ms.wav 229,388bytes

テスト・プログラム

GitHub に公開しているテスト・プログラムを実行すると、以下のような画面が表示され、BGMが5秒毎に切り替わる。スクリーンをタッチすると効果音が再生される。

Sound Manager クラスは、SoundManager.swift ファイルにコーディングしている。コメント入れて 200行未満となった。

class GameScene: SKScene {

    private var sound: CgSoundManager!

    override func didMove(to view: SKView) {

        // Create and reset sound object.
        sound = CgSoundManager(view: self)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        sound.playSE(.EatDot)
    }

    private let bgm: [CgSoundManager.EnKindOfSound] = [.BgmNormal, .BgmSpurt1, .BgmSpurt2, .BgmPower, .BgmReturn]
    private var bgmIndex: Int = 0
    private var bgmTime: Int = 0

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

        // Play back BGM.
        if bgmTime == 0 {
            bgmTime = 16*60*5  // 5s
            sound.playBGM(bgm[bgmIndex])
            bgmIndex += 1
            if bgmIndex >= bgm.count {
                bgmIndex = 0
            }
        } else {
            bgmTime -= 16
        }

        // Update sound manager.
        sound.update(interval: 16 /* ms */)
    }
}

CgSoundManagerクラスは、SKViewオブジェクトをパラメータとして、オブジェクトを生成する。

SKScene からオーバーライドした touchesEndedイベントのメソッドで、SEを再生する。

また、同様に updateイベントのメソッドで、5秒毎にBGMを切り替えて再生する。

参考

次の記事

【入門】iOS アプリ開発 #4【アーキテクチャの設計】

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3