【AudioMixer】exposed parametersの操作クラスを自動生成する

この記事は[Unity Advent Calendar 2023](https://qiita.com/advent-calendar/2023/unity)　18日目の記事です。
昨日は[Source Generatorでプロパティを自動実装してみた](https://qiita.com/amenone_games/items/25c857608af0138eb646)でした。
明日は [Source GeneratorでNullObjectを自動生成する](https://qiita.com/amenone_games/items/f918ce9b828993484dbb) が公開されます！


# なんの記事？
Unity標準のAudioMixerは非常に便利な機能ですが、

スクリプトから各機能にアクセスするためには、exposed parametersを作成し、

文字列でそれを読み取って制御しなければなりません。

せっかく柔軟な音声コントロールを提供しているのに、

**文字列が増えたり減ったりする度にスクリプト編集が必要になると 柔軟性を活かしきれません。**

そこで、**自動でコントローラークラスを生成してくれるエディタ拡張** を作りました！

# 導入方法

* 以下URL右側のReleaseページから unitypackage　をDLして使ってください。

https://github.com/amenonegames/AudioMixerControllerGenerator

# 利用方法

## 起動

メニューバーのTools>GenerateCode>AudioMixerControllerから起動します。

![img_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955766/95d8c9d1-de79-4b54-7bbc-9997c0f52d7d.png)

起動すると以下のようなウインドウが表示されます。

AudioMixerをアタッチして、ボタンを押すと、設定位置にコードが自動生成されます！

![img_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955766/1875951f-57a8-c23d-8419-42f98909177e.png)

初期設定では、Controllerは```Assets/Scripts/Sound/AudioMixerController.cs```として生成されます。

また、interfaceは``Assets/Scripts/Sound/Interface/IAudioMixerControllable.cs``として生成されます。

::: note warn

Asyncメソッド生成は、[Dotween](https://dotween.demigiant.com/)と[UniTask](https://github.com/Cysharp/UniTask)に依存しています。

標準設定のままAsyncメソッドを利用したい場合には、各パッケージを導入しておいてください。

:::

## 実装メソッド

自動で実装されるメソッドを以下に紹介します。

標準で以下の3種類のメソッドが生成されます。

::: note 
引数のExposedPropertyは自動実装されたEnumです。
AudioMixerに登録されているExposedPropertyが自動でEnumとして生成されます。
実行時に操作するExposedPropertyを指定してください。
:::

```csharp 
//指定のExposedPropertyの値を変更します。
//仮にMasterVolumeの実装は以下のようになります。
public void ExposedPropertyChange(float value ,ExposedProperty prop) 
{ 
    switch(prop)
    {
        case ExposedProperty.MasterVolume:
            _audioMixer.SetFloat("MasterVolume", value);
            break;
    }
}

//指定のExposedPropertyの値を起動時の値に戻します。
//仮にMasterVolumeの実装は以下のようになります。
public void ExposedPropertyReset(float value ,ExposedProperty prop)
{

    switch(prop)
    {
        //_originalMasterVolumeは初期値です。Awakeメソッド、またはコンストラクタで格納されます。
        case ExposedProperty.MasterVolume:
            _audioMixer.SetFloat("MasterVolume", _originalMasterVolume);
            break;
    }
}

//指定のExposedPropertyの現在の値を取得します。
//取得に成功するとtrueが返ります。また、out valueに現在の値が返ります。
//仮にMasterVolumeの実装は以下のようになります。
public bool GetExposedProperty(out float value ,ExposedProperty prop)
{
    switch(prop)
    {
        case ExposedProperty.MasterVolume:
            return _audioMixer.GetFloat("MasterVolume", out value);
            break;
        default :
            value = 0;
            return false;
    }
}
```

Asyncメソッドを要求した場合は、追加で以下の2種類のメソッドが生成されます。


```csharp 
public async UniTask ExposedPropertyChangeAsync(float value, CancellationToken token, float duration ,ExposedProperty prop)
{
    //指定のExposedPropertyの値をduration分の時間をかけて変更します。
    //仮にMasterVolumeの実装は以下のようになります。
   switch(prop)
    {
        case ExposedProperty.MasterVolume:
            await _audioMixer.DOSetFloat("MasterVolume", value, duration).WithCancellation(token);
            break;
    }
}

public async UniTask ExposedPropertyResetAsync(CancellationToken token, float duration ,ExposedProperty prop)
{
    //指定のExposedPropertyの値を、duration分の時間をかけて元の値に戻します。
    //仮にMasterVolumeの実装は以下のようになります。
       switch(prop)
    {
        case ExposedProperty.MasterVolume:
            await _audioMixer.DOSetFloat("MasterVolume", _originalMasterVolume, duration).WithCancellation(token);
    }
}
```


::: note warn

Asyncメソッド生成は、[Dotween](https://dotween.demigiant.com/)と[UniTask](https://github.com/Cysharp/UniTask)に依存しています。

:::

::: note info

この他にも、各ExposedPropertyの値を変更するメソッドが自動生成されます。

詳細は生成されたコードを参照してください。

:::

# 生成コードの設定変更


生成コードの設定は以下ファイルを書き換えることで変更可能です。

``` Assets/Plugins/GenerateAudioMixerController/Editor/AudioMixerControllerGenerationSetting.cs```


各設定項目の解説を以下に解説します。コードと併せて確認してください。

# よく使う設定

## isMonoBehaviourAndSerializeAudioMixer
ControllerクラスをMonoBehaviourとして実装するかを指定します。
trueの場合はMonoBehaviour継承クラスとして生成し、AudioMixerをシリアライズドフィールドで受付けます。

falseの場合にはピュアクラスとして生成し、コンストラクタ引数でAudioMixerを受け取ります。

## classNameSpace
Controllerクラスを配置するNamespaceを指定します。

## className
Controllerクラスのクラス名を指定します。

## GenerateClassFilePath()
Controllerクラスを保存するファイルパスを指定します。

{className}部分が、上記のclassNameの値に置換されます。

## interfaceNameSpace

InterfaceのNamespaceを指定します。

## interfaceName

Interfaceの名前を指定します。

# あまり使わない設定

::: note
以降の情報は、より便利に使うための詳細情報です。
標準設定で使う分には把握する必要はありません。
生成されるメソッド自体を変更するような場合には把握する必要があります。
:::

## requireOverrideFiles
同名ファイルが既にある場合に上書きするかを指定します。

同名クラスが重複するとエラーになるので、true にして常に上書きする運用を推奨します。

## GenerateClassImportNameSpace()
Controllerクラスにusingステートメントで宣言するNameフィールドを指定します。

AsyncMethodの生成が要求された場合、以下の箇所でDotWeenとUniTask関連のusingステートメントが生成される設定になっています。

他の方法でAsyncメソッドを実装する場合は書き換えてください。

```csharp
            if (requireAsyncMethod) 
                classImportNameSpace += @$"
using System.Threading;
using Cysharp.Threading.Tasks;
using DG.Tweening;
";
```

また、interface の実装が要求されており、
interfaceのネームスペースが切られている場合は以下の箇所で自動的にusing宣言します。

```csharp
            if (requireInterfaceGeneration && !string.IsNullOrEmpty(interfaceNameSpace)) 
                classImportNameSpace += @$"
using {interfaceNameSpace};";
```

## GenerateInterfaceFilePath()
Interfaceを保存するファイルパスを指定します。

{interfaceName}部分が、interfaceNameの値に置換されます。


## GenerateInterfaceImportNameSpace()

Interfaceのusingステートメントを指定します。

## additionalClasVariableDeclaration
Controllerクラスのメンバ変数として追加で宣言したいことがあれば記入します。

## GenerateChangeMethodName(string propertyName)
ExposedPropertyの値を変更するメソッド名を指定します。Interfaceの生成に利用されます。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateChangeMethodCall(string propertyName)

ExposedPropertyの値を変更するメソッドをコールする記載を指定します。

共通メソッドであるExposedPropertyChangeｎの生成に利用されます。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateChangeMethod(string propertyName)

個別のExposedPropertyの値を変更するメソッドの実装を指定します。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateResetMethodName(string propertyName)

ExposedPropertyの値をリセットするメソッド名を指定します。Interfaceの生成に利用されます。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateResetMethodCall(string propertyName)

ExposedPropertyの値をリセットするメソッドをコールする記載を指定します。

共通メソッドであるExposedPropertyResetの生成に利用されます。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateResetMethod(string propertyName)

個別のExposedPropertyの値をリセットするメソッドの実装を指定します。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateGetMethodName(string propertyName)
個別のExposedPropertyの値を取得するメソッドの名前を指定します。Interfaceの生成に利用されます。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateGetMethodCall(string propertyName)
個別のExposedPropertyの値を取得するメソッドをコールする記載を指定します。

共通メソッドであるGetExposedPropertyの生成に利用されます。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateGetMethod(string propertyName)
個別のExposedPropertyの値を取得するメソッドの実装を指定します。

引数のpropertyNameには、ExposedPropertyの名前が渡されます。

## GenerateChangeAsyncMethodName(string propertyName)

GenerateChangeMethodNameと同様ですが、Asyncメソッド用です。

## GenerateChangeAsyncMethodCall(string propertyName)

GenerateChangeMethodCallと同様ですが、Asyncメソッド用です。

## GenerateChangeAsyncMethod(string propertyName)

GenerateChangeMethodと同様ですが、Asyncメソッド用です。

## GenerateResetAsyncMethodName(string propertyName)

GenerateResetMethodNameと同様ですが、Asyncメソッド用です。

## GenerateResetAsyncMethodCall(string propertyName)

GenerateResetMethodCallと同様ですが、Asyncメソッド用です。

## GenerateResetAsyncMethod(string propertyName)

GenerateResetMethodと同様ですが、Asyncメソッド用です。

# 変更できない変数

## requireAsyncMethod
Asyncメソッドを生成するかを指定します。
エディタ上のチェックボックスよって値が上書きされるため、コードからは変更できません。

## requireInterfaceGeneration
Interfaceを生成するかを指定します。
エディタ上のチェックボックスよって値が上書きされるため、コードからは変更できません。

# おわりに
ここまで読んでくださってありがとうございました。
利用に際して不明な点などありましたら、コメントや、XのDMにてご連絡ください。

開発の一助になれば幸いです。