Unity for ADX環境下で、エディター上のGame Viewへサウンドのカテゴリ（BGMや効果音）ごとにミュートするボタンを付ける

# ADX for Unity環境下におけるエディタでのカテゴリごとミュート

先日実施されたUnity Japan主催のイベントUnity SYNCの講演にて、「痒いところに手が届く！Editor拡張で開発をチョット快適にするススメ」というセッションがありました。

 - 痒いところに手が届く！Editor拡張で開発をチョット快適にするススメ(株式会社ポケラボ)
https://learning.unity3d.jp/8858/

その中の一節で、「エディター内で音声をカテゴリごとにミュートする機能」について紹介がありました。本公演で取り上げていたポケラボ社開発の『シノアリス』はCRIWARE採用タイトルで、サウンドの実装はADXが使用されています。講演は非常に限られた時間だったため、ミュートの詳細な実装については紹介については触れられていませんでした。そこで、同様のミュート機能の実装と、プラスアルファした形で作ってみました。

 - デモ動画
<iframe width="560" height="315" src="https://www.youtube.com/embed/Aly844vPdAI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## ADXにおける「カテゴリ」とは
ADXのワークフローでは、「音データ」側に様々な制御パラメータを埋め込んで、ゲームランタイム上で再生する際の制御コードを減らすアプローチをとっています。
その代表的なものとして「カテゴリ」があります。これは、音データごとに属性をタグのように付与できるもので、再生しているAudio Source（的なもの）にかかわらずカテゴリごとに音量制御などができます。

カテゴリ機能
https://game.criware.jp/manual/unity_plugin/latest/contents/atom4u_keys_category.html

### 参考１：ゲーム内の機能として音量コンフィグを実装
ゲーム内でカテゴリーごとに音量を変更するオプション画面を作る場合の説明は以下にあります。

- ADX for Unityで音量コンフィグを実装
https://qiita.com/nimushiki/items/4e6db6fcd82177a3d117

### 参考２：ADX使用環境における全体のミュート
ADX導入環境下において、Game ViewのミュートボタンをADX対応にする拡張については、実はgistで公開しています。
https://gist.github.com/TakaakiIchijo/6d2f8573ac92e877a621e5aa679c7a18

Muteボタンが押されているかどうかのステートをEditorUtility.audioMasterMuteから取れるので、これの値の変化を監視してADXシステム全体のミュートを設定します。
これで、標準のミュートボタンをオーバーライドして、エディタ実行時にADX再生音のミュートを行うことができます。

```cs
using CriWare;
using UnityEditor;

[InitializeOnLoadAttribute]
public class CriWareGameViewMuteButton
{
    private static bool isAudioMute = false;
    
    static CriWareGameViewMuteButton()
    {
        EditorApplication.update  += () =>
        {
            if (isAudioMute == EditorUtility.audioMasterMute) return;
        
            isAudioMute = EditorUtility.audioMasterMute;
        
            CriAtomExAsr.SetBusVolume(0, isAudioMute ? 0f : 1f);
        };
    }
}
```

# カテゴリごとのミュート
本題のカテゴリごとのミュートを作っていきます。発想のもととなったポケラボ社さんの講演では、ミュート操作用のエディタウィンドウを用意し、チェックボックスでオンオフをする実装がとられていました。実際、このやり方が一番エディタ拡張のカロリーが低いです。そこで今回はちょっとチャレンジして、Game Viewに専用ボタンを表示するアプローチをとりました。

Game Viewの「Mute Audio」とは別に「Mute SE」「Mute BGM」が表示されています。

![ADX2MuteButtonEditorCategory - SampleScene - Windows, Mac, Linux - Unity 2021.3.4f1 _DX11_ 2022_12_15 17_50_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/4a21d382-8060-85f0-ca59-fe284f8621ed.png)

## ADXカテゴリごとのミュートオンオフ関数
まずはサウンドの制御側を作ります。「BGM」「SE」の2カテゴリがあるとします。Atom Craft側のカテゴリ設定は次の通りです。

![CRI Atom Craft LE Ver.3.47.01 (built on Jun 21 2022) 2022_12_15 17_39_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/d8754810-17ff-15e8-efce-ab32729cc840.png)

これらのカテゴリが、各音声データ（ADX用語で言うところの「キュー」）に設定されているものとします。
Unity側のランタイムでカテゴリごとにミュートを設定するコードは以下の通りです。

```cs
CriAtomExCategory.Mute("BGM", true);
```

指定のカテゴリ名でミュートをオンオフする関数を作っておきます。

```cs
using UnityEditor;

    private static void SetAdx2Mute(string categoryName, bool isMute)
    {
        if (Application.isPlaying)
        {
            CriAtomExCategory.Mute(categoryName, isMute);
        }
    }
```

この関数を通じてオンオフを行うボタンを作っていきます。

## Game Viewにボタンを足す
Game Viewにボタンを生やします。
UnityEditor.UIElementsでToolbarToggleを描画し、UnityEditor.GameViewを取得してツールバーに乗せてあげましょう。

Toolbar Toggle
https://docs.unity3d.com/ScriptReference/UIElements.ToolbarToggle.html

Game Viewの参照はReflectionを使って以下の手続きで取得できます。

```cs
using System.Reflection;

//中略

var asm = Assembly.Load("UnityEditor");
var typeGameView = asm.GetType("UnityEditor.GameView");
var gameView = EditorWindow.GetWindow(typeGameView, false, "Game", false);
```

Toggleの描画は以下の通りです。

```cs

    private const string EditorPrefsIsBGMMute = "Editor_Prefs_IsBGMMute";
    private const string EditorPrefsIsSEMute = "Editor_Prefs_IsSEMute";


//中略

        var toolbarToggleBGM = new ToolbarToggle() { text = "Mute BGM" };
        var toolbarToggleSE = new ToolbarToggle() { text = "Mute SE" };

        var toolbar = new VisualElement();
        var style = toolbar.style;
        var rootVisualElement = gameView.rootVisualElement;

        style.flexDirection = FlexDirection.RowReverse;
        style.height = 20;
        style.marginTop = 1;
        style.marginRight = 300;
        style.paddingLeft = 100;

        toolbarToggleBGM.value = EditorPrefs.GetBool(EditorPrefsIsBGMMute);

        toolbarToggleBGM.RegisterValueChangedCallback(x =>
        {
            EditorPrefs.SetBool(EditorPrefsIsBGMMute, toolbarToggleBGM.value);
            SetAdx2Mute("BGM",toolbarToggleBGM.value);
        });

        toolbarToggleSE.value = EditorPrefs.GetBool(EditorPrefsIsSEMute);

        toolbarToggleSE.RegisterValueChangedCallback(x =>
        {
            EditorPrefs.SetBool(EditorPrefsIsSEMute, toolbarToggleSE.value);
            SetAdx2Mute("SE",toolbarToggleSE.value);
        });

        toolbar.Add(toolbarToggleBGM);
        toolbar.Add(toolbarToggleSE);
        
        toolbar.BringToFront();

        rootVisualElement.Clear();
        rootVisualElement.Add(toolbar);
```

トグルの状態はPlayごとにリセットされてしまうため、EditorPrefを使ってEditorPrefsIsBGMMute, EditorPrefsIsSEMuteの名前でトグルの状態を保存しています。

これらをクラスにまとめ、[InitializeOnLoad]で起動時に読み込むようにすればボタンが出ます。

```cs
using System.Reflection;
using UnityEngine;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
using CriWare;

[InitializeOnLoad]
public static class GameViewExtension
{
    private const string EditorPrefsIsBGMMute = "Editor_Prefs_IsBGMMute";
    private const string EditorPrefsIsSEMute = "Editor_Prefs_IsSEMute";

    static GameViewExtension()
    {
           //ここに上記処理を書く
    }
}

```


## エディタ再生時にトグルボタンの状態を取得して反映

この状態でも動きますが、エディター停止状態からミュートボタンを押したときはその設定が反映されません。プレイ中の操作のみが反映されます。
これを解消するために、Playが始まったときにボタン状況を確認してすぐミュートする処理を足します。

```cs
    private static void OnChangedPlayMode(PlayModeStateChange state)
    {
        if (state == PlayModeStateChange.EnteredPlayMode)
        {
            if (EditorPrefs.HasKey(EditorPrefsIsBGMMute))
            {
                var isMute = EditorPrefs.GetBool(EditorPrefsIsBGMMute, false);
                if (isMute == false) return;

                SetAdx2Mute("BGM",true);
            }
            
            if (EditorPrefs.HasKey(EditorPrefsIsSEMute))
            {
                var isMute = EditorPrefs.GetBool(EditorPrefsIsSEMute, false);
                if (isMute == false) return;

                SetAdx2Mute("SE",true);
            }
        }
    }
```

この関数を先ほどのstatic GameViewExtension()初期化関数の中で呼ぶことで、エディターがplayになる前にミュートボタンをクリックした操作が反映されます。

# まとめ
今回はリフレクションを使ったあまりエレガントでない方法ですが、もしかするとGameViewの中にUI要素を表示する「Overlays」を使ったほうが便利かもしれません。
今後調査します。

https://docs.unity3d.com/Manual/overlays.html