【Swift】SpriteKitの使い方。バックミュージックや効果音を鳴らす。(Swift 2.2、XCode 7.3)
PlaySoundFileNamed Actionとは
本記事ではSpriteKit Sceneファイルの編集画面(シーンエディタ)にある部品のPlaySoundFileNamed Action(以下、サウンドアクション)について説明する。
サウンドアクションとは、バックミュージックや効果音を再生するためのアクションである。ゲームを楽しく盛り上げるにはサウンドは欠かせない存在だ。
サウンドアクションを使ってみる
実際にサウンドアクションを使ってみよう。以降の手順を行う前のXcodeプロジェクトをGitHubに置いたので、試してみる方はご利用下さい。⇒「テスト用プロジェクト」
事前準備では、背景と鳥2匹をシーンに追加し、タップで茶鳥を落として赤鳥と衝突するところまでを実装しておいた。それと、サウンドファイル(フリー素材)を追加しておいた。
下図赤枠のSKSファイルを選択してシーンエディタを開く。Animate横のボタン(黄緑枠)を押してタイムラインを表示する。茶色枠のオブジェクトライブラリボタンを押して部品一覧を表示し、ドラッグ&ドロップで「PlaySoundFileNamed Action」を背景ノードのタイムラインまで運ぶ(紫矢印)。
アトリビュートインスペクタボタンを押して設定画面を表示し、Filenameに「bird_music」を設定する。追加したアクションの左隅(黄枠)をクリックして吹き出しのメニューを表示し「∞」を選択する。これで、サウンドが繰り返し再生されるようになった。
以下は実際のプレイ動画
アトリビュートインスペクタの設定項目は以下の3つ。
StartTime
シーンが切り替わってからサウンドが再生し始めるまでの時間(秒)
Duration
サウンドの再生時間(秒)
Filename
サウンドファイル名
ソースコードでサウンドを再生する
SKSファイルでは無く、ソースコードでサウンドを再生する場合はdidMoveToViewメソッドを以下のコードのように変更する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//現在シーン設定時の呼び出しメソッド override func didMoveToView(view: SKView) { //SKSファイルに配置した鳥ノードを取得する。 birdBrown = self.childNodeWithName("bird_brown") as? SKSpriteNode //デリゲート先を自分に設定する。 self.physicsWorld.contactDelegate = self //サウンドアクションを作成する。 let action = SKAction.playSoundFileNamed("bird_music", waitForCompletion: true) //無限ループするアクションに変更する。 let actionLoop = SKAction.repeatActionForever(action) //アクションを実行する。 self.runAction(actionLoop) } |
playSoundFileNamedメソッドに与える引数のwaitForCompletionは、trueの場合はサウンドがすべて流れ終わったらアクション終了、falseの場合はサウンド再生開始とともにアクション終了となる。
例えば、以下のコードのようにド、ミ、ソの3つのサウンドを順番に再生するアクションを作るときに、waitForCompletionをすべてtrueにするとどうなるか。
1 2 3 4 5 6 7 8 9 10 11 |
//サウンドアクションを作成する。 let action = SKAction.playSoundFileNamed("sound_do.mp3", waitForCompletion: true) let action1 = SKAction.playSoundFileNamed("sound_mi.mp3", waitForCompletion: true) let action2 = SKAction.playSoundFileNamed("sound_so.mp3", waitForCompletion: true) //アクションを順番に実行する。 let actionAll = SKAction.sequence([action, action1, action2]) //アクションを実行する。 self.runAction(actionAll) |
以下の動画のようになる。
waitForCompletionをすべてfalseにするとどうなるか。
1 2 3 4 5 6 7 8 9 10 11 |
//サウンドアクションを作成する。 let action = SKAction.playSoundFileNamed("sound_do.mp3", waitForCompletion: false) let action1 = SKAction.playSoundFileNamed("sound_mi.mp3", waitForCompletion: false) let action2 = SKAction.playSoundFileNamed("sound_so.mp3", waitForCompletion: false) //アクションを順番に実行する。 let actionAll = SKAction.sequence([action, action1, action2]) //アクションを実行する。 self.runAction(actionAll) |
以下の動画のようになる。ドミソがほぼ同時に再生されて和音になった。サウンドが開始した瞬間に次のアクションに移行するため、同時に再生されたように聞こえるということだ。
今回の検証では、サウンドを再生するアクションは1つなのでtrueでもfalseでも変わらないように思える。
しかし、falseにして実行してみたところ、筆者の環境ではアクションを無限ループに変更する箇所でメモリが増え続け、サウンドは再生されずにアプリが以下のエラーを吐いて落ちる。。(・□・;)
新手のバグだろうか。。waitForComplitionをtrueにすればメモリは増えずにサウンドが再生されるので今回の検証ではtrueで進める。
1 2 3 4 |
TestPlaySoundFile(1614,0x2e77000) malloc: *** mach_vm_map(size=1048576) failed (error code=3) *** error: can't allocate region securely *** set a breakpoint in malloc_error_break to debug |
サウンドアクションで再生した音楽を止めるには以下のコードのように、removeActionForKeyメソッドを利用する方法が昔あったが、現在の筆者の環境ではなぜか停止できない。⇒参考stackoverflow「How to stop/cancel PlaySoundFileNamed?」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// // TestScene.swift // import Foundation import SpriteKit class TestScene:SKScene { //現在シーン設定時の呼び出しメソッド override func didMoveToView(view: SKView) { //サウンドアクションを作成する。 let action = SKAction.playSoundFileNamed("bird_music", waitForCompletion: false) //アクションを実行する。 self.runAction(action, withKey: "test") } //画面タッチ時の呼び出しメソッド override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { //アクションを削除する。 self.removeActionForKey("test") } } |
音楽の一時停止や音量、速度の調整が必要であれば、サウンドアクションを使わずに、以下のコードのようにAVAudioPlayerを利用したほうが良さそうだ。AVAudioPlayerの使い方については次の記事を参照されたし。⇒「AVAudioPlayerとは」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// // TestScene.swift // import Foundation import SpriteKit import AVFoundation class TestScene:SKScene { // var birdBrown:SKSpriteNode! var player:AVAudioPlayer! //現在シーン設定時の呼び出しメソッド override func didMoveToView(view: SKView) { let url = NSBundle.mainBundle().bundleURL.URLByAppendingPathComponent("bird_music.mp3") do { try player = AVAudioPlayer(contentsOfURL:url) //音楽を再生する。 player.play() } catch { print(error) } } //画面タッチ時の呼び出しメソッド override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { if(player.playing) { //音楽が再生中の場合は停止する。 player.stop() } else { //音楽が停止中の場合は再生する。 player.play() } } } |
以下はAVAudioPlayerを使ったプレイ動画
効果音を鳴らす
サウンドアクションを効果音に使う場合は、アクションをループさせないだけである。では最後に、鳥ノードが衝突したときに鳥の鳴き声の効果音を鳴らそう。
TestScene.swiftを以下のコードに変更する。ノードの接触が通知されたらサウンドアクションを使って効果音を再生している。バックミュージックはAVAudioPlayerで再生し、画面タップで一時停止できるようにした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
// // TestScene.swift // import Foundation import SpriteKit import AVFoundation class TestScene:SKScene, SKPhysicsContactDelegate { var birdBrown:SKSpriteNode! var player:AVAudioPlayer! //バックミュージック let url = NSBundle.mainBundle().bundleURL.URLByAppendingPathComponent("bird_music.mp3") //効果音 let action = SKAction.playSoundFileNamed("bird_sound.mp3", waitForCompletion: true) //現在シーン設定時の呼び出しメソッド override func didMoveToView(view: SKView) { //SKSファイルに配置した鳥ノードを取得する。 birdBrown = self.childNodeWithName("bird_brown") as? SKSpriteNode do { //音楽を再生する。 try player = AVAudioPlayer(contentsOfURL:url) player.play() } catch { print(error) } //デリゲート先を自分に設定する。 self.physicsWorld.contactDelegate = self } //画面タッチ時の呼び出しメソッド override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { //タッチした座標のノードを取得する。 let location = touches.first!.locationInNode(self) let node = nodeAtPoint(location) as! SKSpriteNode if(node == birdBrown){ //茶鳥をタップした場合、重力の影響を受けるようにする。 node.physicsBody?.affectedByGravity = true } else if(player.playing) { //音楽が再生中の場合は停止する。 player.stop() } else { //音楽が停止中の場合は再生する。 player.play() } } //接触開始時の呼び出しメソッド func didBeginContact(contact: SKPhysicsContact) { //アクションを実行する。 self.runAction(action) } } |
以下は実際のプレイ動画