2017年11月10日金曜日

SpriteKit で効果音を再生する方法

概要

SpriteKit で効果音を再生する方法を紹介します
ここで効果音は単発で再生する音のことを言います
例えばキャラがジャンプしたりアイテムを取得したときのような音です
ゲームの BGM のようにループでずっと流しておく感じではないです

いろいろとハマリポイントもあったのでその辺りも紹介します

環境

  • macOS X 10.13.1
  • Xcode 9.1 (9B55)
  • Swift3

SKAudioNode を使う

再生するためのコードは以下のような感じです

func play(music: String, loop: Bool) {
    if #available(iOS 9.0, *) {
        let play = SKAudioNode(fileNamed: music)
        play.autoplayLooped = loop
        self.addChild(play)
        self.run(
            SKAction.sequence([
                // SKAction.wait(forDuration: 0.1),
                SKAction.run {
                    play.run(SKAction.play())
                }
            ])
        )
    } else {
        let play = SKAction.playSoundFileNamed(music, waitForCompletion: true)
        self.run(play)
    }
}

まずポイントとしては iOS 9 以上と 8 以下で処理が変わるということです
iOS9 以上では SKAudioNode を使って再生できますが iOS8 以下では使えません
なので旧方式である SKAction を使って再生させます

SKAudioNode を使って再生する場合 addChild する必要があります
なので、音を再生するたびに SKScene 上にノードが追加されてしまうのでシーン上のノード数をカウントするようなアプリの場合には追加した音の数を考慮する必要があります

AVAudioPlayer を使う

以下のクラスを追加して使います
参考サイトにあるコードをほぼそのまま使っています

import Foundation
import SpriteKit
import AVFoundation

private let JKAudioInstance = JKAudioPlayer()

open class JKAudioPlayer {

    var musicPlayer: AVAudioPlayer!
    var soundPlayer: AVAudioPlayer!

    static var canShareAudio = false {
        didSet {
            canShareAudio ? try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
                : try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
        }
    }

    open class func sharedInstance() -> JKAudioPlayer {
        return JKAudioInstance
    }

    open func playMusic(_ fileName: String, withExtension type: String = "") {
        if let url = Bundle.main.url(forResource: fileName, withExtension: type) {
            musicPlayer = try? AVAudioPlayer(contentsOf: url)
            musicPlayer.numberOfLoops = -1
            musicPlayer.prepareToPlay()
            musicPlayer.play()
        }
    }

    open func stopMusic() {
        if musicPlayer != nil && musicPlayer!.isPlaying {
            musicPlayer.currentTime = 0
            musicPlayer.stop()
        }
    }

    open func pauseMusic() {
        if musicPlayer != nil && musicPlayer!.isPlaying {
            musicPlayer.pause()
        }
    }

    open func resumeMusic() {
        if musicPlayer != nil && !musicPlayer!.isPlaying {
            musicPlayer.play()
        }
    }

    open func playSoundEffect(named fileName: String) {
        if let url = Bundle.main.url(forResource: fileName, withExtension: "") {
            soundPlayer = try? AVAudioPlayer(contentsOf: url)
            soundPlayer.stop()
            soundPlayer.numberOfLoops = 0
            soundPlayer.prepareToPlay()
            soundPlayer.play()
        }
    }
}

これを追加して音を作成したいところで

let audio = JKAudioPlayer.sharedInstance()
audio.playSoundEffect(named: "sound1.mp3")

という感じで音を出します

こっちのやり方の方がノードを追加する必要もないので自分的にはオススメです
また iOS のバージョンも気にする必要はありません
クラスには playMusic という BGM を再生するようのメソッドも用意されているので BGM としてループするような音はそっちのメソッドを使うだけで実現できます

Tips

SpriteKit 上で音を再生するのは上記の方法でできます
が、それでもアプリと状況によっては「なぜか再生できない」という状況が発生します

自分が遭遇したケースでは音を再生する処理が終了する前に次のシーンに進むと音が出ないというケースがありました
また例えば音の再生を行っているメソッドがループするようなメソッドになっていて何度もコールされてしまい音がうまく再生されないというケースもありました
SKScene だと update メソッドや touchDown メソッドなどイベント系のメソッド内でコールするときには連続して音が再生されないような工夫が必要かなと思います

最後に

SpriteKit で音を再生してみました
自分は mp3 の効果音を再生しています
wav などでも再生できるらしいので拡張子は幅広くサポートしていると思います
タイミングなど結構考える点があり少しコツが必要かなと思いました

参考サイト

0 件のコメント:

コメントを投稿