Unityでオーディオを管理する その2 〜スクリプト作成〜

記事一覧

  1. Unityでオーディオを管理する その1 〜SQLの導入〜
  2. Unityでオーディオを管理する その2 〜スクリプト作成〜
  3. Unityでオーディオを管理する その3 〜DBからリストを作成する〜

←前回

次回→

前回はSQLiteを動かすところまで書きました。

今回はこれを使ってサウンド再生をするためのスクリプトを書いていきます。

作成にあたって

シーン遷移の際などに音が途切れないようにしたいため、シングルトンというデザインパターンを採用します。

シングルトンの設計については、こちらのサイト様を参考にさせていただきました。

【Unity】なんちゃらManagerクラスを作ろう(シングルトン)
UnityC#でシングルトンなManagerクラスを作ります。

というか、SingletonMonoBehavior.csは、サイトのものをそのまんま使わせていただいています。

AudioManagerの設計はこちらの記事がとても参考になります。

ぼくがかんがえたさいきょうのAudioManager2【Unity】 - (:3[kanのメモ帳]
ぼくがかんがえたさいきょうのAudioManager ぼくがかんがえたさいきょうのAudioManager【Unity】 - (:3上記記事の続編となります。記事を書いた後に改善案を思い付いたので実装してみました。 改善内容は ■オーディオリスナーおよびオーディオソースをスクリプトから追加する。 ■同時にSEを複数鳴ら...

必要な機能

スクリプト全般

  • BGM・SEが再生できる
  • 3D/2Dの切り替えができる
  • オーディオミキサーが使用できて、3D/2DそれぞれのBGMとSEにチャンネルがある。

欲しい機能はこんな感じです。

BGMとSEでは、求められる機能が微妙に変わってきます。

BGM

  • クリップを再生させる
  • 停止させる
  • フェードアウトさせる
  • 音量を変更する
  • 再生が終わったらメモリを開放させる(カクつくようならオフにする。コード内に記述)

SE

  • クリップを再生させる
  • 音量を変更する
  • 同時再生ができる。

スクリプト

これらの機能に対応できるようにしたAudioManagerスクリプトがこちらになります。


using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Audio;
using UniRx;



public class AudioManager : SingletonMonoBehaviour
{

    //ボリューム保存用のkeyとデフォルト値
    private const float BGM_VOLUME_DEFAULT = 0.1f;
    private const float SE_VOLUME_DEFAULT = 0.1f;
    private const float FADE_OUT_RES = 100f;
    private const float FADE_OUT_RATE = 10f;

    //SEの最大同時発音数(2D)
    private const int SE_PLAY_MAX = 5;

    //ミキサーのアウトプット設定
    [SerializeField]
    private AudioMixerGroup BGMout, BGMout_3D, SEout, SEout_3D;

    //BGM用、SE用に分けてオーディオソースを持つ
    private AudioSource BGMSource;
    private AudioSource BGM_source_3D;
    private List SESource_list;

    //再生用
    private AudioSource AttachBGMSource, AttachSESource;

    //Audioを保持するためのDictionary
    private Dictionary<string, AudioClip> clip_dic;

    //オーディオミキサーを保持(いらない気がする)
    [SerializeField]
    private AudioMixer AudioMixer;

    //フェードアウト中かを判定する
    private bool isfade;

    [SerializeField]
    bool debug_mode;

    private void Awake()
    {
        ///シングルトンのための記述
        if (this != Instance)
        {
            Destroy(this);
            return;
        }
        DontDestroyOnLoad(this.gameObject);

        ///3DのBGM用にAudioSourceを作成
        BGM_source_3D = new AudioSource();

        ///2DのSE用にAudioSourceを作成
        for (int i = 0; i < SE_PLAY_MAX; i++)
        {
            gameObject.AddComponent();
        }
        //SE
        AudioSource[] AudioSourceArray = GetComponents();
        SESource_list = new List();
        for (int i = 0; i < AudioSourceArray.Length; i++)
        {
            AudioSourceArray[i].playOnAwake = false;
            SESource_list.Add(AudioSourceArray[i]);
            AudioSourceArray[i].outputAudioMixerGroup = SEout;
        }

        //作成したオーディオソースを取得して各変数に設定、ボリュームも設定

        //BGM(2D)
        BGMSource = gameObject.AddComponent();
        BGMSource.loop = true;
        BGMSource.outputAudioMixerGroup = BGMout;
        BGMSource.volume = BGM_VOLUME_DEFAULT;
        //BGM(3D)
        BGM_source_3D = gameObject.AddComponent();
        BGM_source_3D.spatialBlend = 1;
        BGM_source_3D.loop = true;
        BGM_source_3D.outputAudioMixerGroup = BGMout_3D;
        BGM_source_3D.volume = BGM_VOLUME_DEFAULT;


    }
    //=====================================================================
    //再生
    //=====================================================================
    /// 
    /// BGMを再生させる
    public void PlaySound(string sound_name)
    {
        //データベースへの接続
        SqliteDatabase sqlDB = new SqliteDatabase("db.db");
        string query = string.Format("select * from sound where name = '{0}'", sound_name);
        //Debug.Log(query);
        DataTable dataTable = sqlDB.ExecuteQuery(query);

        string name = "";
        string clip = "";
        float volume = 0;
        string mode = "";
        string dimention = "";
        string model = "";
        //データを取得
        foreach (DataRow dr in dataTable.Rows)
        {
            name = (string)dr["name"];
            clip = (string)dr["clip"];
            volume = (int)dr["volume"]; volume = volume / 10;
            mode = (string)dr["mode"];
            dimention = (string)dr["3d"];
            model = (string)dr["model"];
            //Debug.Log(name + ":" + clip + ":" + volume + ":" + mode + ":" + 3d + ":" + model);

        }
        //データから再生方法を分岐
        if (dimention == "Yes" && mode == "BGM")
        {
            //Debug.Log("PlaySound:Mode…BGM Dimention…3D");
            ChangeBGMVolume(volume, "Mode_3D");
            PlayBGM_3D(clip, GameObject.Find(model));
        }

        else if (dimention == "Yes" && mode == "SE")
        {
            //Debug.Log("PlaySound:Mode…SE Dimention…3D");
            PlaySE_3D(clip, GameObject.Find(model), volume);
        }
        else if (dimention == "Yes" && model == "Nothing")
        {
            //Debug.Log("モデルがNothingのため2Dで再生します");
            ChangeBGMVolume(volume, "Mode_2D");
            PlayBGM(clip);
        }

        else if (dimention == "No" && mode == "BGM")
        {
            //Debug.Log("PlaySound:Mode…BGM Dimention…2D");
            ChangeBGMVolume(volume, "Mode_2D");
            PlayBGM(clip);
        }
        else if (dimention == "No" && mode == "SE")
        {
            //Debug.Log("PlaySound:Mode…SE Dimention…2D");
            PlaySE(clip, volume);
        }
        else
        {
            Debug.Log("エラー:存在しないクリップを参照");
        }

    }


    //================================================================
    //BGM再生部分
    //================================================================
    /// 
    /// 3DのBGMを再生する
    /// 指定したファイル名のBGMを流す。ただし既に流れている場合は前の曲をフェードアウトさせてから。
    ///  
    private void PlayBGM_3D(string BGMName, GameObject Model)
    {
        AttachBGMSource = BGM_source_3D;
        //現在BGMが流れていない時はそのまま流す
        if (!AttachBGMSource.isPlaying)
        {
            BGM_source_3D.transform.position = Model.transform.position;
            AttachBGMSource.clip = (AudioClip)Resources.Load("Clip/" + BGMName);
            AttachBGMSource.Play();
        }
        //違うBGMが流れている時は、流れているBGMを止めてから次を流す。同じBGMが流れている時はスルー
        else if (AttachBGMSource.clip.name != BGMName)
        {
            FadeOutBGM("Mode_3D", 1);//フェードアウト呼び出し
            Observable.Timer(TimeSpan.FromMilliseconds(1500)).Subscribe(_ => PlayBGM_3D(BGMName, Model));
            //Debug.Log("PlayBGM_3D:FadeOutとPlayBGM_3Dを実行 Next:" + BGMName);
        }
    }
    /// 
    /// 2DのBGMを再生する
    /// 指定したファイル名のBGMを流す。ただし既に流れている場合は前の曲をフェードアウトさせてから。
    /// 
    private void PlayBGM(string BGMName)
    {
        AttachBGMSource = BGMSource;
        //現在BGMが流れていない時はそのまま流す
        if (!AttachBGMSource.isPlaying)
        {
            AttachBGMSource.clip = (AudioClip)Resources.Load("Clip/" + BGMName);
            AttachBGMSource.Play();
        }
        //違うBGMが流れている時は、流れているBGMを止めてから次を流す。同じBGMが流れている時はスルー
        else if (AttachBGMSource.clip.name != BGMName)
        {
            FadeOutBGM("Mode_2D", 1);//フェードアウト呼び出し
            Observable.Timer(TimeSpan.FromMilliseconds(1500)).Subscribe(_ => PlayBGM(BGMName));
            //Debug.Log("PlayBGM:FadeOutとPlayBGMを実行 Next:" + BGMName);
        }

    }


    //=================================================================
    //停止
    //=================================================================
    /// 
    /// BGMをストップさせる 引数 Mode:"Mode_3D"…3Dのみ変更。"Mode_2D"…2Dのみ変更。"Mode_All"…どちらも変更。
    /// 
    public void StopBGM(string Mode)
    {
        if (Mode == "Mode_3D" || Mode == "Mode_All")
        {
            if (BGM_source_3D.isPlaying)
            {
                BGM_source_3D.Stop();
                BGM_source_3D.clip.UnloadAudioData();//停止時にカクつくようならここをコメントアウトする
            }
        }
        if (Mode == "Mode_2D" || Mode == "Mode_All")
        {
            if (BGMSource.isPlaying)
            {
                BGMSource.Stop();
                BGMSource.clip.UnloadAudioData();//停止時にカクつくようならここをコメントアウトする
            }

        }
    }
    //===============================================================
    //フェードアウト
    //===============================================================
    /// 
    /// BGMをフェードアウトさせる 引数 Mode:"Mode_3D"…3Dのみ変更。"Mode_2D"…2Dのみ変更。"Mode_All"…どちらも変更。"StopAll"…どちらも変更。FadeTime…ボリューム0までの時間。
    /// 
    public void FadeOutBGM(String Mode, float FadeTime)
    {

        if (isfade == false)
        {
            isfade = true;
            //Debug.Log("フェードフラグ:True");
            if (Mode == "Mode_3D" || Mode == "Mode_All")
            {
                AttachBGMSource = BGM_source_3D;
                if (AttachBGMSource.isPlaying)
                {
                    FadeOutRx(AttachBGMSource, FadeTime);
                }

            }
            if (Mode == "Mode_2D" || Mode == "Mode_All")
            {
                AttachBGMSource = BGMSource;
                if (AttachBGMSource.isPlaying)
                {
                    FadeOutRx(AttachBGMSource, FadeTime);
                }

            }
        }

    }


    /// 
    /// フェードアウトするメソッド部分。
    /// 
    private void FadeOutRx(AudioSource Source, float FadeTime)
    {

        float VolumeTmp = Source.volume;
        Observable.Interval(TimeSpan.FromMilliseconds(FADE_OUT_RES))
            //徐々に音を小さくする
            .Select(_ => Source.volume = Source.volume - Mathf.Lerp(0f, VolumeTmp, 1 / (FADE_OUT_RATE * FadeTime)))
            .First(volume => volume <= 0) .Subscribe(_ =>
            {
                Source.Stop();
                Source.volume = VolumeTmp;
                Source.clip.UnloadAudioData();
                isfade = false;
                //Debug.Log("フェードフラグ:False");
            })
            .AddTo(this);

    }

    //================================================================
    //ボリューム変更
    //================================================================
    /// 
    /// BGMのボリュームを変更&保存。引数2:Mode:"Mode_3D"…3Dのみ変更。"Mode_2D"…2Dのみ変更。"Mode_All"…どちらも変更。
    /// 
    public void ChangeBGMVolume(float BGMVolume, string Mode)
    {
        if (Mode == "Mode_3D" || Mode == "Mode_All")
        {
            AttachBGMSource = BGM_source_3D;
        }
        else if (Mode == "Mode_2D" || Mode == "Mode_All")
        {
            AttachBGMSource = BGMSource;
        }
        AttachBGMSource.volume = BGMVolume;
    }

    //=================================================================
    //ミキサーのスナップショット変更
    //================================================================
    /// 
    /// ミキサーに登録してあるシーンをリコールする。
    /// Scene_name…変更したいスナップショット、Duration…切り替わりの時間、通常は0f。
    /// 
    public void SceneRecall(AudioMixerSnapshot Scene_Name, float Duration)
    {
        Scene_Name.TransitionTo(Duration);
    }
    
    //================================================================
    //SE再生部分
    //================================================================
    /// 
    /// 指定したファイル名の2D-SEを流す。volume:ボリューム(デフォルトは1.0f)
    /// 
    private void PlaySE(string SEName, float volume = 1f)
    {
        foreach (AudioSource seSource in SESource_list)
        {
            if (!seSource.isPlaying)
            {
                seSource.volume = volume;
                seSource.PlayOneShot((AudioClip)Resources.Load("Clip/" + SEName));
                return;
            }
        }
    }
    /// 
    /// 指定したモデルの場所でSEを流す。
    /// 
    private void PlaySE_3D(string clip, GameObject Model, float ClipVolume)
    {
        //エディターで作業しているときはここのifを有効にする
        //if (UnityEditor.EditorApplication.isPlaying)
        //{
        GameObject Model_tmp = new GameObject(Model.name + "-Audio");
        AudioSource audio_tmp = new AudioSource();
        AudioClip audio = (AudioClip)Resources.Load("Clip/" + clip);
        audio_tmp = Model_tmp.AddComponent();
        audio_tmp.clip = audio;
        audio_tmp.transform.position = Model.transform.position;
        audio_tmp.spatialBlend = 1;
        audio_tmp.loop = false;
        audio_tmp.volume = ClipVolume;
        audio_tmp.outputAudioMixerGroup = SEout_3D;
        audio_tmp.Play();
        Destroy(Model_tmp, audio.length + 0.1f);
        //}

    }
    
}

※注意 これを動かそうという方へ

  • 動かすにはSingletonMonoBehabior.csとSQLiteUnityKit、UnyRxが必要です。
  • 随所に設計が甘い部分があります。
  • UnyRXは、コルーチンやlnvokeなどを使って置き換えても良いかもしれません。

サイトのスクリプトと違うところ

  • 事前に全てのクリップをロードせず、使うときに読み込んでくる。
  • SQLiteを使っている。
  • 3D音響に対応
  • UnyRXというアセットを使っている
  • オーディオミキサーに対応させている
    ※UnyRXは、非同期処理ができるようになるUnityのアセットで、例えば「n秒後に処理を実行」といった、これまではループやコルーチンを使って書かなければいけなかった記述を簡単に実現できるため活用しています。

3D音響を使ったスクリプトについては、こちらの記事も参考にどうぞ

AudioMixerに対応したPlayClipAtPoint風スクリプト
Unity標準のスクリプト「PlayClipAtPoint」では、AudioMixerの出力先を指定できないようなので、似たようなコマンドを作成しました。

ということで、今回はスクリプト作成編でした。

次回は実際に使用するための準備をしていきます

 

記事一覧

  1. Unityでオーディオを管理する その1 〜SQLの導入〜
  2. Unityでオーディオを管理する その2 〜スクリプト作成〜
  3. Unityでオーディオを管理する その3 〜DBからリストを作成する〜