今回は久しぶりにBGMに関することを記載したいと思います
ゲームをプレイしているとき、BGMによってはイントロがあるタイプがあります
今回はイントロを再生したのちループBGMを再生する制御についてみていきたいと思います
コンテンツ
要件と前提条件
今回の要件と前提条件はこうなります
- イントロ音源とループ音源の2つが存在する
- BGMの切り替わりを滑らかにするためにフェードで切り替えたい
- イントロ音源が存在する場合そちらを優先して再生
- BGMを切り替える場合フェードを行う
このような要件になります
実装
実装ですが、イントロ実装はイントロ音源とループ音源が分かれている場合は比較的簡単に実装できます
まずイントロ用とループ用の2つのオーディオを持ちますそしてイントロ即再生し、
ループ用を遅延再生します
遅延実行は公式によると以下で実装できます
引数に遅延時間を要求されますが時間はBGMの以下で取得することができます
これでイントロを実装する材料ができました
↓ということでシンプルに実装するとこうなります
// オーディオ
public AudioSource m_audioIntro;
public AudioSource m_audioLoop;
// BGMデータ
public AudioClip m_intro;
public AudioClip m_loop;
public void Play()
{
m_audioIntro.clip = m_intro;
m_audioLoop.clip = m_loop;
m_audioIntro.Play();
m_audioLoop.PlayDelayed(m_intro.length);
}
これでイントロを再生したと同時にループを遅延実行することができます
BGMをイントロとループに分けたい場合は音源を加工する必要があります
加工方法はこちらを参考にしました
https://www.ay3s-room.com/entry/soundengine-compilation1
※BGMの加工なので切り分ける場合は加工OKなBGMにしましょう
プラスアルファでフェード切り替えを実装する
ここにさらに再生したBGMを切り替えることを考えたいと思います
実装する前に同じBGMかどうか判断する必要があります
そのためBGMをScriptableObjectにまとめてしまいIDを設定できるようにします
↓BGMのデータ定義
using UnityEngine;
/// <summary>
/// BGMデータ
/// </summary>
[CreateAssetMenu(menuName = "Data/Create BgmData")]
public class BgmData : ScriptableObject
{
// ID
[SerializeField]
private string m_id = "";
// イントロBGM
[SerializeField]
private BgmSet m_bgmIntro = null;
// ループBGM
[SerializeField]
private BgmSet m_bgmLoop = null;
public string Id => m_id;
public BgmSet BgmIntro => m_bgmIntro.BGM != null ? m_bgmIntro : m_bgmLoop;
public BgmSet BgmLoop => m_bgmLoop;
}
[System.Serializable]
public class BgmSet
{
// レート
[SerializeField]
private float m_rate = 1.0f;
// BGM
[SerializeField]
private AudioClip m_bgm = null;
public float Rate => m_rate;
public AudioClip BGM => m_bgm;
}
その後、BGM再生機能にこのデータを丸々渡してあげます
そのBGM再生機能がこうなります
↓BGM再生機能
using UniRx;
using UniRx.Triggers;
using UnityEngine;
public class BgmPlayer : MonoBehaviour
{
// ------------------------------------------------
// 定数
private static readonly float MaxRate = 1.0f;
public enum Phase
{
None,
FadeOut,
FadeIn,
}
// ------------------------------------------------
// 設定項目
// イントロ
[SerializeField]
private VolumeChanger m_intro = null;
// ループ
[SerializeField]
private VolumeChanger m_loop = null;
// フェード時間
[SerializeField]
private float m_fadeTime = 0.5f;
// ------------------------------------------------
// メンバ変数
// 再生中のID
private string m_id = "";
// 時間
private float m_time = 0.0f;
// 切り替えフェーズ
private Phase m_phase = Phase.None;
// BGMデータ
private BgmData m_fadingBgmData = null;
private void Awake()
{
this.UpdateAsObservable()
.Where((_) => m_phase != Phase.None && !GameMainManager.Instance.IsStop)
.Subscribe((_) => PlayFade())
.AddTo(this);
}
/// <summary>
/// 再生する
/// </summary>
/// <param name="bgmData">BGMデータ</param>
public void Play(BgmData bgmData)
{
if (string.IsNullOrEmpty(m_id))
{
m_fadingBgmData = bgmData;
ChangeBGM(MaxRate, m_fadingBgmData.Id, m_fadingBgmData);
}
else if (m_id == bgmData.Id)
{
return;
}
else
{
switch (m_phase)
{
case Phase.None:
m_time = 0.0f;
m_phase = Phase.FadeOut;
break;
case Phase.FadeOut:
break;
case Phase.FadeIn:
if (m_id != bgmData.Id)
{
m_phase = Phase.FadeOut;
}
break;
}
m_fadingBgmData = bgmData;
}
}
/// <summary>
/// BGMをフェード再生する
/// </summary>
private void PlayFade()
{
m_time += Time.deltaTime;
var id = m_phase switch
{
Phase.FadeIn => m_fadingBgmData.Id,
Phase.FadeOut or _ => m_id,
};
if (m_time > m_fadeTime)
{
if (m_phase == Phase.FadeOut)
{
m_phase = Phase.FadeIn;
m_time = 0.0f;
}
else if (m_phase == Phase.FadeIn)
{
m_phase = Phase.None;
m_time = m_fadeTime;
}
}
ChangeBGM(m_time / m_fadeTime * MaxRate, id, m_fadingBgmData);
}
/// <summary>
/// BGMを変える
/// </summary>
/// <param name="rate">レート</param>
/// <param name="id">ID</param>
/// <param name="bgmData">BGMデータ</param>
private void ChangeBGM(float rate, string id, BgmData bgmData)
{
if (m_id != id)
{
m_intro.Audio.clip = bgmData.BgmIntro.BGM;
m_loop.Audio.clip = bgmData.BgmLoop.BGM;
m_intro.Audio.Play();
m_loop.Audio.PlayDelayed(bgmData.BgmIntro.BGM.length);
m_id = id;
}
m_intro.Rate = bgmData.BgmIntro.Rate * rate;
m_loop.Rate = bgmData.BgmLoop.Rate * rate;
}
}
VolumeChangerは自作で以前にこちらで作成したものです。
不要であればAudioSourceに変えてしまっても問題ないかと思います
まとめ
ゲームにイントロがあると非常に盛り上がりますがループとは非常に相性が悪いですがこのように実装すればきれいにまとめることができるかと思います
あとは、
- 切り替え時のフェード
- 各種音源の音量がバラバラの場合の調整
- マスタボリュームコンフィグでの調整
と諸々まとまっています
これであればBGMをデータ化する手間がありますが、そこさえクリアすればゲームの
クォリティも1段階上がることでしょう