【Unity】デフォルト値の設定できないアセットのデフォ値を初期化する方法　（例：アニメーションステート）

# 目的

Unity の一般的なアセットやスクリプトは、Project Settings にある Preset Manager で、
各パラメーターのデフォルト値を設定できます。
アセットの場合、更に配置場所によって違うデフォ値を適用できます。

しかし、アニメーションステートや、アニメーション遷移など、一部のアセットではデフォルト値を設定できません。
ステートの Write Defaults を常にオフにしたい場合などでは、毎回わざわざオフにする手間が増えて、とても困ります。
![WriteDefaults.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/268b7f04-1a64-b507-6dc9-d97bce64bef3.png)

この記事では、この問題を解決するための簡単な手法を紹介します。

# 手法概要

Unity の **コンポーネント・アセットの新規作成イベント** にフックして、
コールバックで新規コンポーネント・アセットの値を調整することで、擬似的にデフォルト値を設定できます。

# 手法詳細

Unity 2020 から、[`ObjectChangeEvents`](https://docs.unity3d.com/ja/2021.2/ScriptReference/ObjectChangeEvents.html) という新しいクラスが追加されました。

`ObjectChangeEvents` はアセットの作成・削除や、シーン上に GameObject・コンポーネントの作成、親子関係の変更など、
多岐に渡る処理のイベントコールバックを提供しています。

今回は、`ObjectChangeEvents` の「新規コンポーネント・アセットのイベントコールバック」を利用して、
作成された直後に値をすぐ設定することで、擬似的にデフォルト値を変えます。

イベント種類（`ObjectChangeKind`）については、
* 新規アセット：`CreateAssetObject` 
* 新規 GameObject：`CreateGameObjectHierarchy`
* 新規コンポーネント：`ChangeGameObjectStructure`

このような感じです。

　
この手法のメリットとしては、イベントは自動的に発火されますので、一度処理を書いてしまえば、
**Inspector 上では特に何もしなくてもデフォ値が自動で変わります** ので、プリセットを適用する手間もありません。
　

### 例：アニメステートの Write Defaults をデフォルトオフにする

例として、先程挙げた「アニメーションステートの Write Defaults をデフォルトオフにする」のやり方を紹介します。
　
今回は下記のようなステップのコード書きます：

1. `ObjectChangeEvents` にコールバックをフックする
2. コールバック内で、「新規アニメーションステート作成」のイベントかをチェックする
3. 新規アニメーションステートの Write Defaults を `false` にする

```AnimatorStateDefaultParamSetter.cs
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

/// <summary>
/// アニメーションステートのデフォルト値を設定するクラス
/// </summary>
public class AnimatorStateDefaultParamSetter
{
    /// <summary>
    /// エディターロード時に、イベントにフックする
    /// </summary>
    [InitializeOnLoadMethod]
    private static void HookToObjectChangeEventStream()
    {
        ObjectChangeEvents.changesPublished += CheckNewAnimatorState;
    }

    /// <summary>
    /// 新規アニメーションステートのチェック
    /// </summary>
    private static void CheckNewAnimatorState(ref ObjectChangeEventStream stream)
    {
        for (var i = 0; i < stream.length; i++)
        {
            // 新規アセット作成イベントのみを処理する
            if (stream.GetEventType(i) is not ObjectChangeKind.CreateAssetObject) continue;

            // アニメーターステートのみを処理する
            stream.GetCreateAssetObjectEvent(i, out var createAssetEventData);
            if (Resources.InstanceIDToObject(createAssetEventData.instanceId) is not AnimatorState state) continue;

            // Write Default をオフにする
            state.writeDefaultValues = false;
        }
    }
}
```

結果：
新規作成されたアニメーションステートの Write Defaults が無事オフになっています。

![レコーディング 2024-01-20 201719.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/ae9c2545-196f-e993-9430-fa68958f8e6b.gif)

# 応用

上のスクリプト例ではアセットの値をコード上で直接設定していますので、デフォ値の変更は再コンパイルを伴います。
より柔軟で便利に設定したい場合は、シングルトンの `ScriptableObject` にして、インスペクター上で任意に Preset を設定できるようにすると良いでしょう。

ただし、`[ExcludeFromPreset]` アトリビュートのつけたものはプリセットが作成できないので、その時はコード上で値を直接設定するしかありません。

```例.cs
    // 重複コードを一部省略...
    [SerializeField] private Preset[] presets;

    private void ObjectChangeEventStreamCallback(ref ObjectChangeEventStream stream)
    {
        for (var i = 0; i < stream.length; i++)
        {
            // イベント種類のチェック...

            var targetObj = Resources.InstanceIDToObject(createAssetEventData.instanceId);
            if (targetObj) {
                // Preset が適用できるなら、適用する
                foreach (var preset in presets) {
                    if (preset.CanBeAppliedTo(targetObj)) preset.ApplyTo(targetObj);
                }
            }
        }
    }
```

# 結

今回は `ObjectChangeEvents` を利用して、アニメステート・遷移のデフォ値を変える方法を紹介しました。
この方法では、デフォルト値が設定できないアセットでも、新規作成時に値を代入することで、擬似的にデフォルト値にしています。

この方法応用すれば、より強力なツールになれると思います。