Unity Animator Parameterへの文字列アクセスを排除する

# 目的
コードからAnimatorのParameterにアクセスしたい！！！
ただし、コード中に文字列は使わない。

# 課題と対策
コード中にエディタ側でいくらでも変更できる文字列を書き込むのは意味が分からない
↓
文字列アクセスを排除するためにstaticメソッドの構築を自動化するなどした。

# 手法
エディタ拡張
みんなだいすきMenuItemから.csファイルを構築する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163397/afc7a4bc-4e3d-7a93-e0fe-9aacaf506c95.png)

AnimatorControllerの数だけクラスが増えるので適当な名前空間に封じ込める。

```cs
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
using System.IO;

public class AnimParamAccessorCreater : Editor
{
	[MenuItem("Assets/Animator Utility/Animator Parameter Accessor")]
	static void Create()
	{
		foreach (AnimatorController anim in Selection.GetFiltered(typeof(AnimatorController), SelectionMode.Assets))
		{
			CreateAnimParamAccessor(anim);
		}
	}
	static void CreateAnimParamAccessor(AnimatorController anim)
	{
		if (anim == null)
			return;
		var path = Path.ChangeExtension(AssetDatabase.GetAssetPath(anim), ".cs");
		if (File.Exists(path))
		{
			File.Delete(path);
		}
		using (FileStream fs = File.Create(path))
		{
			AddText(fs, "namespace AnimParamAccessor{\n");
			AddText(fs, "using UnityEngine;\n");
			AddText(fs, "\n");
			AddText(fs, "public class " + anim.name + "Anim\n");
			AddText(fs, "{\n");
			foreach (AnimatorControllerParameter p in anim.parameters)
			{
				WriteGetMethod(fs, p);
				WriteSetMethod(fs, p);
				WriteResetMethod(fs, p);
			}
			AddText(fs, "}\n");
			AddText(fs, "}\n");
		}
		// 作成が終わったらスクリプトを強制リロード
		EditorUtility.RequestScriptReload();
	}
	static void WriteSetMethod(FileStream fs, AnimatorControllerParameter param)
	{
		var paramName = param.name;
		var paramType = param.type;
		var paramRef = "";
		var paramMethod = "";
		switch (paramType)
		{
			case AnimatorControllerParameterType.Float:
				paramRef = ", float " + paramName;
				paramMethod = "SetFloat";
				break;
			case AnimatorControllerParameterType.Int:
				paramRef = ", int " + paramName;
				paramMethod = "SetInteger";
				break;
			case AnimatorControllerParameterType.Bool:
				paramRef = ", bool " + paramName;
				paramMethod = "SetBool";
				break;
			case AnimatorControllerParameterType.Trigger:
				paramMethod = "SetTrigger";
				break;
		}
		AddText(fs, "public static void Set" + paramName + "(Animator anim" + paramRef + ")\n");
		AddText(fs, "{\n");
		if (paramType == AnimatorControllerParameterType.Trigger)
		{
			// Triggerのみ入力引数を必要としない
			AddText(fs, "    anim." + paramMethod + "(" + param.nameHash + ");\n");
		}
		else
		{
			AddText(fs, "    anim." + paramMethod + "(" + param.nameHash + ", " + paramName + ");\n");
		}
		AddText(fs, "}\n");
	}
	static void WriteGetMethod(FileStream fs, AnimatorControllerParameter param)
	{
		var paramName = param.name;
		var paramType = param.type;
		var paramTypeString = "";
		var paramMethod = "";
		if (paramType == AnimatorControllerParameterType.Trigger)
		{
			// TriggerにGetはない
			return;
		}
		switch (paramType)
		{
			case AnimatorControllerParameterType.Float:
				paramTypeString = "float";
				paramMethod = "GetFloat";
				break;
			case AnimatorControllerParameterType.Int:
				paramTypeString = "int";
				paramMethod = "GetInteger";
				break;
			case AnimatorControllerParameterType.Bool:
				paramTypeString = "bool";
				paramMethod = "GetBool";
				break;
		}
		AddText(fs, "public static " + paramTypeString + " Get" + paramName + "(Animator anim)\n");
		AddText(fs, "{\n");
		AddText(fs, "    return anim." + paramMethod + "(" + param.nameHash + ");\n");
		AddText(fs, "}\n");
	}
	static void WriteResetMethod(FileStream fs, AnimatorControllerParameter param)
	{
		var paramName = param.name;
		var paramType = param.type;
		if (paramType != AnimatorControllerParameterType.Trigger)
		{
			// Trigger以外Resetはない
			return;
		}
		AddText(fs, "public static void Reset" + paramName + "(Animator anim)\n");
		AddText(fs, "{\n");
		AddText(fs, "    anim.ResetTrigger(" + param.nameHash + ");\n");
		AddText(fs, "}\n");
	}
	private static void AddText(FileStream fs, string value)
	{
		byte[] info = new System.Text.UTF8Encoding(true).GetBytes(value);
		fs.Write(info, 0, info.Length);
	}
}
```

Animator Parameterを以下のように設定すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163397/7bfa3bd5-863c-9d32-c417-a7ee66c1bb03.png)

以下のように変換される。
```cs
namespace AnimParamAccessor{
using UnityEngine;

public class TestControllerAnim
{
public static float GetSpeed(Animator anim)
{
    return anim.GetFloat(-823668238);
}
public static void SetSpeed(Animator anim, float Speed)
{
    anim.SetFloat(-823668238, Speed);
}
public static int GetState(Animator anim)
{
    return anim.GetInteger(1649606143);
}
public static void SetState(Animator anim, int State)
{
    anim.SetInteger(1649606143, State);
}
public static bool GetIsMove(Animator anim)
{
    return anim.GetBool(1236215618);
}
public static void SetIsMove(Animator anim, bool IsMove)
{
    anim.SetBool(1236215618, IsMove);
}
public static void SetTestTrigger(Animator anim)
{
    anim.SetTrigger(1033918907);
}
public static void ResetTestTrigger(Animator anim)
{
    anim.ResetTrigger(1033918907);
}
}
}

```

こんな具合に使う。
```cs
using UnityEngine;
using AnimParamAccessor;

public class DebugAnimParamAccess : MonoBehaviour
{
	[SerializeField] Animator anim;
	void OnEnable()
	{
		TestControllerAnim.SetSpeed(anim, 3.0f);
		TestControllerAnim.SetState(anim, 3);
		TestControllerAnim.SetIsMove(anim, true);
		TestControllerAnim.SetTestTrigger(anim);
	}

	void OnDisable()
	{
		TestControllerAnim.SetSpeed(anim, 0.0f);
		TestControllerAnim.SetState(anim, 0);
		TestControllerAnim.SetIsMove(anim, false);
		TestControllerAnim.ResetTestTrigger(anim);
	}
}

```
実演。
![AnimParamTest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163397/ba677c2b-3aab-b558-aae6-e4439145bd28.gif)

### メリット
- インテリセンスが効く(最強)
- typoがない
- ハッシュ値からのアクセスなので文字列アクセスよりちょっと速い

### デメリット
- 特に安全性とか考えてないから基本的にフェイルセーフが効いてない
- アニメーターコントローラーの数だけクラスが増える
- 同名アニメーターコントローラーがあるとエラー
    - そんなもの作るな
    - 回避法としてはクラス名をフルパスにするとかハッシュ付けちゃうとか

# まとめと愚痴
### まとめ
文字列アクセスの排除のため、staticメソッドを書くMenuItemを作成した。
適当に名前空間やクラス名を調整して使うとよいです
。
### 愚痴
本当は拡張メソッド使いたかった
```
using AnimParamAccessor;
[SerializeField]Animator anim;
void Start()
{
    anim.SetisMove(true);
}
```
みたいな具合に
けど別のAnimatorControllerとパラメーター名被るとエラー吐く実装は流石に意味が分からないのでやめた
(メソッドの後ろにハッシュ値とかなんか付けたら何とかなるけど見た目が汚い)

Unityはコード側からAnimatorparameterを触ることを推奨していないのでは...と思うくらいにはアクセス手段がよくわからない。