🎵

【Unity】動的にAudioFilterを適用,無効化したい【C#】

に公開

背景

  • 開発サウンドライブラリでプリセット機能(ロード方法の指定とか)を実装していた
  • 実行時に使用する AudioFilter を予めプリセットで設定できる機能が欲しくなった
  • しかし、そもそも AudioFilter を動的に AudioListener へ適用する機能がなかった
  • 実装するぞ~

どんな風に実装していくのか

  • MonoBehaviour を継承しない

    • ツール系クラスとして 純粋な C# クラス に留める
    • ライブラリ開発の事情もある
    • オブジェクトにアタッチして使いたい方向けに Monobehaviour 継承版アリ↓
  • 必須機能

    • 追加(ApplyFilter)
    • 無効化(DisableFilter)
    • 破棄(RemoveFilter)
  • 依存ライブラリは Unity 標準 API のみ

結果

  • AudioListener に対し AudioFilter(Reverb 等)を動的に追加, 削除できるクラスを実装
Monobehaviour 継承版
using UnityEngine;
using System;
using System.Collections.Generic;

[DisallowMultipleComponent]
[RequireComponent(typeof(AudioListener))]
public class ListenerEffectorMB : MonoBehaviour
{
    //フィルターの種類(AudioChorusFilter 等)を選ぶための列挙型
    public enum FilterKind { Chorus, Distortion, Echo, HighPass, LowPass, Reverb }

    [SerializeField, Tooltip("現在アタッチ済みフィルター(デバッグ表示用)")]
    private List<string> activeFilters = new();

    private readonly Dictionary<Type, Component> filterDict = new();
    private AudioListener listener;

    private void Awake()
    {
        listener = GetComponent<AudioListener>();
    }

    private void OnDisable() => DisableAllFilters(); //シーン停止時、音を切る
    private void OnDestroy() => RemoveAllFilters();

    //ジェネリック型で直接追加 + 設定
    public void ApplyFilter<FilterT>(Action<FilterT> configure = null)
        where FilterT : Behaviour
    {
        if (!filterDict.TryGetValue(typeof(FilterT), out var comp))
        {
            comp = listener.gameObject.AddComponent<FilterT>();
            filterDict[typeof(FilterT)] = comp;
            activeFilters.Add(typeof(FilterT).Name);
        }

        if (comp is FilterT filter)
        {
            filter.enabled = true;
            configure?.Invoke(filter);
        }
    }

    //無効化(指定フィルターのみ)
    public void DisableFilter<FilterT>() where FilterT : Behaviour
    {
        if (filterDict.TryGetValue(typeof(FilterT), out var comp) &&
            comp is FilterT filter)
        {
            filter.enabled = false;
        }
    }

    //無効化(全フィルター)
    public void DisableAllFilters()
    {
        foreach (var comp in filterDict.Values)
            if (comp is Behaviour b) b.enabled = false;
    }

    //破棄(指定フィルターのみ)
    public void RemoveFilter<FilterT>() where FilterT : Behaviour
    {
        if (filterDict.TryGetValue(typeof(FilterT), out var comp))
        {
            filterDict.Remove(typeof(FilterT));
            activeFilters.Remove(typeof(FilterT).Name);
            Destroy(comp);
        }
    }

    //破棄(全フィルター)
    public void RemoveAllFilters()
    {
        foreach (var comp in filterDict.Values)
        {
            if (comp) Destroy(comp);
        }
        filterDict.Clear();
        activeFilters.Clear();
    }

    //FilterKind → 実際のクラス型 に変換するヘルパー関数
    private static Type GetFilterType(FilterKind k) => k switch
    {
        FilterKind.Chorus     => typeof(AudioChorusFilter),
        FilterKind.Distortion => typeof(AudioDistortionFilter),
        FilterKind.Echo       => typeof(AudioEchoFilter),
        FilterKind.HighPass   => typeof(AudioHighPassFilter),
        FilterKind.LowPass    => typeof(AudioLowPassFilter),
        FilterKind.Reverb     => typeof(AudioReverbFilter),
        _                     => null,
    };
}
using UnityEngine;
using System;
using System.Collections.Generic;

public sealed class ListenerEffector
{
    //フィルターの種類(AudioChorusFilter 等)を選ぶための列挙型
    public enum FilterKind { Chorus, Distortion, Echo, HighPass, LowPass, Reverb }

    private AudioListener listener;
    private readonly Dictionary<Type, Component> filterDict = new();

    public ListenerEffector(AudioListener l) { listener = l; }

    //ジェネリック型で直接追加 + 設定
    public void ApplyFilter<FilterT>(Action<FilterT> configure = null)
        where FilterT : Behaviour
    {
        if (!filterDict.TryGetValue(typeof(FilterT), out var component))
        {
            component = listener.gameObject.AddComponent<FilterT>();
            filterDict[typeof(FilterT)] = component;
        }

        if (component is FilterT filter)
        {
            filter.enabled = true;
            configure?.Invoke(filter);
        }
    }

    //無効化(指定フィルターのみ)
    public void DisableFilter<FilterT>() where FilterT : Behaviour
    {
        if (filterDict.TryGetValue(typeof(FilterT), out var component) &&
            component is FilterT filter)
        {
            filter.enabled = false;
        }
    }

    //無効化(全フィルター)
    public void DisableAllFilters()
    {
        foreach (var component in filterDict.Values)
        {
            if (component is Behaviour b)
                b.enabled = false;
        }
    }

    //破棄(指定フィルターのみ)
    public void RemoveFilter<FilterT>() where FilterT : Behaviour
    {
        if (filterDict.TryGetValue(typeof(FilterT), out var component))
        {
            filterDict.Remove(typeof(FilterT));
            UnityEngine.Object.Destroy(component);
        }
    }

    //破棄(全フィルター)
    public void RemoveAllFilters()
    {
        foreach (var component in filterDict.Values)
        {
            if (component != null)
                UnityEngine.Object.Destroy(component);
        }

        filterDict.Clear();
    }

    //FilterKind → 実際のクラス型 に変換するヘルパー関数
    private Type GetFilterType(FilterKind kind)
    {
        return kind switch
        {
            FilterKind.Chorus     => typeof(AudioChorusFilter),
            FilterKind.Distortion => typeof(AudioDistortionFilter),
            FilterKind.Echo       => typeof(AudioEchoFilter),
            FilterKind.HighPass   => typeof(AudioHighPassFilter),
            FilterKind.LowPass    => typeof(AudioLowPassFilter),
            FilterKind.Reverb     => typeof(AudioReverbFilter),
            _                     => null,
        };
    }
}

使い方

//AudioListener 取得
var listener = Camera.main.GetComponent<AudioListener>();

//ユーティリティ生成
var effector = new ListenerEffector(listener);

//Reverb を追加してパラメータ上書き
effector.ApplyFilter<AudioReverbFilter>(f =>
{
    f.reverbLevel = -1000f;
    f.decayTime   = 2.5f;
});

//フィルター無効化
effector.DisableFilter<AudioReverbFilter>();

//フィルター破棄
effector.RemoveFilter<AudioReverbFilter>();

実装のポイント

破棄と無効化の分離

  • DisableFilterBehaviour.enabled = false(状態は保持する)
  • RemoveFilterDestroy(完全に削除し、GC 対象へ)

参考

Discussion