以前作ったSoundManagerの改良

追記 2020/03/16
githubで公開しました。
GitHub - wakepon/SoundManager: This is SoundManager for Unity project.

                              • -

効果音を鳴らした後にスクリプトからFadeOutしたり、FadeInとかしたくなったので、以前作ったSoundManagerを改良しました。

qiita.com
(この時はqiita使ってた...)

  • Handleを返すようにして、Handle経由でFadeIn, FadeOutができるように
  • それにともない。同じフレームで同じSeが重複して鳴るのを回避する方式を変更

ソース

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

[System.Serializable]
public class SoundVolume{
    public float bgm   = 1.0f;
    public float se    = 1.0f;

    public bool  mute  = false;

    public void Reset(){
        bgm  = 1.0f;
        se   = 1.0f;
        mute = false;
    }
}

public class SoundManager : SingletonMonoBehaviour< SoundManager > {

    public class Handle{
        public float volume    = 1.0f;
        public float fadeSpeed = 1.0f;
        public long  frame     = 0;

        public void FadeIn(){
            SoundManager.Instance.StartCoroutine( fadeIn() );
        }

        public void FadeOut(){
            SoundManager.Instance.StartCoroutine( fadeOut() );
        }

        public void ResetParams(){
            volume = 1.0f;
            fadeSpeed = 1.0f;
            frame = 0;
        }

        private IEnumerator fadeIn(){
            while( volume < 1.0f )
            {
                volume += fadeSpeed * Time.deltaTime;
                yield return null;
            }
            volume = 1.0f;
        }

        private IEnumerator fadeOut(){
            while( volume > 0.0f )
            {
                volume -= fadeSpeed * Time.deltaTime;
                yield return null;
            }
            volume = 0.0f;
        }
    }

    [SerializeField]
    private SoundVolume volume = new SoundVolume();
    public SoundVolume Volume { 
        get { return volume; }
        set { volume = value; } 
    }

	private AudioClip[] seClips;
	private AudioClip[] bgmClips;

	private Dictionary<string,int> seIndexes = new Dictionary<string,int>();
	private Dictionary<string,int> bgmIndexes = new Dictionary<string,int>();

    private const int cNumChannel = 16;

    private AudioSource bgmSource;
    private Handle      bgmHandle = new Handle();

    private AudioSource[] seSources = new AudioSource[cNumChannel];
    private Dictionary<Handle,AudioSource> seHandles = new Dictionary<Handle,AudioSource>();

    private long frameCounter;

	//------------------------------------------------------------------------------
	void Awake () {
		if( this != Instance )
		{
			Destroy(this);
			return;
		}

        bgmSource = gameObject.AddComponent<AudioSource>();
        bgmSource.loop = true;

        for(int i = 0 ; i < seSources.Length ; i++ ){
            seSources[i] = gameObject.AddComponent<AudioSource>();
            seHandles[new Handle()] = seSources[i];
        }

        seClips  = Resources.LoadAll<AudioClip>("Audio/SE");
        bgmClips = Resources.LoadAll<AudioClip>("Audio/BGM");

        for( int i = 0; i < seClips.Length; ++i )
        {
            seIndexes[seClips[i].name] = i;
        }

        for( int i = 0; i < bgmClips.Length; ++i )
        {
            bgmIndexes[bgmClips[i].name] = i;
        }
	}

	//------------------------------------------------------------------------------
	void Update()
	{
        bgmSource.mute = volume.mute;
        foreach(var source in seSources ){
            source.mute = volume.mute;
        }

        bgmSource.volume = volume.bgm * bgmHandle.volume;
        foreach(var pair in seHandles ){
            pair.Value.volume = volume.se * pair.Key.volume;
        }

        frameCounter++;
	}

	//------------------------------------------------------------------------------
	public int GetSeIndex( string name )
	{
        return seIndexes[name];
	}

	//------------------------------------------------------------------------------
	public int GetBgmIndex( string name )
	{
        return bgmIndexes[name];
	}

    //------------------------------------------------------------------------------
    public Handle PlayBgm( string name ){
        int index = bgmIndexes[name];
        return PlayBgm( index );
    }

    //------------------------------------------------------------------------------
    public Handle PlayBgm( int index ){
        if( 0 > index || bgmClips.Length <= index ){
            return null;
        }

        if( bgmSource.clip == bgmClips[index] ){
            return bgmHandle;
        }

        bgmSource.Stop();
        bgmSource.clip = bgmClips[index];
        bgmSource.Play();

        bgmHandle.ResetParams();
        bgmHandle.frame = frameCounter;

        return bgmHandle;
    }

    //------------------------------------------------------------------------------
    public void StopBgm(){
        bgmSource.Stop();
        bgmSource.clip = null;
    }

    //------------------------------------------------------------------------------
    public bool IsBgmPlaying{ get{ return bgmSource.isPlaying; } }

	//------------------------------------------------------------------------------
	public Handle PlaySe( string name )
    {
        return PlaySe( GetSeIndex( name ) );
    }
   
	//------------------------------------------------------------------------------
	public Handle PlaySe( int index )
    {
        if( 0 > index || seClips.Length <= index ){
            return null;
        }

        //@memo 二回ループは一回ループにまとめられるが、
        //可読性重視で二回ループにしておく
        //for avoiding duplicated sounds
        //同一フレームでの重複再生回避
        foreach( var k in seHandles ){
            AudioSource source = k.Value;
            Handle handle = k.Key;
            if( source.clip == seClips[index] && 
                 handle.frame == frameCounter )
            {
                return handle;
            }
        }

        foreach( var k in seHandles ){
            AudioSource source = k.Value;
            Handle handle = k.Key;
            if( false == source.isPlaying ){
                handle.ResetParams();
                source.clip = seClips[index];
                source.Play();
                handle.frame = frameCounter;
                return handle;
            }
        } 

        return null;
	}

    //------------------------------------------------------------------------------
    public void StopSe(){
        foreach(AudioSource source in seSources){
            source.Stop();
            source.clip = null;
        }  
    }
}