ホーム < ゲームつくろー! < Unity/サウンド編

サウンド編
その3 スクリプト内だけでSEを鳴らす


 前章で何らかのトリガーをきっかけにSEを鳴らす方法を見てきました。そこではAudioSourceとAudioClipを予めスクリプトに知らせておいて、トリガーが駆動した時を見計らってAudioSource.PlayOuneShotメソッドにAudioClipを渡すことでSEを鳴らしました。いわば予定調和的な鳴らし方です。

 この方法は簡単なのですが、スクリプトに逐一鳴らすAudioClipを渡しておかなければならないのが面倒でした。鳴らす場所も鳴らすSEも決まっているなら事前準備無しに、

playSE( "se001" );

のような簡単な呼び出しで鳴るのが理想です。

 この章では、上のように極めて簡単にSEを鳴らす仕組みを作っていこうと思います。



@ どこからでも呼べる人

 いつでもどこでもSEを鳴らせるためには、いつでもどこからでも呼び出せる人を用意しなければなりません。さらに、それはアプリ内で唯一の存在である必要もあります。こういう条件を満たすにはデザインパターンの一つである「シングルトン」を実装するのが近道です。

 シングルトンの実装はそれ程難しくありません。ここでは厳密な実装は抜きに、最低限の機能を満たすシングルトンを実装します。コードはこんな感じです:

using UnityEngine;
using System.Collections;

public class Singleton<T> where T : class, new() {

    static T obj = null;

    Singleton() {}

    public static T instance {
        get {
            if ( obj == null )
                obj = new T();
            return obj;
        }
    }
}

これは「ジェネリッククラス」です。C++のテンプレートとほぼ同じですが、C#の場合は制約があります。whereの後ろがそれで、ここに型Tの性質を記述する必要があります。今は「T型はclassでデフォルトコンストラクタを持ている」という型しか受け付けないように制限しています。

 シングルトンが抱えているオブジェクトはinstanceプロパティが呼ばれた時に初めてnewされます。以後はinstanceを呼ぶ度に作ったそれを使い回す事になります。

 んーと、難しい事抜きに、SEを再生してくれる人をSoundPlayerというクラスだとすると、

Singleton<SoundPlayer>.instance.playSE( "se001" );

とすると音が鳴る(ように作る)という事です(^-^;;



A 音を鳴らす人「SoundPlayer」

 音を鳴らす人としてSoundPlayerさんを新設します。この人はゲーム内の音再生を管理します。SoundPlayerさんはシングルトンの中に作るため、newできる必要があります。Unityの基本クラスであるMonoBehaviourはnew出来ないため、SoundPlayerさんはこれを継承しません。

 SoundPlayerは特定のSEを鳴らすよう指示があった時に、そのSEに対応するAudioClipをProject内からロードします。一度ロードされたSEは内部に蓄えておいて、2度目以降はそれを利用します。

 AudioClipを動的にロードするには、AudioClipを「Resources」というフォルダの下に入れる必要があります。その状態で、

AudioClip clip = (AudioClip)Resources.Load( "kaifuku" );

とするとロードできます。このロードは同期読みなので、あまり長い物をロードすると処理が止まるので注意です。

 指定のSEを鳴らす処理はこんな感じになります:

SoundPlayer.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SoundPlayer {

    GameObject soundPlayerObj;
    AudioSource audioSource;
    Dictionary<string, AudioClipInfo> audioClips = new Dictionary<string, AudioClipInfo>();

    // AudioClip information
    class AudioClipInfo {
        public string resourceName;
        public string name;
        public AudioClip clip;

        public AudioClipInfo( string resourceName, string name ) {
            this.resourceName = resourceName;
            this.name = name;
        }
    }

    public SoundPlayer() {
        audioClips.Add( "se001", new AudioClipInfo( "kaifuku", "se001" ) );
        audioClips.Add( "bgm001", new AudioClipInfo( "Encounter_loop", "bgm001" ) );
    }

    public bool playSE( string seName ) {
        if ( audioClips.ContainsKey( seName ) == false )
            return false; // not register

        AudioClipInfo info = audioClips[ seName ];

        // Load
        if ( info.clip == null )
            info.clip = (AudioClip)Resources.Load( info.resourceName );

        if ( soundPlayerObj == null ) {
            soundPlayerObj = new GameObject( "SoundPlayer" );
            audioSource = soundPlayerObj.AddComponent<AudioSource>();
        }

        // Play SE
        audioSource.PlayOneShot( info.clip );

        return true;
    }
}

 ん〜、ちょっとだけ長くなってしまいました…。

 まず、コンストラクタで「SE名」と「SEファイル名」を対にして登録しています。通常、プログラム内で使用するSEの名前と実際のファイル名は別にします。正しくはファイル名を直接していしません。これは、SEは音が差し替わる事が良くあるからです。その時ファイル名で指定しているとその箇所をすべて直さなければなりませんが、プログラム内だけの名前にしておくと、リソースの差し替えだけで対応が終わります。

 playSEメソッドは引数に指定されたSE名のSEを再生します。実際に再生しているのは一番最後の1行なのですが、それまでに各種エラーチェックが走っています。

 登録した中に指定のSE名がなければ何もせずに返します。指定SEが登録されていた時に、それが初めて呼ばれた時はまだAudioClipがロードされていないため、AudioClipInfo.resourceNameでロードします。次にGameObjectを作ってAudioSourceをくっつけています。



B SoundPlayerを使ってみよう

 Aで作ったSoundPlayerはもう使えます。試しに前章で出てきたPlayerスクリプトで使ってみる事にしましょう。ちなみに前章のPlayerスクリプトはこんな感じでした:

Player.cs(その2掲載)
using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {

    public AudioClip audioClip;
    AudioSource audioSource;

    void Start() {
        audioSource = gameObject.GetComponent<AudioSource>();
        audioSource.clip = audioClip;
    }

    void Update () {
        if ( Input.GetKeyDown(KeyCode.A) == true ) {
            // Torigger
            audioSource.Play();
        }
    }
}

自身にAudioSourceコンポーネントが付いていて、audioClipがInspectorで指定されているという前提で、「A」キーを押すとSEが鳴ります。

 これを今回のSoundPlayerで置き換えるとこうなってしまいます:

Player.cs(SoundPlayer使用)
using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {
    void Update () {
        if ( Input.GetKeyDown(KeyCode.A) == true ) {
            // Torigger
            Singleton<SoundPlayer>.instance.playSE( "se001" );
        }
    }
}

激短!たった1行で指定のSEがちゃんとなります(^-^)。ただし各SEはResourceフォルダ下に置く必要があります:


 中々便利な人が出来ました。次はこの人をBGM再生用に機能追加していきましょう。