Unity上でSerializedObjectやSerializedPropertyに対する型付けされたアクセスを提供するSource Generator「UniTyped」がリリースされていたので触ってみました。
UniTypedとは
UniTypedは、SerializedObject や SerializedPropertyに対し、型付けされたアクセスを提供するソースジェネレータです。より簡潔で安全なエディタ拡張コードを書くのに役立ちます。
とある通り、FindPropertyでSerializedPropertyとして取得し様々な操作を記述していた既存のコードから、Source Generatorにより生成された型付けされたより安全なコーディングができることを目的としたライブラリです。 また、生成されるコードは構造体ベースになっており、boxingを避けるような設計になっているため、ヒープのメモリ確保量が少なくパフォーマンスにも優れたものとなっています。
導入方法
Package Managerからhttps://github.com/ruccho/UniTyped.git?path=/Packages/com.ruccho.unityped
を入力してセットアップは完了します。
サンプルコード
[UniTyped]
属性をMonoBehaviourやScriptableObject、そのほかSerializableなクラスに付与すると、UniTyped.Generated.[YourNamespace].[YourClass]View
という構造体が使用できるようになります。
この生成された[YourClass]View
という構造体のTarget
プロパティにSerializedObject
のインスタンスを渡したインスタンス経由でFindProperty
などを必要とせずシリアライズされたプロパティの編集が可能となります。(※1)
using UnityEngine; using UniTyped; namespace Sample { [UniTyped] public class Example01 : MonoBehaviour { [SerializeField] private int number; } }
using Sample; using UnityEditor; using UnityEngine; using UniTyped.Generated.Sample; namespace Editor { [CustomEditor(typeof(Example01))] public class Example01Editor : UnityEditor.Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); Example01View view = new Example01View { Target = serializedObject }; if (GUILayout.Button("+")) { view.number++; } if (GUILayout.Button("-")) { view.number--; } serializedObject.ApplyModifiedProperties(); } } }
※1. サポートされている型に記載があるものはUniTypedのコード生成がサポートしています。
生成コードの調性方法
一部のフィールドを対象外にする
ignore
オプションをtrueに設定することで指定したフィールドをコード生成の対象外にすることができます。
using UnityEngine; using UniTyped; namespace Sample { [UniTyped] public class Example01 : MonoBehaviour { [SerializeField] private int number; [SerializeField, UniTypedField(ignore = true)] private int number02; } }
using Sample; using UnityEditor; using UnityEngine; using UniTyped.Generated.Sample; namespace Editor { [CustomEditor(typeof(Example01))] public class Example01Editor : UnityEditor.Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); Example01View view = new Example01View { Target = serializedObject }; if (GUILayout.Button("+")) { view.number++; } if (GUILayout.Button("-")) { view.number--; } // ignore = true のためこれは生成されていない // view.number01++; serializedObject.ApplyModifiedProperties(); } } }
SerializedPropertyへのアクセスも提供する
UniTypedはデフォルトでは値の直接操作を可能にするために、アクセスプロパティを実際の値の型として公開します(フラット化)が、オプションを設定することでSerializedPropertyを公開することもできます。
using UnityEngine; using UniTyped; namespace Sample { [UniTyped] public class Example01 : MonoBehaviour { [SerializeField] private int number; [SerializeField, UniTypedField(ignore = true)] private int number02; [SerializeField, UniTypedField(forceNested = true)] private int number03; } }
using Sample; using UnityEditor; using UnityEngine; using UniTyped.Generated.Sample; namespace Editor { [CustomEditor(typeof(Example01))] public class Example01Editor : UnityEditor.Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); Example01View view = new Example01View { Target = serializedObject }; if (GUILayout.Button("+")) { view.number++; } if (GUILayout.Button("-")) { view.number--; } // ignore = true のためこれは生成されていない // view.number01++; // forceNested = true のためSerializedPropertyへのアクセスも公開される Debug.Log($"SerializedProperty: {view.number03.Property}"); Debug.Log($"Value: {view.number03.Value}"); serializedObject.ApplyModifiedProperties(); } } }
Source Generatorが使用できない(Unity2021.2以前の環境)
UniTypedはRoslyn Source Generatorの機能を利用しているため、Unity2021.2以前の環境ではそのままでは利用できません。そういった環境向けにManual Generatorが提供されています。
Assemblyを切り分けている場合
とある機能を提供するModule01
が存在し、Runtime
とEditor
でAssemblyを切り分けている場合、Runtime側に存在するコードに対して生成されるViewに対しても問題なくEditor側から使用できます。
Uni Typed Manual Generator Profile
を使用して対象とする.csprojを選択しコード生成を手動で行うことが可能となっています。
Module01.Runtimeに下記のコードを配置。
using UnityEngine; using UniTyped; namespace Module01.Runtime { [UniTyped] public class Module01Example : MonoBehaviour { [SerializeField] private int number; } }
Module01.Editor側からViewを使用することができる。
using Module01.Runtime; using UnityEditor; using UnityEngine; using UniTyped.Generated.Module01.Runtime; namespace Module01.Editor { [CustomEditor(typeof(Module01Example))] public class Module01ExampleEditor : UnityEditor.Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); Module01ExampleView view = new Module01ExampleView { Target = serializedObject }; if (GUILayout.Button("Random")) { view.number = Random.Range(0, 100); } serializedObject.ApplyModifiedProperties(); } } }
まとめ
Source Generatorの機能を利用した新しいライブラリを使ってみた記事でした。UnityにおけるC#スクリプティングの環境は年々改良されており、Source Generatorに関しても利用したライブラリがいろいろと出てきてますが、意識せずコードが生成されているという開発者体験はかなり便利で個人的に好みです。