🚀

音を極めろ!オーディオエフェクト

2024/11/08に公開

※この記事は2024年11月8日のUnity⚪︎⚪︎完全理解したイベント、Unityエフェクト完全理解したの登壇資料です。

AudioMixerを使う

AudioMixerは、視覚的な操作インターフェースを通じて、音響経験が少ないプログラマーにも音声ミキシングやエフェクト処理を直感的に行えるようにしたツールです。音響設定のプロフェッショナルな管理ができることで、ゲームにおける豊かなサウンド体験を提供でき、プレイヤーの没入感が向上します。

AudioMixerとは?

UnityのAudioMixerは、音声を効果的に管理・制御するためのツールです。
これは、複数の音源(BGM、効果音、ボイスなど)をまとめてミキシングし、音量やエフェクトの調整を視覚的に行える機能を提供します。実際のオーディオミキサーを模したインターフェースで、音声の流れやエフェクトの適用を直感的に理解しやすくなっており、特に音響経験のないプログラマーにも扱いやすい設計になっています。

AudioMixerの特徴と利点

  1. 視覚的な操作が可能

    • 音源のグループ化やエフェクトの適用を、視覚的に確認しながら操作できます。
    • ドラッグ&ドロップやスライダーで音量やエフェクトのパラメータをリアルタイムに調整し、変更結果を即座に確認可能です。
  2. エフェクトの適用とスナップショット機能

    • 音源にリバーブ、エコーなどのエフェクトを追加でき、シーンや状況に応じて音響の雰囲気をダイナミックに変更できます。
    • スナップショット機能により、ミキサーの設定状態を保存・切り替えることができ、場面に応じた音響効果の演出が容易に行えます。
  3. プログラマーにとっての操作性

    • プログラマーが実際のミキサーに不慣れでも、音声の流れを視覚的に把握できるため、音響処理の基礎を理解しやすくなります。
    • スクリプトから音量やエフェクトの強度を動的に制御することができ、リアルタイムでの音声操作が可能です。
  4. プロフェッショナルな音響体験の提供

    • 音量バランスやエフェクトを一貫して管理でき、ゲーム全体のオーディオ品質を向上させます。
    • サウンドデザイナーとプログラマーが同じツールを使用することで、コミュニケーションが円滑になり、音響面での品質向上が期待できます。

AudioSourceのエフェクト

AudioSourceで使用する各フィルターやリバーブのコンポーネントと、AudioMixerで使用するエフェクトにはいくつかの違いがあります。
主に、適用範囲、目的、管理方法の面で異なります。

  • AudioSourceのエフェクトは、特定のサウンドに対する個別のエフェクトに向いており、より細かいカスタマイズが必要な場合に使われます。
  • AudioMixerのエフェクトは、複数のサウンドをまとめて管理・調整するのに向いており、ゲームの状況やシーンに応じた統一された音響設定を簡単に行えます。

用途に応じて、AudioSourceとAudioMixerのエフェクトを使い分けることで、効率的に音響演出を管理できます。

1. 適用範囲の違い

  • AudioSourceのエフェクト

    • 個々のオーディオソースに対してフィルターやリバーブを適用します。
    • 例えば、特定のキャラクターの声や特定のサウンドエフェクトにのみリバーブやフィルターをかける場合に便利です。
  • AudioMixerのエフェクト

    • AudioMixerグループ単位で適用されるため、複数のAudioSourceにまとめてエフェクトをかけることができます。
    • BGMや環境音、効果音などのカテゴリーごとにエフェクトを適用したいときに便利です。

2. 目的の違い

  • AudioSourceのエフェクト

    • 個別のサウンドに対する特定のエフェクト調整が目的です。キャラクターが特定の位置や状態にいる場合にだけエフェクトをかけるなど、特定のサウンドに限定して設定を細かく調整できます。
  • AudioMixerのエフェクト

    • シーン全体やカテゴリ単位での統一されたエフェクト調整が目的です。例えば、複数の効果音グループにまとめてフィルターをかけ、シーン全体の効果音を一括して制御することができます。ゲームのシーンやシチュエーションに応じて、音響の雰囲気を統一する際に適しています。

3. 管理方法の違い

  • AudioSourceのエフェクトの管理

    • 各AudioSourceに直接コンポーネントとして追加されます(例:Audio Low Pass FilterAudio High Pass FilterAudio Reverb Filter)。
    • コンポーネントを直接AudioSourceに加えるため、エフェクト設定がそのAudioSource専用となり、個別に管理されます。
    • AudioSourceの設定変更はスクリプトでも可能ですが、他のサウンドと統一された操作は難しく、複数のサウンドに同時に変更を加えたい場合には手間がかかります。
  • AudioMixerのエフェクトの管理

    • AudioMixerグループにエフェクトを追加し、AudioMixerウィンドウで調整します。AudioMixerにはスナップショット機能もあり、設定の切り替えも容易です。
    • 複数のAudioSourceが同じAudioMixerグループにルーティングされている場合、グループ全体にエフェクトをかけられるため、統一された音響設定を適用できます。
    • プログラムからもエクスポーズパラメータを通じてエフェクトを一括制御できるため、シーンの変化に応じてエフェクトを柔軟に管理できます。

4. 適用可能なエフェクトの種類

  • AudioSourceのフィルター

    • 主にAudio Low Pass FilterAudio High Pass FilterAudio Reverb Filterなどのシンプルなフィルターが用意されています。これらのフィルターは特定のサウンドだけに簡易的な効果を付けるためのものです。
  • AudioMixerのエフェクト

    • AudioMixerでは、リバーブやフィルターに加え、コンプレッサーイコライザーディレイなど、より幅広いエフェクトを使用できます。これにより、複雑な音響効果の調整や、サウンドグループ全体の処理が可能になります。

なので、個別の音源AudioClipをMixしたい場合はAudioSource単位で、全体のトーナリティーバランスを触りたい場合はAudioMixerといった具合です。

スナップショットという機能

AudioMixerの機能であるスナップショットは、各グループの音量・ピッチ調整、エフェクトのパラメータ設定、ルーティング状態の保存・呼び出しに最適な機能です。
ギターのエフェクターペダルをイメージしてもらえるとわかりやすいかと。

しかし、エフェクトの追加・削除やグループ構成の変更、エフェクトの適用順序の変更など、構造に関わる部分の操作はスナップショットでは扱えません。

スナップショットでできること

  1. 音量やピッチの調整

    • 各グループ(バス)ごとの音量やピッチの設定を保存して、必要に応じてスナップショットで切り替えることが可能です。
    • 例えば、戦闘シーンではBGMの音量を上げ、日常シーンでは控えめにするといった調整ができます。
  2. エフェクトのパラメータ設定

    • リバーブ、フィルター、イコライザーなど、各エフェクトの強度や周波数の数値設定をスナップショットに保存できます。
    • これにより、洞窟内ではリバーブを強めて反響音を再現し、屋外ではエフェクトを控えめにして自然な音響にする、といった環境に応じた音響の変化が可能です。
  3. グループごとのルーティング状態の管理

    • スナップショットを通じて、どの音源がどのグループに属し、どのグループがどのようにルーティングされているかを保存しておけます。
    • シーンによってルーティングを変更し、特定の音声のみを強調するといった設定を活用できます。
  4. エフェクトのオン・オフの切り替え

    • 各エフェクトを有効化・無効化する状態もスナップショットに保存されます。
    • 例えば、緊張感を演出する場面ではフィルターをオンにし、普段はオフにすることで、音声の質感をシーンごとに変えることができます。

スナップショットでできないこと

  1. エフェクトそのものの追加・削除

    • スナップショットでエフェクトのパラメータやオン・オフは変更できますが、エフェクト自体を追加したり削除したりすることはできません。エフェクトの構成を変更するには、AudioMixer自体を調整する必要があります。
    • 例:新しいリバーブエフェクトを追加するには、スナップショットではなくAudioMixerの設定自体を変更する必要があります。
  2. 新しいグループ(バス)の追加・削除

    • スナップショットではグループ構造自体を変えることはできません。あらかじめ必要なグループ構成を作成し、その中で音量やエフェクトのパラメータを設定・保存しておく必要があります。
    • 例:新しいサウンドグループを追加したい場合は、ミキサーの編集が必要です。
  3. エフェクトの順序の変更

    • スナップショットはエフェクトの適用順序を変更することができません。エフェクトの順序を変えたい場合は、最初にAudioMixer上でエフェクト順を設定しておく必要があります。
    • 例:フィルターをリバーブの後に適用したい場合、スナップショットでは対応できないため、ミキサー内でのエフェクト順を手動で変更する必要があります。

エフェクター

1. ハイパスフィルター (High-Pass Filter)

ハイパスフィルターは、低い周波数の音をカットし、高い周波数の音だけを通すフィルターです。特定の音の「こもり感」を減らしたり、環境音に明瞭感を与えたりするために使用されます。

  • 使い方のポイント
    • カットオフ周波数:カットオフ周波数を設定し、その周波数よりも低い音をカットします。カットオフ周波数を上げるとより多くの低音がカットされ、スッキリした印象の音になります。
    • 適用シーン例:車内や狭い空間の音響効果を演出する際、音声の低音をカットすることでクリアな音声表現を実現できます。

2. ローパスフィルター (Low-Pass Filter)

ローパスフィルターは、逆に高い周波数の音をカットし、低い周波数の音だけを通すフィルターです。これにより、音が「こもった」感じになり、距離感や障害物越しの音響を表現するのに便利です。

  • 使い方のポイント
    • カットオフ周波数:こちらもカットオフ周波数を設定し、その周波数よりも高い音をカットします。カットオフ周波数を下げることで、より高音成分がカットされ、音がよりこもった感じに聞こえます。
    • 適用シーン例:壁越しや水中での音響、遠くの物音など、明瞭さを落としたい状況で使います。

3. リバーブ (Reverb)

リバーブは、音に残響を加えるエフェクトで、空間の広さや反響具合を表現するために使います。リバーブを使うことで、音が室内で反響しているような効果や、広大な場所での余韻を演出することができます。

  • 使い方のポイント
    • ルームサイズ:空間の広さを設定します。広い部屋や大聖堂のような空間を表現したい場合は、ルームサイズを大きく設定します。
    • デシケイ(音の減衰時間):反響音が消えるまでの時間を設定します。長くすることで音が持続し、残響が豊かな印象になります。
    • 適用シーン例:洞窟やホールの音響、広い空間での演出などに使います。

エフェクトのパラメーター(変数)

フィルターとリバーブの値

レゾナンス(または「Q値」)は、ハイパスフィルターやローパスフィルターにおける特定の周波数の強調を表すパラメータです。これにより、カットオフ周波数付近の音が強調され、エッジの効いたサウンド効果を作り出すことができます。
レゾナンスは、フィルターで音を単にカットするだけでなく、カットオフ周波数付近にピークを持たせ、音に独特のキャラクターを加えるためのパラメータです。

レゾナンスの役割と使い方

  • カットオフ周波数の強調:レゾナンスを上げると、カットオフ周波数の周辺音が強調され、独特のピークが発生します。この強調効果は、特に電子音やフィルターをかけたボイスなど、際立たせたいサウンドで有用です。

  • 高レゾナンスの効果:高めのレゾナンスを設定すると、カットオフ周波数の周辺が鋭く強調され、より尖った音や「鳴り」を感じさせる音になります。この効果は、シンセサイザーのフィルター操作でよく使われ、音に存在感を与えたい場合に便利です。

  • 低レゾナンスの効果:レゾナンスを低くすると、周波数の強調が減り、カットオフ周波数で音がスムーズに減衰していきます。より自然な音のカットにしたい場合や、控えめなフィルター効果を狙いたい場合に適しています。

実用例

  • ローパスフィルターでの使用

    • 高い周波数のカットに加え、カットオフ周波数付近にレゾナンスを加えると、こもった音の中に特徴的なピークが生まれ、独特なムードやエフェクトを作り出せます。
    • 例:キャラクターが遠くから話しかける声にローパスフィルターをかけ、少しレゾナンスを加えて特徴的なこもり音を演出する。
  • ハイパスフィルターでの使用

    • 低音をカットしつつレゾナンスを上げることで、特定の高音成分が際立つシャープなサウンドにできます。
    • 例:背景音としてかすかに聞こえる風の音にハイパスフィルターをかけ、レゾナンスを少し加えることで風切り音を強調する。

以下に、AudioMixerManagerAudioSnapshotEnumGenerator、およびそれらの使用方法と仕組みを含むドキュメントをまとめました。このドキュメントは、各クラスの目的と設定方法、主な機能を分かりやすく説明することを目的としています。

スナップショット切り替えコード

このドキュメントは、UnityのAudioMixerのスナップショット管理を効率化するためのシングルトンパターンに基づいたAudioMixerManagerクラス、スナップショット名をenumとして自動生成するAudioSnapshotEnumGenerator、およびテストとデバッグ用のAudioMixerTesterクラスについて説明します。

  • AudioMixerManager : シングルトンとしてAudioMixerのスナップショットを一元管理し、簡単に切り替えが可能。
  • AudioSnapshotEnumGenerator : AudioMixerSnapshotの名前を列挙型(enum)として自動生成し、コードでのスナップショット指定を効率化。
  • AudioMixerTester : 現在のスナップショットの状態を画面上で確認し、デバッグのために手動でスナップショットを切り替えるインターフェースを提供。

クラス一覧

  1. AudioMixerManager: シングルトンパターンを使用し、AudioMixerのスナップショットを簡単に切り替えられるマネージャークラス。
  2. AudioSnapshotEnumGenerator: プロジェクト内のAudioMixerSnapshotを列挙型(enum)として自動生成するエディタ拡張。
  3. AudioMixerTester: 現在アクティブなスナップショットを画面上で可視化し、簡単に切り替えをテストできるデバッグ用クラス。

1. AudioMixerManager

クラスの目的

AudioMixerManagerは、プロジェクト内のAudioMixerのスナップショットを簡単に管理し、シングルトンパターンを用いてどこからでもスナップショットの切り替えを行えるようにするクラスです。シーン内のAudioMixerManagerを1つ配置して使用します。

主な機能

  • シングルトンパターン:どのスクリプトからでも簡単にアクセス可能。
  • スナップショットのキャッシュAudioSnapshots enumに基づいて、キャッシュを構築し、スナップショットの高速なアクセスを実現。
  • 起動時のデフォルトスナップショットの適用:デフォルトスナップショットとフェード時間を指定し、シーン起動時に適用するかを制御。

プロパティとメソッド

プロパティ

  • Instance : AudioMixerManagerのシングルトンインスタンス。
  • audioMixers : シーン内で利用するAudioMixerの配列。
  • defaultSnapshot : デフォルトで適用するスナップショットをAudioSnapshots enumで指定。
  • useDefaultSnapshotOnStart : シーン起動時にデフォルトスナップショットを適用するかを指定。
  • defaultTransitionTime : スナップショットの切り替え時に使用するデフォルトのフェード時間(秒単位)。

メソッド

  • SwitchSnapshot(AudioSnapshots snapshotEnum, float transitionTime = -1f) : 指定したAudioSnapshotsを切り替える静的メソッド。transitionTimeが指定されない場合はdefaultTransitionTimeが使用されます。

使用方法

  1. シーン内に空のGameObjectを作成し、AudioMixerManagerスクリプトをアタッチします。
  2. インスペクタでaudioMixersに使用するAudioMixerを追加し、デフォルトのスナップショットや起動時の適用フラグなどを設定します。
  3. 他のスクリプトからAudioMixerManager.SwitchSnapshot(AudioSnapshots.Mixer1_SnapshotA)のように呼び出してスナップショットを切り替えます。

AudioMixerManager.cs

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

namespace dsgarage.Audio
{
    /// <summary>
    /// AudioMixerManager は、シングルトンとして AudioMixer のスナップショットを一元管理し、
    /// どこからでも簡単にスナップショットを切り替えることができるマネージャークラスです。
    /// </summary>
    public class AudioMixerManager : MonoBehaviour
    {
        // シングルトンインスタンス
        public static AudioMixerManager Instance { get; private set; }

        // AudioMixerとスナップショットのキャッシュ
        private Dictionary<string, AudioMixerSnapshot> snapshotCache = new Dictionary<string, AudioMixerSnapshot>();

        // デフォルトのスナップショットと起動時の適用フラグ
        public AudioSnapshots defaultSnapshot;
        public bool useDefaultSnapshotOnStart = true;

        // 遷移時間(フェード時間)
        [SerializeField] private float defaultTransitionTime = 1.0f;

        private void Awake()
        {
            // シングルトンのインスタンスを設定
            if (Instance == null)
            {
                Instance = this;
                DontDestroyOnLoad(gameObject); // シーン間でオブジェクトを保持
            }
            else
            {
                Destroy(gameObject); // 重複するインスタンスがある場合は削除
                return;
            }

            // 起動時にシーン内のAudioMixerを検索してキャッシュを構築
            BuildSnapshotCache();

            // 起動時にデフォルトのスナップショットを適用するかどうかを確認
            if (useDefaultSnapshotOnStart)
            {
                SwitchSnapshot(defaultSnapshot, defaultTransitionTime);
            }
        }

        /// <summary>
        /// シーン内の AudioMixer を検索し、スナップショットキャッシュを構築します。
        /// </summary>
        private void BuildSnapshotCache()
        {
            snapshotCache.Clear();

            // シーン内のすべての AudioMixer を検索
            AudioMixer[] mixersInScene = Resources.FindObjectsOfTypeAll<AudioMixer>();

            foreach (AudioMixer mixer in mixersInScene)
            {
                foreach (AudioSnapshots snapshot in System.Enum.GetValues(typeof(AudioSnapshots)))
                {
                    string snapshotName = snapshot.ToString();
                    string[] parts = snapshotName.Split('_');

                    if (parts.Length == 2 && parts[0] == mixer.name)
                    {
                        AudioMixerSnapshot mixerSnapshot = mixer.FindSnapshot(parts[1]);
                        if (mixerSnapshot != null)
                        {
                            snapshotCache[snapshotName] = mixerSnapshot;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// スナップショットを切り替える静的関数
        /// </summary>
        /// <param name="snapshotEnum">AudioSnapshots で指定するスナップショット</param>
        /// <param name="transitionTime">オプションの遷移時間(省略時は defaultTransitionTime を使用)</param>
        public static void SwitchSnapshot(AudioSnapshots snapshotEnum, float transitionTime = -1f)
        {
            if (Instance == null)
            {
                Debug.LogError("AudioMixerManager インスタンスが初期化されていません。");
                return;
            }

            // transitionTime が指定されていなければデフォルトの遷移時間を使用
            if (transitionTime < 0)
            {
                transitionTime = Instance.defaultTransitionTime;
            }

            // enum からキャッシュのキーとなる名前を取得
            string snapshotEnumName = snapshotEnum.ToString();
            if (Instance.snapshotCache.TryGetValue(snapshotEnumName, out AudioMixerSnapshot snapshot))
            {
                snapshot.audioMixer.TransitionToSnapshots(new[] { snapshot }, new float[] { 1.0f }, transitionTime);
                Debug.Log($"スナップショットに切り替えました: {snapshotEnumName} in AudioMixer: {snapshot.audioMixer.name}");
            }
            else
            {
                Debug.LogWarning($"スナップショット '{snapshotEnumName}' がキャッシュ内に見つかりません。");
            }
        }
    }
}

2. AudioSnapshotEnumGenerator

クラスの目的:

AudioSnapshotEnumGeneratorクラスは、Unityプロジェクト内のすべてのAudioMixerとそのスナップショットを自動的に検出し、それらを列挙型(enum)としてスクリプトに生成するためのエディタ拡張ツールです。これにより、コード内でスナップショットを型安全に参照でき、誤字やタイプミスを防ぐことができます。

主な機能:

  • 自動検出と列挙型生成:
    プロジェクト内のAudioMixerおよびそのスナップショットを検索し、AudioSnapshotsEnum.csというファイルに列挙型を生成します。

  • 識別子のバリデーション:
    スナップショット名やミキサー名に数字や記号が含まれている場合、そのエントリをコメントアウトしてエラーを防止します。

  • エディタメニューからの実行:
    Unityのメニュー「Tools > Generate AudioSnapshots Enum」から簡単に実行できます。

使用方法:

  1. スクリプトの配置:
    AudioSnapshotEnumGeneratorクラスをエディタスクリプトとしてAssets/Editorフォルダ(またはEditorフォルダ内)に保存します。

  2. スナップショットの設定:
    プロジェクト内でAudioMixerとそのスナップショットを作成・設定します。

  3. 列挙型の生成:

    • Unityエディタ上部のメニューから「Tools > Generate AudioSnapshots Enum」をクリックします。
    • スクリプトが実行され、Assets/Scriptsフォルダ内にAudioSnapshotsEnum.csファイルが生成されます。
  4. 生成された列挙型の使用:

    using UnityEngine;
    
    public class AudioManager : MonoBehaviour
    {
        public AudioSnapshots currentSnapshot;
    
        public void ChangeSnapshot(AudioSnapshots snapshot)
        {
            // スナップショットの変更処理をここに実装
        }
    }
    

注意事項:

  • 識別子の制限:
    C#の識別子として無効な文字(数字で始まる、記号が含まれるなど)がスナップショット名やミキサー名に含まれている場合、そのエントリはコメントアウトされます。必要に応じて、名前を変更して再度列挙型を生成してください。

  • 非公開プロパティの使用:
    このスクリプトはSerializedObjectを使用してAudioMixerの非公開プロパティm_Snapshotsにアクセスしています。これはUnityのバージョンアップにより動作しなくなる可能性があります。

  • エディタ専用:
    SerializedObjectはエディタ専用の機能であり、ビルド後の実行ファイルでは使用できません。このスクリプトはエディタ拡張としてのみ使用してください。

  • スクリプトの配置場所:
    スクリプトは必ずEditorフォルダ内に配置してください。そうしないと、ビルド時に不要なエラーが発生する可能性があります。

  • 命名規則の一貫性:
    ミキサー名やスナップショット名にスペースが含まれている場合、アンダースコアに置き換えられます。一貫性を保つため、可能であればスペースを避けて命名することをおすすめします。

  • エラーの確認:
    生成されたAudioSnapshotsEnum.csファイルを確認し、コメントアウトされたエントリやエラーメッセージがないかチェックしてください。

AudioSnapshotEnumGenerator.cs


using UnityEditor;
using UnityEngine;
using UnityEngine.Audio;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

public class AudioSnapshotEnumGenerator
{
    /// <summary>
    /// プロジェクト内のすべてのAudioMixerとそのスナップショットを取得し、それぞれのAudioMixer名とスナップショット名を元にAudioSnapshotsという列挙型を生成します。
    /// 各スナップショットは "AudioMixer名_Snapshot名" の形式で登録されます。
    /// スナップショット名に数字や記号が含まれている場合、そのエントリはコメントアウトされます。
    /// </summary>
    [MenuItem("Tools/Generate AudioSnapshots Enum")]
    public static void GenerateEnum()
    {
        // Enumスクリプトを保存するディレクトリとファイル名
        string directoryPath = "Assets/Scripts";
        string enumPath = Path.Combine(directoryPath, "AudioSnapshotsEnum.cs");

        // ディレクトリが存在しない場合は作成
        if (!Directory.Exists(directoryPath))
        {
            Directory.CreateDirectory(directoryPath);
        }

        // AssetDatabaseからすべてのAudioMixerを検索
        string[] mixerGuids = AssetDatabase.FindAssets("t:AudioMixer", new[] { "Assets/Audio" });
        if (mixerGuids.Length == 0)
        {
            Debug.LogWarning("プロジェクト内にAudioMixerが見つかりませんでした。");
            return;
        }

        // スクリプト生成用の文字列を構築
        StringBuilder enumScript = new StringBuilder();
        enumScript.AppendLine("public enum AudioSnapshots");
        enumScript.AppendLine("{");

        foreach (string guid in mixerGuids)
        {
            // GUIDからパスを取得し、AudioMixerオブジェクトをロード
            string path = AssetDatabase.GUIDToAssetPath(guid);
            AudioMixer mixer = AssetDatabase.LoadAssetAtPath<AudioMixer>(path);
            if (mixer != null)
            {
                // SerializedObjectを使用して内部データにアクセス
                SerializedObject serializedMixer = new SerializedObject(mixer);
                SerializedProperty snapshotsProperty = serializedMixer.FindProperty("m_Snapshots");

                if (snapshotsProperty != null && snapshotsProperty.isArray)
                {
                    for (int i = 0; i < snapshotsProperty.arraySize; i++)
                    {
                        SerializedProperty snapshotProp = snapshotsProperty.GetArrayElementAtIndex(i);
                        AudioMixerSnapshot snapshot = snapshotProp.objectReferenceValue as AudioMixerSnapshot;

                        if (snapshot != null)
                        {
                            // AudioMixerの名前とスナップショットの名前を組み合わせて、enum名を生成
                            string mixerName = mixer.name.Replace(" ", "_");
                            string snapshotName = snapshot.name.Replace(" ", "_");
                            string enumName = $"{mixerName}_{snapshotName}";

                            // 有効なC#の識別子かをチェック
                            if (IsValidIdentifier(enumName))
                            {
                                // スクリプトにスナップショットを追加
                                enumScript.AppendLine("    " + enumName + ",");
                            }
                            else
                            {
                                // 無効な場合はコメントアウト
                                enumScript.AppendLine("    // " + enumName + "  // 名前に無効な文字が含まれています");
                            }
                        }
                    }
                }
                else
                {
                    Debug.LogWarning($"Mixer '{mixer.name}' 内のスナップショットを取得できませんでした。");
                }
            }
        }

        enumScript.AppendLine("}");

        // ファイルに書き込み
        File.WriteAllText(enumPath, enumScript.ToString(), Encoding.UTF8);
        Debug.Log("AudioSnapshots列挙型を生成しました: " + enumPath);

        // AssetDatabaseを更新して新しいenumを即座に使用可能に
        AssetDatabase.Refresh();
    }

    // 有効なC#識別子かどうかをチェックする関数
    private static bool IsValidIdentifier(string identifier)
    {
        if (string.IsNullOrEmpty(identifier))
            return false;

        // 識別子がキーワードでないかチェック
        if (IsCSharpKeyword(identifier))
            return false;

        // 最初の文字がアルファベットまたはアンダースコアである必要がある
        if (!Regex.IsMatch(identifier[0].ToString(), @"^[a-zA-Z_]+$"))
            return false;

        // 残りの文字がアルファベット、数字、またはアンダースコアである必要がある
        if (!Regex.IsMatch(identifier, @"^[a-zA-Z_][a-zA-Z0-9_]*$"))
            return false;

        return true;
    }

    // C#のキーワードかどうかをチェックする関数
    private static bool IsCSharpKeyword(string word)
    {
        string[] keywords = new string[]
        {
            "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char",
            "checked", "class", "const", "continue", "decimal", "default", "delegate",
            "do", "double", "else", "enum", "event", "explicit", "extern", "false",
            "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit",
            "in", "int", "interface", "internal", "is", "lock", "long", "namespace",
            "new", "null", "object", "operator", "out", "override", "params", "private",
            "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short",
            "sizeof", "stackalloc", "static", "string", "struct", "switch", "this",
            "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe",
            "ushort", "using", "virtual", "void", "volatile", "while"
        };
        foreach (string keyword in keywords)
        {
            if (word == keyword)
                return true;
        }
        return false;
    }
}


3. AudioMixerTester

クラスの目的

AudioMixerTesterは、デバッグ用のクラスであり、選択したAudioSnapshotsのスナップショットを簡単に切り替え、現在どのスナップショットがアクティブかを確認できるインターフェースを提供します。

主な機能

  • 選択されたスナップショットの切り替えAudioSnapshots enumから指定したスナップショットに簡単に切り替え可能。
  • スナップショットのデバッグ表示OnGUIメソッドを使用して、画面上に現在のスナップショットを表示し、切り替えボタンを提供。

使用方法

  1. シーン内に新しいGameObjectを作成し、AudioMixerTesterスクリプトをアタッチします。
  2. selectedSnapshotに切り替えたいAudioSnapshotsのenum値を設定し、transitionTimeを調整します。
  3. 実行時に、画面の左上に現在のスナップショット名が表示され、切り替えボタンを押すと、AudioMixerManager経由でスナップショットが切り替わります。

プロパティとメソッド

プロパティ

  • selectedSnapshot : インスペクタで切り替えたいスナップショットを選択します。
  • transitionTime : スナップショットの切り替え時に使用するフェード時間(秒単位)。

メソッド

  • SwitchToSelectedSnapshot() : インスペクタで指定されたスナップショットに切り替えるメソッド。
  • OnGUI() : OnGUIを使用して画面上に現在のスナップショット名と切り替えボタンを表示します。

AudioMixerTester.cs

using UnityEngine;

namespace dsgarage.Audio
{
    /// <summary>
    /// AudioMixerTester は、AudioMixerManager のスナップショットの切り替えをテストするためのデバッグ用クラスです。
    /// 現在のスナップショット状態を画面上に表示し、選択されたスナップショットに切り替え可能です。
    /// </summary>
    public class AudioMixerTester : MonoBehaviour
    {
        // AudioMixerManagerを参照(起動時に自動検索)
        private AudioMixerManager audioMixerManager;

        // 切り替え用のAudioSnapshots選択
        public AudioSnapshots selectedSnapshot;

        // 遷移時間(フェード時間)
        public float transitionTime = 1.0f;

        private AudioSnapshots lastSnapshot;

        private void Start()
        {
            // シーン内のAudioMixerManagerを検索して設定
            audioMixerManager = FindObjectOfType<AudioMixerManager>();
            if (audioMixerManager == null)
            {
                Debug.LogError("AudioMixerManager was not found in the scene.");
                return;
            }

            // 初回のスナップショット表示更新
            UpdateSnapshotDisplay();
        }

        private void Update()
        {
            // 選択されたスナップショットが変更されたら切り替え
            if (selectedSnapshot != lastSnapshot)
            {
                SwitchToSelectedSnapshot();
                lastSnapshot = selectedSnapshot;
            }
        }

        /// <summary>
        /// 選択されたスナップショットに切り替えます。
        /// </summary>
        public void SwitchToSelectedSnapshot()
        {
            if (audioMixerManager != null)
            {
                AudioMixerManager.SwitchSnapshot(selectedSnapshot, transitionTime);
                UpdateSnapshotDisplay();
            }
        }

        /// <summary>
        /// 現在のスナップショット名を画面上に表示します。
        /// </summary>
        private void OnGUI()
        {
            // デバッグ情報の表示位置を設定
            GUIStyle labelStyle = new GUIStyle(GUI.skin.label)
            {
                fontSize = 16,
                fontStyle = FontStyle.Bold,
                normal = { textColor = Color.white }
            };

            // ボタンとラベルの位置を調整
            if (GUI.Button(new Rect(10, 10, 200, 30), "Switch to Selected Snapshot"))
            {
                SwitchToSelectedSnapshot();
            }

            // ボタンの下に現在のスナップショットを表示
            GUI.Label(new Rect(10, 50, 300, 30), "Current Snapshot: " + selectedSnapshot.ToString(), labelStyle);
        }

        /// <summary>
        /// スナップショットの切り替えをデバッグログに出力します。
        /// </summary>
        private void UpdateSnapshotDisplay()
        {
            Debug.Log("Switched to snapshot: " + selectedSnapshot.ToString());
        }
    }
}

Discussion