Unityやってるワン

Unityで勉強したことを備忘録として記事にしていきます

音声を管理・制御するクラスを作ってみた

Unityで勉強したことを備忘録として記事にしていきます
とか言いつつ、しばらくサボってしまいました…
これからは週1回くらいのペースで、Unityで勉強したことを記事にしていきたいです。


今回はUnityでの音声関連の制御について勉強して、スクリプトを作ったので記事にしてみました。まずは音声の再生や停止などを制御するSoundPlayer.csです。



SoundPlayer.cs

using System.Collections;
using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class SoundPlayer : MonoBehaviour
{
    /// <summary>サウンドID</summary>
    private int m_soundID = 0;

    /// <summary>オーディオソース</summary>
    private AudioSource m_source;

    /// <summary>再生時間</summary>
    private float m_playTime = 0.0f;

    /// <summary>一時停止フラグ</summary>
    private bool m_isPauseNow = false;



    /// <summary>
    /// オブジェクト生成時
    /// </summary>
    private void Awake ()
    {
        m_source  = gameObject.GetComponent<AudioSource>();
        m_soundID = SoundManager.Instance.AddPlayer(this);
    }

    /// <summary>
    /// オブジェクト破棄時
    /// </summary>
    private void OnDestroy ()
    {
        SoundManager.Instance.RemovePlayer(m_soundID);
    }

    /// <summary>
    /// 再生
    /// </summary>
    public void Play ()
    {
        if (m_source.clip != null)
        {
            if (m_isPauseNow)
            {
                m_source.time = m_playTime;
                m_source.UnPause();
            }
            else
            {
                m_source.Stop();
                m_source.Play();
            }
            m_isPauseNow = false;
        }
    }

    /// <summary>
    /// 停止
    /// </summary>
    public void Stop ()
    {
        if (!m_source.isPlaying)
        {
            m_source.Stop();
            m_isPauseNow = false;
        }
    }

    /// <summary>
    /// 一時停止
    /// </summary>
    public void Pause ()
    {
        m_source.Pause();
        m_playTime = m_source.time;
        m_isPauseNow = true;
    }

    /// <summary>
    /// 音量セット
    /// </summary>
    /// <param name="_volume">音量</param>
    public void SetVolume (float _volume)
    {
        m_source.volume = _volume;
    }

    /// <summary>
    /// 再生速度セット
    /// </summary>
    /// <param name="_pitch">再生速度</param>
    public void SetPitch (float _pitch)
    {
        m_source.pitch  = _pitch;
    }
}

作ったとはいうものの、ほとんどAudioSourceが再生、停止などをやってくれるので自力でやってる感はあまりないです…
AudioSourceの処理以外に、Awakeで実態生成時にSoundPlayerのIDを取得して、SoundPlayerが破棄された時にOnDestroyでIDも破棄するようにしています。
このIDは以下のSoundManager.csでSoundPlayerを管理する際に使用しています。
他にはRequireComponentで必ずAudioSourceがアタッチされるようにしています。



SoundManager.cs

using System.Collections.Generic;
using System.Collections;
using UnityEngine;

public class SoundManager : SingletonClass<SoundManager>
{
    /// <summary>サウンドリスト</summary>
    public Dictionary<int, SoundPlayer> m_soundList = new Dictionary<int, SoundPlayer>();



    /// <summary>
    /// 実体生成時
    /// </summary>
    private void Awake ()
    {
        DontDestroyOnLoad(this);
    }

    /// <summary>
    /// プレイヤー追加
    /// </summary>
    /// <param name="_soundPlayer">サウンドプレイヤー</param>
    /// <returns>未使用ID</returns>
    public int AddPlayer (SoundPlayer _soundPlayer)
    {
        //使用されていないIDをサーチ
        int id = 0;
        while (true)
        {
            if (!m_soundList.ContainsKey(id))
            {
                m_soundList.Add(id, _soundPlayer);
                return id;
            }
            id++;
        }
    }

    /// <summary>
    /// プレイヤー破棄
    /// </summary>
    /// <param name="_id">サウンドID</param>
    public void RemovePlayer (int _id)
    {
        if (m_soundList.ContainsKey(_id))
        {
            m_soundList.Remove(_id);
        }
    }

    /// <summary>
    /// リスト破棄
    /// </summary>
    public void Clear ()
    {
        m_soundList.Clear();
    }

    /// <summary>
    /// 再生
    /// </summary>
    public void Play ()
    {
        foreach (SoundPlayer soundPlayer in m_soundList.Values)
        {
            soundPlayer.Play();
        }
    }

    /// <summary>
    /// 停止
    /// </summary>
    public void Stop ()
    {
        foreach (SoundPlayer soundPlayer in m_soundList.Values)
        {
            soundPlayer.Stop();
        }
    }

    /// <summary>
    /// 一時停止
    /// </summary>
    public void Pause ()
    {
        foreach (SoundPlayer soundPlayer in m_soundList.Values)
        {
            soundPlayer.Pause();
        }
    }

    /// <summary>
    /// 音量設定
    /// </summary>
    /// <param name="_volume">音量</param>
    public void SetVolume (float _volume)
    {
        foreach (SoundPlayer soundPlayer in m_soundList.Values)
        {
            soundPlayer.SetVolume(_volume);
        }
    }
}

SoundManagerではシーン内のSoundPlayerを一括管理します。
以前、記事に書いたSingletonClassを継承しているため、シーンが切り替わっても破棄されずに残ります。シーンの切り替え時にフェードイン・フェードアウトで全サウンドの音量を徐々に変えたい場合やポーズ画面で全サウンドを一時停止させたい場合に、SoundManagerで制御ができます。


学生の頃、DirectXC言語でサウンドの制御関連の処理を作った時にかなり苦戦した記憶がありますが、Unityだとあっという間に作れました。ゲームエンジンの恩恵ですね..


次回は入力関連について勉強して記事にまとめてみたいと思います。

継承することでシングルトンパターンで実装できるクラスを作ってみた

前回シングルトンパターンで管理系のクラスを作成する記事を書きました。
シングルトンでマネージャーっぽいクラスを作ってみた - Unityやってるワン


でも、シングルトンのパターンで実装するたびにstaticでインスタンスを保持する処理を用意するのはちょっと面倒です。なので継承することでシングルトンクラスとして実装することができるクラスを作ってみました。このクラスを継承すれば、シングルトンのパターンを毎回用意する必要がなくなりそうです。


SingletonClass.cs

using UnityEngine;

/// <summary>
/// シングルトンクラス
/// </summary>
public class SingletonClass<T> : MonoBehaviour
{
    /// <summary>インスタンス</summary>
    private static T m_instance;



    /// <summary>
    /// インスタンス取得
    /// </summary>
    public static T Singleton
    {
        get
        {
            if (m_instance == null)
            {
                //シングルトンオブジェクト生成
                var components = new[]{ typeof( T ) };
                GameObject obj = new GameObject(typeof(T).Name, components);
                m_instance = obj.GetComponent<T>();

                //シーン遷移時に破棄させないようにする
                DontDestroyOnLoad(obj);
            }

            return m_instance;
        }
    }

    /// <summary>
    /// インスタンスの生成を無効化
    /// </summary>
    protected SingletonClass () { }
}

シングルトンパターンのクラスSingletonClass.csを作ってみました。
ジェネリックでシングルトンパターンのクラスを宣言しています。SingletonClassを継承する際に継承先のクラスを渡す形で使用します。
ジェネリックとは、決まった型持たず後から型を指定することができる方法です。
実際にSingletonClassを使う場合は以下のような感じになります。


PointManager.cs

using UnityEngine;

/// <summary>
/// 得点管理クラス
/// </summary>
public class PointManager : SingletonClass<PointManager>
{
    /// <summary>得点</summary>
    private int m_point = 0;



    /// <summary>
    /// インスタンスの生成を無効化
    /// </summary>
    private PointManager () { }

    /// <summary>
    /// ポイント加算
    /// </summary>
    /// <param name="_point">加算するポイント</param>
    public void AddCount (int _point = 1)
    {
        m_point += _point;
    }

    /// <summary>
    /// ポイント取得
    /// </summary>
    public int Count { get { return m_point; } }
}

SingletonClassクラスを継承して得点を管理するPointManager.csを作りました。
継承する際に自身を渡してジェネリッククラスに型を認知させています。
これで、シングルトンのパターンを用意する手間が省けそうですね。


PointManagerは以下のような形で利用できます。

PointManager.Singleton.AddCount();
PointManager.Singleton.AddCount(10);
Debug.Log("現在の得点:" + PointManager.Singleton.Count);

f:id:nsdeveloperman:20170708164929p:plain

シングルトンでマネージャーっぽいクラスを作ってみた

Unityで入力や音声などを管理するクラスを作成する際は、シングルトンのパターンで実装することが多いようです。
シングルトンとはインスタンスが1つしか生成されないようにする実装パターンです。
〇〇Managerみたいな管理系のクラスは複数存在することがないようにこのパターンで実装するのがいいと思います。


ManagerClass.cs

using UnityEngine;

/// <summary>
/// マネージャークラス
/// </summary>
public class ManagerClass : MonoBehaviour
{
    /// <summary>インスタンス</summary>
    private static ManagerClass m_instance;

    /// <summary>
    /// インスタンス取得
    /// </summary>
    public static ManagerClass Instance
    {
        get
        {
            if (m_instance == null)
            {
                //オブジェクト生成
                GameObject managerObject = new GameObject("ManagerClass");
                m_instance = managerObject.AddComponent<ManagerClass>();

                //シーン遷移時に破棄させないようにする
                DontDestroyOnLoad(managerObject);
            }

            return m_instance;
        }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    protected ManagerClass () { }
}

実体を取得する関数を用意し、実体が生成されていなければ新たに生成するといった感じです。コンストラクタをprivateで宣言して、外部で実体を生成させないようにしています。また、GameObjectとして生成してからDontDestroyOnLoadでシーン遷移時に破棄されないようにしています。

これで入力や音声データなどを制御するManagerっぽいクラスが実装できそうですね。