プレイするゲームがないなら、自分で作ればいいじゃない!
ということで Unity 初心者が「ゲームを作る」ゲームをやっていきます!
今回は「2Dサウンド(BGM、SE)対応」の記事です
内容
「BGM、SE」を再生するクラス【SoundManager】の作成
音を鳴らすには
音に必要なのは「耳と音源」になります
Audio Listener
Unity での耳、Main Camera に始めから追加されている
2Dゲームの場合はこのままで問題ない
Audio Source
Unity での音源、音を鳴らしたいオブジェクトに追加する
今回はサウンドマネージャーのスクリプトから追加します
Audio Clip
音のデータ、これを AudioSource で再生する
BGM等のサイズの大きいデータは「ロードタイプをストリーミング」にしておくといい
BGMとSEのスクリプト
別に分けなくてもいいかもしれないが…
SE用
AudioSource を設定して、再生するだけのシンプルなもの
public class SeSound
{
AudioSource source = null;
// オーディオソースの設定
public void SetAudioSource(AudioSource _source)
{
source = _source;
}
// 再生
public void Play(AudioClip _clip, float _volume)
{
source.PlayOneShot(_clip, _volume);
}
}
AudioSource.PlayOneShot(AudioClip, float)
音データと音量(0~1)を設定する
※音量の部分は省略可能(その場合は1になる)
重複して再生ができる:SE1を再生中にSE2を再生してもSE1が消えない
BGM用
SEの処理に停止関係とフェードの処理を追加している
public class BgmSound
{
// BGM状態
private enum eBgmState
{
BGM_STOP = 0,
BGM_PLAY,
BGM_FADEIN,
BGM_FADEOUT,
};
AudioSource source = null; // オーディオソース
float fade = 0; // フェード処理用
float fadeTime = 0; // フェード時間
float volume = 0; // ボリューム
bool isPause = false; // 一時停止中か
eBgmState state = eBgmState.BGM_STOP; // サウンドの状態
// 更新
public void Update()
{
if (isPause) { return; }
// フェードイン
if(state == eBgmState.BGM_FADEIN)
{
fade += Time.deltaTime;
if(fade >= fadeTime)
{
state = eBgmState.BGM_PLAY;
SetVolume(volume);
}
else
{
source.volume = Mathf.Lerp(0, volume, fade / fadeTime);
}
}
// フェードアウト
else if (state == eBgmState.BGM_FADEOUT)
{
fade += Time.deltaTime;
if (fade >= fadeTime)
{
Stop();
}
else
{
source.volume = Mathf.Lerp(volume, 0, fade / fadeTime);
}
}
}
// オーディオソースの設定
public void SetAudioSource(AudioSource _source)
{
source = _source;
}
// 再生中か
public bool IsPlaying()
{
// 一時停止も判定に入れる?
//if (isPause)
//{
// return false;
//}
return source.isPlaying;
}
// 再生
public void Play(AudioClip _clip, float _volume, float _fade, bool _loop)
{
fade = 0;
fadeTime = _fade;
volume = _volume;
isPause = false;
source.clip = _clip;
source.volume = 0;
source.loop = _loop;
source.Play();
state = eBgmState.BGM_FADEIN;
}
// フェードアウト
public void FadeOut(float _fade)
{
fade = 0;
fadeTime = _fade;
state = eBgmState.BGM_FADEOUT;
}
// 一時停止
public void Pause()
{
isPause = true;
source.Pause();
}
// 一時停止解除
public void UnPause()
{
isPause = false;
source.UnPause();
}
// 停止
public void Stop()
{
source.Stop();
state = eBgmState.BGM_STOP;
}
// 音量設定
public void SetVolume(float _volume)
{
if (state != eBgmState.BGM_PLAY)
{
volume = _volume;
return;
}
// 再生中の音は設定する
source.volume = _volume;
}
}
AudioSource.Play(ulong)
ディレイタイムを設定する
※ディレイタイムの部分は省略可能(その場合は0になり、即再生される)
重複して再生されない:BGM1を再生中にBGM2を再生するとBMG1が消える
SoundManager のスクリプト
BGMとSEのクラスを管理するクラス
まだ単純な操作しかできない、適宜欲しい機能を追加する
※ LikeDictionary は後述
public class SoundManager : MonoBehaviour
{
public static SoundManager Instance { get; private set; } = null; // シングルトン
[SerializeField] LikeDictionary<string, AudioClip> soundDictionary; // サウンドデータ
BgmSound[] bgmTbl = new BgmSound[2]; // BGM
SeSound se; // SE
List<string> nameList = new List<string>(); // SE用の再生リスト
float bgmVolume = 1.0f; // BGMのボリューム
float seVolume = 1.0f; // SEMのボリューム
void Awake()
{
if (Instance == null)
{
// インスタンスがない場合は代入
Instance = this;
// シーン切り替え時に消さないようにする
DontDestroyOnLoad(this.gameObject);
}
else
{
// ある場合は2つ目なのでゲームオブジェクトを消す
Destroy(this.gameObject);
return;
}
// LikeDictionaryの初期化
soundDictionary.Init();
// オーディオソースの設定
for (int i=0; i<bgmTbl.Length; ++i)
{
bgmTbl[i] = new BgmSound();
bgmTbl[i].SetAudioSource(gameObject.AddComponent<AudioSource>());
}
{
se = new SeSound();
se.SetAudioSource(gameObject.AddComponent<AudioSource>());
}
nameList.Clear();
}
void LateUpdate()
{
// BGMの更新
foreach (BgmSound _bgm in bgmTbl)
{
_bgm.Update();
}
// 再生があれば再生
foreach (string _name in nameList)
{
Play(_name);
}
// 再生リストを空に
nameList.Clear();
}
// BGM再生
public void PlayBgm(string _name, float _fade = 0, bool _loop = true)
{
foreach (BgmSound _bgm in bgmTbl)
{
if (!_bgm.IsPlaying())
{
_bgm.Play(soundDictionary.Value(_name), bgmVolume, _fade, _loop);
break;
}
}
}
// BGM切り替え
public void ChangeBgm(string _name, float _fade = 0, bool _loop = true)
{
// 今鳴っているものをフェードアウト
foreach (BgmSound _bgm in bgmTbl)
{
if (_bgm.IsPlaying())
{
_bgm.FadeOut(_fade);
break;
}
}
// 再生
PlayBgm(_name, _fade, _loop);
}
// SE再生(予約)
public void PlaySe(string _name)
{
// 同じフレームに同じSEは再生しないようにする
if(nameList.Exists(n => n == _name)){
return;
}
nameList.Add(_name);
}
// BGMの一時停止
public void PauseBgm()
{
foreach (BgmSound _sound in bgmTbl)
{
_sound.Pause();
}
}
// BGMの一時停止を解除
public void UnPauseBgm()
{
foreach (BgmSound _sound in bgmTbl)
{
_sound.UnPause();
}
}
// BGMの音量を設定
public void SetBgmVolume(float _volume)
{
bgmVolume = Mathf.Clamp(_volume, 0, 1.0f);
foreach (BgmSound _sound in bgmTbl)
{
_sound.SetVolume(bgmVolume);
}
}
// SEの音量を設定
public void SetSeVolume(float _volume)
{
seVolume = Mathf.Clamp(_volume, 0, 1.0f);
}
// SE再生
void Play(string _name)
{
se.Play(soundDictionary.Value(_name), seVolume);
}
}
1フレームで同じ SE を鳴らさないように nameList で制御している
void LateUpdate()
Update処理の最後で呼び出される
BGM の更新(フェード関係)と List にある SE の再生を行う
LikeDictionary のスクリプト
サウンド情報をインスペクタービューで設定したかったので、色々試した結果
Dictionary を使って名前から AudioClip を参照する形になりました
Dictionary だと…
しかし Unity の仕様で Dictionary はインスペクタービューに表示されません!
ということで色々調べて「エディタ拡張」など様々なやり方を見た結果…
List で設定したものを Dictionary に移すという形になりました
LikeDictionary.cs
値しか取れない、他の機能は適宜追加する
[Serializable]
public class LikeDictionary<Tkey, Tvalue>
{
[Serializable]
private struct DictionaryData
{
public Tkey key;
public Tvalue value;
}
[SerializeField] List<DictionaryData> list; // エディター用のリスト
Dictionary<Tkey, Tvalue> dictionary; // サウンドデータ
// list ⇒ dictionary にデータを移す
public void Init()
{
dictionary = new Dictionary<Tkey, Tvalue>();
while (list.Count > 0)
{
dictionary.Add(list[0].key, list[0].value);
list.RemoveAt(0);
}
}
// 値を取得
public Tvalue Value(Tkey _key)
{
return dictionary[_key];
}
}
Init を呼ぶと List ⇒ Dictionary にデータを移す
処理的に設定数が増えてくると重くなりそうですが
求めすぎると先に進めなくなるので、重くなった時に考えることにしましょう
再生してみる
空のオブジェクトを作成⇒「SoundManager のスクリプト」を追加します
※そのオブジェクトをプレハブにしておく
インスペクタービューで「再生用の名前とサウンドデータ」を設定する
あとは音を再生したい箇所にスクリプトを書いていきましょう
// BGM再生の例
SoundManager.Instance.ChangeBgm("bgm_title", 0.2f);
// SE再生の例
SoundManager.Instance.PlaySe("cursor_move");
サンプル動画
さいごに
音を鳴ると一気に雰囲気が変わりますね!
今回のものは簡単な設定しかしていませんが
Audio~ は他にも色々な設定が可能なので、確認してみるといいかもしれません
コメント