アマゾンバナーリンク

Unityで作る弾幕シューティングゲームその5 音声を扱うフレームワークの作成

こんにちは!ジェイです。ゲームといえば、BGMやSEがかかせないですよね?今回はそれを簡単に使える様にまとめたフレームワークの作成について説明します。

まずは、通常のUnityで音を出すときの手順を考えてみます。

  • UnityのProjectウィンドウに音声ファイルを直接ドロップ
  • AudioClipをスクリプト内に用意
  • AudioSourceをスクリプト内に用意
  • AudioSource.clipにAudioClipを代入
  • AudioSource.Playメソッド呼び出し
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
            Debug.Log( "A!" );
        }
    }
}

以上のスクリプトでauidoClipにならしたい音声をアタッチするとAキーを押すたびにその音がなるようになります。

スクリプト内だけで音声を鳴らす

通常スクリプトで音を鳴らすには、わざわざ音声を1つずつアタッチしなければなりません。これはめんどくさいですよね。最初に音声のリソースを一気に読み込んで、名前を指定して音を鳴らせると、とても便利なのでそのような仕組みを作ります。

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

public class CResourcesLoader<T> where T : UnityEngine.Object
{
	Dictionary<string, T> ResourcesHandles = new Dictionary<string, T>();

	public bool LoadObject(string file_name)
	{
		string fn = Path.GetFileName(file_name);

		if (ResourcesHandles.ContainsKey(fn))
		{
			Debug.Log("LoadObject()で同じ名前のキーがありました");
			Debug.Log(file_name + "にある" + fn + "の名前を変更してください");
			return false;
		}
		else
		{
			T ob = Resources.Load<T>(file_name);
			if (ob)
			{
				ResourcesHandles.Add(fn, ob);
				return true;
			}
			else
			{
				Debug.Log("LoadObject()" + file_name + "の読み込みが失敗しました");
				return false;
			}
		}
	}

	public bool LoadAllObjects(string file_name)
	{
		T[] obs = Resources.LoadAll<T>(file_name);

		if (obs.Length <= 0)
		{
			return false;
		}

		foreach (T ob in obs)
		{
			if (ResourcesHandles.ContainsKey(ob.name))
			{
				Debug.Log("LoadAllObject()で同じ名前のキーがありました");
				Debug.Log(file_name + "にある" + ob.name + "の名前を変更してください");
			}
			else
			{
				ResourcesHandles.Add(ob.name, ob);
			}
		}
		return true;
	}

	public T GetObjectHandle(string name)
	{
		if (ResourcesHandles.ContainsKey(name))
		{
			return ResourcesHandles[name];
		}
		else
		{

			return null;
		}
	}

	public bool ContainsKey(string key_name)
	{
		return ResourcesHandles.ContainsKey(key_name);
	}
}
public class CSoundPlayer
{
	static GameObject SoundPlayerObj, BGMPlayerObj;
	static CResourcesLoader<AudioClip> ResourcesLoader = new CResourcesLoader<AudioClip>();
	static AudioSource SoundAudioSource, BGMAudioSource;
	static CFadeTimer FadeTimer;

	public static IEnumerator SetFadeTimer(float start_val, float end_val, float end_time)
	{
		FadeTimer = new CFadeTimer(start_val, end_val, end_time);

		while (true)
		{
			float t = FadeTimer.CalcTime();
			BGMAudioSource.volume = t;

			if (t <= 0.0f)
			{
				BGMAudioSource.Stop();
				yield break;
			}
			else
			{
				yield return new WaitForSeconds(Time.deltaTime);
			}
		}
	}

	public static bool LoadAudioClip(string audio_path)
	{
		return ResourcesLoader.LoadObject(audio_path);
	}

	public static bool LoadAllSounds(string audio_path)
	{
		return ResourcesLoader.LoadAllObjects(audio_path);
	}

	public static bool StopSound(string se_name)
	{
		GameObject sound_obj = GameObject.Find(se_name);
		if (sound_obj)
		{
			AudioSource aus = sound_obj.GetComponent<AudioSource>();
			if (aus)
			{
				aus.Stop();
				return true;
			}
		}
		return false;
	}

	public static AudioClip GetAudioClip(string se_name)
	{
		if (ResourcesLoader.ContainsKey(se_name) == false)
		{
			return null;
		}
		else
		{
			return ResourcesLoader.GetObjectHandle(se_name);
		}
	}

	public static bool PlaySound(string se_name, bool se_flag = true)
	{
		if (ResourcesLoader.ContainsKey(se_name) == false)
		{
			return false;
		}

		if (se_flag)
		{
			if (SoundPlayerObj == null)
			{
				SoundPlayerObj = new GameObject("SoundPlayer");
				SoundAudioSource = SoundPlayerObj.AddComponent<AudioSource>();
			}
			SoundAudioSource.PlayOneShot(ResourcesLoader.GetObjectHandle(se_name));
		}
		else
		{
			if (BGMPlayerObj == null)
			{
				BGMPlayerObj = new GameObject("BGMPlayer");
				BGMAudioSource = BGMPlayerObj.AddComponent<AudioSource>();
				BGMAudioSource.clip = ResourcesLoader.GetObjectHandle(se_name);
				BGMAudioSource.volume = 1.0f;
				BGMAudioSource.loop = true;
				BGMAudioSource.Play();
			}
			else
			{
				if (BGMAudioSource)
				{
					if (BGMAudioSource.isPlaying)
					{
						BGMAudioSource.Stop();
					}
					else
					{
						BGMAudioSource.Play();
					}
				}
				else
				{
					BGMAudioSource = BGMPlayerObj.AddComponent<AudioSource>();
					if (BGMAudioSource)
					{
						BGMAudioSource.clip = ResourcesLoader.GetObjectHandle(se_name);
						BGMAudioSource.volume = 1.0f;
						BGMAudioSource.loop = true;
						BGMAudioSource.Play();
					}
				}
			}
		}

		return true;
	}
}

// 指定した時間である値からある値まで数を変化させる
// start_val 変化させたい数の初期値
// end_val	 変化させたい数の終了値
// end_time  終了時間(end_timeかけて終了させる)
// 使用手順
// 1 宣言する
// CFadeTimer FadeTimer;
// 2 初期化する
// FadeTimer = new CFadeTimer( 100, 1000, 3 );
// 3 毎ループ行う処理を追加する
// print( FadeTimer.CalcTime () );
public class CFadeTimer
{
	private bool Flag = true;
	private float StartVal, EndVal, StartTime, EndTime, Delta, Result = 0.0f;

	public CFadeTimer(float start_val, float end_val, float end_time)
	{
		StartVal = start_val;
		EndVal = end_val;
		EndTime = end_time;
		StartTime = Time.realtimeSinceStartup;
		Delta = (EndVal - StartVal) / EndTime;
	}

	public float CalcTime()
	{
		if (Flag)
		{
			float t = Time.realtimeSinceStartup - StartTime;
			if (EndTime <= t)
			{
				Flag = false;
				Result = EndVal;
				return Result;
			}
			Result = Delta * t + StartVal;
			return Result;
		}
		else
		{
			return -1.0f;
		}
	}
}

CResourcesLoaderジェネリッククラスで実装していて、where T : UnityEngine.Objectというキーワードで、UnityEngine.Objectのみ型を指定できるように制約することによって、Resources.LoadとResources.LoadAllを使えるようにしています。

実際の使用例

using UnityEngine;
using System.Collections;

public class CResourcesManager : MonoBehaviour
{
	private static CResourcesLoader<GameObject> ResourcesLoader = new CResourcesLoader<GameObject>();
	private static CResourcesLoader<Texture2D> TexturesLoader = new CResourcesLoader<Texture2D>();

	public static bool LoadTexture( string file_name )
	{
		return TexturesLoader.LoadObject( file_name );
	}

	public static Texture2D GetTextureHandle( string obj_name )
	{
		return TexturesLoader.GetObjectHandle( obj_name );
	}

	public static GameObject GetObjectHandle( string obj_name )
	{
		return ResourcesLoader.GetObjectHandle( obj_name );
	}
	
	void Awake()
	{
        // Prefabsフォルダから、すべてのGameObjectを読み込む
        if ( !ResourcesLoader.LoadAllObjects( "Prefabs" ) )
		{
			print ("Prefabsファイル読み込みに失敗しました");
		}
        // 音声ファイルを一曲だけ読み込む
        if (!CSoundPlayer.LoadAudioClip("Sound/Luste"))
        {
            print("Lusteファイル読み込みに失敗しました");
        }
        // Soundフォルダからすべての音声ファイルを読み込む
        if (!CSoundPlayer.LoadAllSounds("Sound"))
        {
            print("Soundファイル読み込みに失敗しました");
        }
        // se_flagオフで音声ファイルを再生する
        CSoundPlayer.PlaySound("Luste", false);
    }

    void Update()
    {
        // Jキーを押すと5秒かけて再生中のフェードアウトする
        if (Input.GetKeyDown(KeyCode.J))
        {
            StartCoroutine(CSoundPlayer.SetFadeTimer(1.0f, 0.0f, 5.0f));
        }
        // Kキーを押すと5秒かけてフェードイン再生する
        if (Input.GetKeyDown(KeyCode.K))
        {
            CSoundPlayer.PlaySound("dangeon07", false);
            StartCoroutine(CSoundPlayer.SetFadeTimer(0.0f, 1.0f, 5.0f));
        }
    }
}

上記のようにGameObjectやTexture2Dなどのリソースをフォルダごと読み込んだり、ファイル1つずつ読み込んだりできるので便利です。

ゲームに組み込んでみる

次に実際に音をならしてみましょう。まず準備でCGameManagerのStartでCSoundPlayer.LoadAllSounds(“se”)でSEファイルをすべて読み込みます。

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

public class CGameManager : MonoBehaviour
{
~中略~
    void Start()
    {
        for(int i = 0; i < BulletFactory.Length; ++i)
        {
            BulletFactory[i].Load();
        }
        // Soundフォルダからすべての音声ファイルを読み込む
        if (!CSoundPlayer.LoadAllSounds("se"))
        {
            print("seファイル読み込みに失敗しました");
        }
    }
}

鳴らしたい箇所にプログラムを組み込む

まずはプレイヤーのショット音 CSoundPlayer.PlaySound(“cshot”, true);
やられた時の音 CSoundPlayer.PlaySound(“char_death”, true);を追加する。

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

public class CPlayerControl : MonoBehaviour
{
    void Update()
    {
       // Zキー押しっぱなしで6フレームに1度ショットを発射する
        if (0 < InputArray["Fire1"] && InputArray["Fire1"] % 6 == 0)
        {
            CSoundPlayer.PlaySound("cshot", true);
            if (0 < InputArray["Fire3"])//低速移動中なら
            {
                LowerSpeedShot();
            }
            else // 通常移動中なら
            {
                HiSpeedShot();
            }
        }
    }
~中略~
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag == "Bullet")
        {
            CSoundPlayer.PlaySound("char_death", true);
            Destroy(gameObject);
            Destroy(collision.gameObject);
        }
    }
}  

次に敵のショット音 CSoundPlayer.PlaySound(“enemy_shot”, true);の追加。

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

public class CEnemy : MonoBehaviour
{
~中略~
    void Update()
    {
        if (Cnt < 100)
        {
            transform.position += new Vector3(0.0f, -3.0f, 0.0f) * Time.deltaTime;
        }
        if( Cnt == 300)
        {
            CSoundPlayer.PlaySound("enemy_shot", true);
            GameObject.Find("GameManager").GetComponent<CGameManager>().BulletFactory[3].CreateBullet(transform.position, 0);
        }
        if (Cnt > 100 + 240)
        {
            transform.position += new Vector3(0.0f, 3.0f, 0.0f) * Time.deltaTime;
        }
        Cnt++;
        if(CUtility.IsOut(transform.position))
        {
            Destroy(gameObject);
        }
    }
~中略~
}

以上でプレイヤーのショットとやられ音、敵のショット、を追加することができました。