工場裏のアーカイブス

素人によるiPhoneアプリ開発の学習記 あと機械学習とかM5Stackとか

SpriteKitでのサウンド再生

ゲーム作成の際にはBGMや効果音などのサウンドも、ゲームを盛り上げるためには是非付けたいものです。本記事では、SpriteKitでサウンドを再生するための方法について調べたことをメモします。

SKActionによるサウンド再生

実はSpriteKitでは、アクションの一つとしてサウンドの再生機能が用意されています。これを利用すると、非常にお手軽に効果音などを鳴らすことが可能です。

具体的な利用法としては、まずプロジェクトにサウンドファイル(例えば”sound.caf”)を追加します。そして以下のようにplaySoundFileNamed:メソッドでサウンド再生アクションを生成し、実行させるだけです。対応しているサウンドファイルの形式については、SpriteKitの公式リファレンスにも記載が無かったのですが、自分が試した限りではcaf、mp3は問題なく再生可能であるようです。

SKAction *playSoundAction = [SKAction playSoundFileNamed:@"sound.caf" 
waitForCompletion:NO];
        
[self runAction:playSoundAction];


なお、メソッドのwaitForCompletion: 引数をNOにすると、 サウンドの再生が開始した瞬間にアクションは完了したとみなされます。引数をYESにすると、サウンドファイルの末尾までの再生が完了して、はじめてアクションは完了したとみなされます。

また、これもアクションの一種である以上、他のアクションと組み合わせることも可能です。例えばrepeatActionForever:と組み合わせると、同じサウンドを延々とループ再生させ続けることも可能です。

音量を調節したり、サウンドファイルの途中から再生したり…といった細かな設定は出来ないようですが、とにかくお手軽なのが良いところです。

通知センターを利用して、ViewController上の AVAudioPlayer をシーンから制御するサンプル

SKActionによるサウンド再生は便利なのですが、例えばアプリ起動中はずっと同じBGMをループ再生し続けて、シーン間の移動が起こる場合にもBGMが途切れないようにする……というケース(拙作のTricolorなど)では実装が困難で悩みました。

色々調べてみたところ、このようなケースは、例えばViewController上でサウンド再生機構(ここではSKActionは利用出来ないので、AVAudioPlayerなど)を用意して、各シーンの処理とは独立してBGMを再生するようにすれば実現出来ることが分かりました。

…しかし、実際にやってみると確かに目的は達せられたのですが、今度はシーン上からBGMのオン、オフを切り替える機能を付けたいと考えたときにまた悩みました。つまりシーンからViewController上のAVAudioPlayerを制御したいのですが、これが一筋縄では行きませんでした。シーンからViewControllerへの参照を取得するような方法が真っ先に浮かびましたが、それはMVCパターンの設計を壊すため推奨されないようです。

そこで、また色々調べてみたところ、通知センター(NSNotification)を利用して、シーンからViewController上の要素を参照なしで制御する方法というのが見つかりました。この方法がベストなのかは分かりませんが、同じようなケースで悩んでいる方の参考になるかもしれませんので、サンプルを作成してみました。

プロジェクトの作成

まず、Xcode(本記事執筆時点:Version 5.1.1)で「SpriteKit」テンプレートを選択してプロジェクトを作成します。そしてプロジェクトに適当なサウンドファイル(例えば”bgm.caf”)を追加します。

ViewControllerの書き換え

ViewController.m を以下のコードに書き換えます(ViewController.h は書き換え不要)

#import "ViewController.h"
#import "MyScene.h"

@import AVFoundation; //AVAudioPlayerを利用するために必要

@interface ViewController()

@property(nonatomic)AVAudioPlayer *bgmPlayer; //プロパティとしてAVAudioPlayerを追加

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //通知センターの追加
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(switchBGM:)
     name:@"BGMButton"
     object:nil];
    
    //AVAudioPlayerによるBGM再生開始(サウンドファイル名は適宜書き換え)
    NSURL *bgmURL = [[NSBundle mainBundle] URLForResource:@“bgm" withExtension:@“caf"];
    self.bgmPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:bgmURL error:nil];
    self.bgmPlayer.volume = 0.5;
    self.bgmPlayer.numberOfLoops = -1;
    [self.bgmPlayer prepareToPlay];
    [self.bgmPlayer play];

    // Configure the view.
    SKView * skView = (SKView *)self.view;
    
    // Create and configure the scene.
    SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
    scene.scaleMode = SKSceneScaleModeAspectFill;
    
    // Present the scene.
    [skView presentScene:scene];
}

//通知センターが呼び出すBGMのオンオフ切り替えメソッドの追加
-(void)switchBGM:(NSNotification *)notification{
    NSDictionary *userInfo = [notification userInfo];
    NSNumber *isBGMPlay = (NSNumber *)[userInfo objectForKey:@"isBGMPlay"];
    
    //MyScene.m からの通知(userInfo に格納されたBOOL値)でBGMをオンオフ
    if([isBGMPlay boolValue] == YES){
        self.bgmPlayer.currentTime = 0;
        [self.bgmPlayer play];
    }
    else [self.bgmPlayer stop];
}

- (BOOL)shouldAutorotate
{
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return UIInterfaceOrientationMaskAllButUpsideDown;
    } else {
        return UIInterfaceOrientationMaskAll;
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

@end


MySceneの書き換え

MyScene.m を以下のコードに書き換えます(MyScene.h は書き換え不要)

#import "MyScene.h"

@interface MyScene(){
    BOOL isPlayBGM; //BGMのオン or オフの状態を表す変数
}

@end

@implementation MyScene

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
        isPlayBGM = YES;
        
        //BGMのオン or オフを切り替えるボタン(SKLabelNodeを利用して実装)
        SKLabelNode *BGMButton = [SKLabelNode labelNodeWithFontNamed:@"Gill Sans Light"];
        BGMButton.name = @"BGM";
        BGMButton.text = @"BGM : On";
        BGMButton.fontSize = 50;
        BGMButton.fontColor = [SKColor blackColor];
        BGMButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
        
        [self addChild:BGMButton];
    }
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    SKLabelNode *BGMButton = (SKLabelNode *)[self childNodeWithName:@"BGM"];
    
    CGPoint point = [[touches anyObject] locationInNode:self];
    
    //ボタンがタップされたことを検知したときの処理
    if ([BGMButton containsPoint:point]) {
        if (isPlayBGM == YES) {
            isPlayBGM = NO;
            BGMButton.text = @"BGM : Off";
            
            //ViewController.m の通知センターに、BGMのオフを通知(userInfo に格納したBOOL値で)
            NSDictionary *userInfo = [NSDictionary dictionaryWithObject:
                                      [NSNumber numberWithBool:NO] forKey:@"isPlayBGM"];
            [[NSNotificationCenter defaultCenter]
             postNotificationName:@"BGMButton" object:self userInfo:userInfo];
        }else{
            isPlayBGM = YES;
            BGMButton.text = @"BGM : On";
            
            //同様に、ViewController.m の通知センターに、BGMのオンを通知
            NSDictionary *userInfo = [NSDictionary dictionaryWithObject:
                                      [NSNumber numberWithBool:YES] forKey:@"isBGMPlay"];
            [[NSNotificationCenter defaultCenter]
             postNotificationName:@"BGMButton" object:self userInfo:userInfo];
        }
    }
}

@end


サンプルの実行

ViewController.m、MyScene.mを書き換えてサンプルを実行すると下図左のようになり、BGMも再生されます。そして画面中央のボタン(文字ラベル)をタップするごとに、BGMのオン、オフが切り替わります。

f:id:fleron:20140812005657p:plain

ViewController上で通知センター(NSNotification)を用意し、MySceneでBGMのオン、オフの切り替え操作があったときには、userInfoという変数名のNSDictionaryを介してViewControllerに通知とBGMのオンオフ指示情報が送られるようにしています。そして通知を受け取るごとにswitchBGM:メソッドが実行されて、AVAudioPlayerのオンオフが切り替わります。