【Unity】サウンドテスト画面を作る - その4

f:id:amayoshitqs:20201012004708p:plain

Unityでサウンドテスト画面を作る第4回です。
箱コンやデュアルショックで遊ぶならサウンドテストだってパッド操作だけで作っちゃいましょう!
そして今回が一旦ラストになります!

今回は「BGM・SEのボリューム調節」の機能を作っていきます。
uGUIのスライダーと、AudioMixerを使用します。

前回はこちら。

amayoshitqs.hatenablog.com

AudioMixerのセットアップ

f:id:amayoshitqs:20201009010342p:plain

ProjectビューでAudioMixerを新規作成します。
「Create -> AudioMixer」 Mixerアセットの名前はご自由にどうぞ。

f:id:amayoshitqs:20201009010519p:plain

アセットをダブルクリックするとAudioMixerウィンドウが表示されます。

f:id:amayoshitqs:20201009010617p:plain f:id:amayoshitqs:20201009010626p:plain

Groupsで最初からあるMasterの下にBGMSEを新規作成します。
「Groups」の右にある+ボタンを押せばOKです。

f:id:amayoshitqs:20201009010823p:plain

続いてExposed Parametersの設定を行います。
ざっくり言うとスクリプトから値を変更できるようにするための設定です。

AudioMixerウィンドウでBGMを選択し、インスペクタのAttenuationVolumeの部分を右クリックして一番上の「Expose ~~~ to script」を選択します。

f:id:amayoshitqs:20201009011158p:plain

次にAudioMixerウィンドウの右上にある「Exposed Parameters」をクリックして「MyExposedParam」を右クリックして「Rename」を選択して分かりやすい名前に変更します。
これをSEでも行ってください。

f:id:amayoshitqs:20201009011406p:plain

今回はBGM_MixerSE_Mixerにしました。

f:id:amayoshitqs:20201009011556p:plain f:id:amayoshitqs:20201009011559p:plain

BGMとSEそれぞれのAudioSourceOutputにAudioMixerをアタッチします。

これでAudioMixerのEditor側での主な作業は完了になります。
初期設定が面倒ですが、1度構築しておくとボリューム設定が非常に楽なのでサウンドテスト関係なく是非やっておきたいですね。
ボリューム以外にも様々な操作が可能ですが、その話はまたいつか。

実装

まずUIを作ります。

f:id:amayoshitqs:20201012002621p:plain

「BGM VOLUME」「SE VOLUME」のメニューを追加しました。

f:id:amayoshitqs:20201012002718p:plain

オブジェクトの構成は今まで隠れていたスライダーを画面上に持ってきているだけで、他のBGMやSE選択のものと変わりません。

各SliderのNavigationの更新も行ってくださいね。

f:id:amayoshitqs:20201012002909p:plain

忘れないうちに前回作ったMenu Listに追加しましょう。
順不同で動作するので順番はご自由にどうぞ。

ではここからスクリプトになります。

using UnityEngine.Audio;

UnityEngine.Audioをusing宣言します。

[SerializeField] Slider bgmVolume;
[SerializeField] Text bgmVolumeValumeText;
[SerializeField] Slider seVolume;
[SerializeField] Text seVolumeValumeText;

メンバ変数を4つ追加します。
役割は名前で分かると思いますが、それぞれのスライダーと音量を表すテキストですね。

次はpublic定義のボリューム調節関数。

public void OnChangeBGMVolume(float value)
{
    audioMixer.SetFloat("BGM_Mixer", -80 + value / 128 * 80);
    bgmVolume.value = value;
    bgmVolumeValumeText.text = value.ToString();
}

public void OnChangeSEVolume(float value)
{
    audioMixer.SetFloat("SE_Mixer", -80 + value / 128 * 80);
    seVolume.value = value;
    seVolumeValumeText.text = value.ToString();
}

それぞれ1行目はAudioMixerのGroupに対して音量を代入。
2行目はこの関数をOnChangeValue以外から飛ばれても機能するように追加したもの。
3行目はテキスト更新です。

AudioMixerのボリュームは「-80dB ~ 20dB」で設定することができるのですが、1dB以上にすると想定以上の出力となり、音が割れる可能性もあるのでオススメしません。
なので今回は「-80dB ~ 0dB」で設定するようにしています。

大体の曲だと-40dB辺りから全然聞こえなくなるのでそこも調整すべきなのですが、色んな音源に対応しようとすると専門的な計算が必要になってくるので今回はこれで妥協します。

最後にStart関数。

void Start()
{
    nowPlayingTitleText.gameObject.SetActive(false);

    bgmLength = Enum.GetNames(typeof(BgmList)).Length;
    bgm.maxValue = bgmLength;
    bgm.minValue = -1;

    seLength = Enum.GetNames(typeof(SeList)).Length;
    se.maxValue = seLength;
    se.minValue = -1;

    bgmVolume.maxValue = 128; // 追加
    bgmVolume.minValue = 0; // 追加

    seVolume.maxValue = 128; // 追加
    seVolume.minValue = 0; // 追加

    OnChangeBGM(0);
    OnChangeSE(0);
    OnChangeBGMVolume(80); // 追加
    OnChangeSEVolume(100); // 追加

    eventSystem.SetSelectedGameObject(bgm.gameObject);
}

各Sliderの最大・最小値の設定。
そしてOnChange~を最初に呼んで初期設定としています。

これでスクリプト編集は以上になります。

f:id:amayoshitqs:20201012003038p:plain

最後に各SliderにOnChangeメソッドを登録しておきます。

完成

f:id:amayoshitqs:20201012004708p:plain

f:id:amayoshitqs:20201012004717p:plain

BGM・SEのボリューム調節ができるようになりました。
AudioMixerウィンドウでもスライダーに合わせてメーターが増減しているのが分かると思います。

余談

基本的な機能を揃えることができました。
背景やエフェクトなど、見た目の部分を度外視すればまぁ問題ない作りになったかと思います。

あとは各音量を保存しておく機能が欲しいですかね。
このシーンから出るときに保存して、また遷移してきた時にロードして初期設定、という感じで。

私自身ゲームのサウンドが大好きですし、拙いですが作曲もしますし、2Dゲームだからと言ってただ音を鳴らすだけなんてことはあり得ません。

そんなエンジニアですので、特にサウンド系は頑張って色々書いていきたいですね!

後日サウンドテスト系の記事をまとめた記事を書くかもです。

今回までのソースコード

SoundManager.cs

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[Serializable]
public class Menu
{
    public Text head;
    public Text body;
    public Slider slider;
    public Image background;
    public Image fill;

    public void ToggleActive(bool isActive)
    {
        head.color = isActive? new Color(0, 0, 0, 1) : new Color(0, 0, 0, 0.5f);
        body.color = isActive? new Color(0, 0, 0, 1) : new Color(0, 0, 0, 0.5f);
        background.color = isActive? new Color(1, 1, 1, 1) : new Color(1, 1, 1, 0.5f);
        fill.color = isActive? new Color(1, 1, 1, 1) : new Color(1, 1, 1, 0.5f);
    }
}

public class SoundManager : MonoBehaviour
{
    [SerializeField] EventSystem eventSystem;
    [SerializeField] Slider bgm;
    [SerializeField] Text bgmNumberText;
    [SerializeField] Slider se;
    [SerializeField] Text seNumberText;
    [SerializeField] Slider bgmVolume;
    [SerializeField] Text bgmVolumeValumeText;
    [SerializeField] Slider seVolume;
    [SerializeField] Text seVolumeValumeText;
    [SerializeField] Slider exit;
    [SerializeField] Text nowPlayingTitleText;
    [SerializeField] AudioSource bgmSource;
    [SerializeField] AudioSource seSource;

    [SerializeField] AudioMixer audioMixer = default;
    [SerializeField] List<Menu> menuList;

    int bgmLength;
    int seLength;
    GameObject go;

    void Start()
    {
        nowPlayingTitleText.gameObject.SetActive(false);

        bgmLength = Enum.GetNames(typeof(BgmList)).Length;
        bgm.maxValue = bgmLength;
        bgm.minValue = -1;

        seLength = Enum.GetNames(typeof(SeList)).Length;
        se.maxValue = seLength;
        se.minValue = -1;

        bgmVolume.maxValue = 128;
        bgmVolume.minValue = 0;

        seVolume.maxValue = 128;
        seVolume.minValue = 0;

        OnChangeBGM(0);
        OnChangeSE(0);
        OnChangeBGMVolume(80);
        OnChangeSEVolume(100);

        eventSystem.SetSelectedGameObject(bgm.gameObject);
    }

void Update()
{
    var current = eventSystem.currentSelectedGameObject;

    if(current != go)
    {
        OnChangeMenu(current);
        go = eventSystem.currentSelectedGameObject;
    }

    if(Input.GetKeyDown(KeyCode.Z))
    {
        if(current == bgm.gameObject)
        {
            BgmList music = (BgmList)bgm.value;
            bgmSource.clip = Resources.Load<AudioClip>("Audio/" + music.ToString());
            bgmSource.Play();
            
            nowPlayingTitleText.gameObject.SetActive(true);
            nowPlayingTitleText.text = SoundConstants.BgmDictionary[music];
        }
        else if(current == se.gameObject)
        {
            SeList sound = (SeList)se.value;
            AudioClip clip = Resources.Load<AudioClip>("Audio/" + sound.ToString());
            seSource.PlayOneShot(clip);
        }
        else if(current == exit.gameObject)
        {
            bgmSource.Stop();
            SceneManager.LoadScene("Title");
        }
    }
}

    void OnChangeMenu(GameObject current)
    {
        foreach(var menu in menuList)
        {
            menu.ToggleActive(current == menu.slider.gameObject);
        }
    }

    public void OnChangeBGM(float value)
    {
        if(value == bgmLength)
        {
            bgm.value = 0;
        }
        if(value == -1)
        {
            bgm.value = bgmLength-1;
        }

        bgmNumberText.text = bgm.value.ToString("00");
    }

    public void OnChangeSE(float value)
    {
        if(value == seLength)
        {
            se.value = 0;
        }
        if(value == -1)
        {
            se.value = seLength-1;
        }

        seNumberText.text = se.value.ToString("00");
    }

    public void OnChangeBGMVolume(float value)
    {
        audioMixer.SetFloat("BGM_Mixer", -80 + value / 128 * 80);
        bgmVolume.value = value;
        bgmVolumeValumeText.text = value.ToString();
    }

    public void OnChangeSEVolume(float value)
    {
        audioMixer.SetFloat("SE_Mixer", -80 + value / 128 * 80);
        seVolume.value = value;
        seVolumeValumeText.text = value.ToString();
    }
}
using System.Collections.Generic;

public enum BgmList
{
    BGM00,
    BGM01,
    BGM02
}

public enum SeList
{
    SE00,
    SE01,
    SE02
}

public static class SoundConstants
{
    public static readonly Dictionary<BgmList, string> BgmDictionary = new Dictionary<BgmList, string>()
    {
        { BgmList.BGM00, "Queen" },
        { BgmList.BGM01, "炎のロックンロール" },
        { BgmList.BGM02, "Keep Yourself Alive" }
    };
}