【Unity】距離によってBGMをクロスフェードさせる

投稿者: | 2021-01-26

FPSプレイヤーからターゲットまでの距離に応じてBGMをクロスフェードさせてみました。

クロスフェードさせるときだけ距離を計算したいので、そのためのオブジェクトを作ってプレハブ化しました。

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

public class DistanceChecker : MonoBehaviour
{
    List<Transform> targets;
    Transform center;
    AudioManager audioManager;

    // 中心とターゲット、音管理オブジェクトを設定
    public void SetUp(Transform center, List<Transform> targets, AudioManager audioManager)
    {
        this.center = center;
        this.targets = targets;
        this.audioManager = audioManager;
    }

    public void SetUp(Transform center, Transform target, AudioManager audioManager)
    {
        this.center = center;
        targets.Clear();
        targets.Add(target);
        this.audioManager = audioManager;
    }

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        // 初めのターゲットの距離
        float distance = Vector3.Distance(center.position, targets[0].position);

        for (int i = 1; i < targets.Count; i++)
        {
            // 次のターゲットの距離
            float d = Vector3.Distance(center.position, targets[i].position);

            // 小さい方を保存
            distance = distance > d ? d : distance;
        }

        // 音管理オブジェクトのメソッドを呼ぶ
        audioManager.SetCrossFadeRatio(distance);
    }
}

スクリプトでは、距離を計算する対象のオブジェクトとオーディオ管理のオブジェクトを設定し、Updateメソッドで距離を計算して、管理オブジェクトのメソッドを呼ぶことで距離を知らせます。

計算するのは中心からターゲットまでの距離です。中心はプレイヤーにして、複数のターゲットを設定できるようにしました。ターゲットは変更できるようにしました。

オーディオ管理のスクリプトでは、クロスフェードさせたいときにこれをインスタンス化します。オブジェクトにはスクリプトの他に2つのオーディオソースが付いていて、距離の値によってボリュームを変更します。

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

public class AudioManager : MonoBehaviour
{
    [SerializeField] AudioClip bgm1;
    [SerializeField] AudioClip bgm2;

    [SerializeField, Range(0f, 1f)] float bgmVolume = 0.5f;
    [SerializeField, Range(0f, 1f)] float maxBgmVolume = 0.5f;

    AudioSource bgmAudioSource1;
    AudioSource bgmAudioSource2;
    
    // 距離を計算するゲームオブジェクト
    [SerializeField] DistanceChecker distanceCheckerPrefab;
    DistanceChecker distanceChecker;

    static AudioManager instance;
    public static AudioManager GetInstance()
    {
        return instance;
    }

    // Start is called before the first frame update
    void Start()
    {
        instance = this;

        bgmAudioSource1 = GetComponents<AudioSource>()[0];
        bgmAudioSource2 = GetComponents<AudioSource>()[1];
    }

    // クロスフェードを開始
    public void StartCrossFade(Transform center, List<Transform> targets)
    {
        // 距離計算オブジェクトがなければインスタンス化
        if (distanceChecker == null) distanceChecker = Instantiate(distanceCheckerPrefab);
        
        // 設定
        distanceChecker.SetUp(center, targets, this);

        // BGMを再生
        SetUpBgmAudioSource(bgm1, bgm2);
    }

    // クロスフェードのボリュームを変更
    public void SetCrossFadeRatio(float distance)
    {
        float crossFadeRatio = Mathf.Clamp(Mathf.Pow(2f / distance, 2f), 0f, 1f);

        // ボリュームを設定
        bgmAudioSource1.volume = (1 - crossFadeRatio) * bgmVolume;
        bgmAudioSource2.volume = crossFadeRatio * bgmVolume;
    }

    // 2つのオーディオソースでBGMを再生
    void SetUpBgmAudioSource(AudioClip bgm1, AudioClip bgm2)
    {
        bgmAudioSource1.clip = bgm1;
        bgmAudioSource1.loop = true;
        bgmAudioSource1.Play();

        bgmAudioSource2.clip = bgm2;
        bgmAudioSource2.loop = true;
        bgmAudioSource2.Play();
    }

    // クロスフェードを終了
    public void StopCrossFade()
    {
        Destroy(distanceChecker.gameObject);
    }

    WaitForSeconds wait = new WaitForSeconds(0.1f);

    // フェードイン
    public void StartFadeInBgm()
    {
        StopAllCoroutines();
        bgmVolume = 0f;
        StartCoroutine("FadeInBgm");
    }

    IEnumerator FadeInBgm()
    {
        while (true)
        {
            yield return wait;
            bgmVolume += 0.01f;

            if (bgmVolume >= maxBgmVolume)
            {
                bgmVolume = maxBgmVolume;
                yield break;
            }
        }
    }

    // フェードアウト
    public void StartFadeOutBgm()
    {
        StopAllCoroutines();
        StartCoroutine("FadeOutBgm");
    }

    IEnumerator FadeOutBgm()
    {
        while (true)
        {
            yield return wait;
            bgmVolume -= 0.01f;

            if (bgmVolume <= 0f)
            {
                bgmVolume = 0f;

                yield break;
            }
        }
    }
}

距離を計算するオブジェクトから渡された距離で、ある値を割ることで、距離がその値以下のときは1以上、それより遠いときは値が小さくなります。それを2乗することで、近づくと急激にボリュームが上がるようにしてみました。最後に0~1の範囲に固定しています。

float crossFadeRatio = Mathf.Clamp(Mathf.Pow(2f / distance, 2f), 0f, 1f);

そして、2つのオーディオソースのボリュームの一方をこの値、もう一方を1から引いた値にしました。この時に両方とも0~1の値をかけて、この値を増減させることで、クロスフェードさせながら全体の音量を変えることができます。

bgmAudioSource1.volume = (1 - crossFadeRatio) * bgmVolume;
bgmAudioSource2.volume = crossFadeRatio * bgmVolume;

そこで、コルーチンを使って全体の音量を増減させてフェードイン/アウトさせてみました。

これで、距離によってクロスフェードさせながら全体の音量を変えられました。距離を計算するオブジェクトがオーディオ管理のメソッドを呼んでボリュームを変えているだけなので、クロスフェードを終了するときは距離を計算するオブジェクトを破壊します。

public void StopCrossFade()
{
    Destroy(distanceChecker.gameObject);
}

今回はオーディオ管理のクロスフェードのメソッドを呼ぶ時に、ターゲットのオブジェクトをタグで検索してリストを作っています。

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

public class Player3 : MonoBehaviour
{
    [SerializeField] Text text;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            if (Input.GetKeyDown(KeyCode.Alpha1))
            {
                // タグでオブジェクトを検索
                GameObject[] obj = GameObject.FindGameObjectsWithTag("Target");

                // ターゲットのリスト
                List<Transform> targets = new List<Transform>();

                // Transformをリストに追加
                for (int i = 0; i < obj.Length; i++)
                {
                    targets.Add(obj[i].transform);
                }

                // クロスフェードを開始
                AudioManager.GetInstance().StartCrossFade(transform, targets);

                text.text = "クロスフェード";
            }
            else if (Input.GetKeyDown(KeyCode.Alpha2))
            {
                // ターゲットのリスト
                List<Transform> targets = new List<Transform>();

                // 別のタグで検索してリストに追加
                targets.Add(GameObject.FindGameObjectWithTag("Target2").transform);

                // クロスフェードを開始
                AudioManager.GetInstance().StartCrossFade(transform, targets);

                text.text = "ターゲット変更";

            }
            else if (Input.GetKeyDown(KeyCode.Alpha3))
            {
                AudioManager.GetInstance().StartFadeOutBgm();
                text.text = "フェードアウト";

            }

            else if (Input.GetKeyDown(KeyCode.Alpha4))
            {
                AudioManager.GetInstance().StartFadeInBgm();
                text.text = "フェードイン";
            }
            else if (Input.GetKeyDown(KeyCode.Alpha5))
            {
                AudioManager.GetInstance().StopCrossFade();
                text.text = "クロスフェード終了";
            }
        }
    }
}

コメントを残す

メールアドレスが公開されることはありません。