概要
今回は個人開発の中で UnityEditor.IMGUI.Controls.TreeView を利用してEditor上でシート管理をしやすくした話をします。
この記事はその1の続きです。
合わせて読むと理解しやすいと思います。
コード
コードの全体は上記のリポジトリにまとめています。
PackageManagerウィンドウから Install Package from git URL… を選択し、下記のURLを入力することで使えるようになります。
https://github.com/jukey17/EditorSheetView.git?path=Assets/EditorSheetView
動作確認環境は Unity6.3 LTS(6000.3.1f1) です。
本題
リポジトリ内の実装からコードを抜粋しながら説明するので、全体のコードといっしょに見るとより分かりやすいと思います。
その1の記事では列情報の事前生成について(ISheetViewColumn と ISheetViewColumnFactory)について書きました。
次にその列情報に沿って描画をする部分の解説を行います。
IColumnDrawer と IColumnDrawerFactory
フィールドとプロパティ情報を元に列情報を表現する SheetViewFieldColumn 及び SheetViewPropertyColumn は IColumnDrawer をメンバ変数に持ち、実際の列の描画は IColumnDrawer に移譲しています。
public sealed class SheetViewFieldColumn : ISheetViewColumn
{
private readonly FieldInfo _fieldInfo;
private readonly IColumnDrawer _drawer;
// 中略…
public void Draw(Rect rect, object rowData)
{
var next = _drawer.Draw(rect, _fieldInfo.GetValue(rowData));
if (next != null)
{
_fieldInfo.SetValue(rowData, next);
}
}
}
ではその IColumnDrawer はいつどのようにして生成されているのかというと…
これは任意に作っても良いようにインターフェイスとして振る舞いのみを定義してあるのですが、 DefaultSheetViewColumnFactory が生成の一例として実装されています。
private readonly IColumnDrawerFactory _columnDrawerFactory;
// 中略…
public ISheetViewColumn[] CreateTreeViewColumns<TRowData>()
{
var type = typeof(TRowData);
var fields = GetFields(type)
.Select(field => new SheetViewFieldColumn(field, _columnDrawerFactory.Create(field.FieldType)))
.Cast<ISheetViewColumn>()
.ToArray();
// 略...
}
IColumnDrawerFactory を使用して型情報を元に生成しています。
ここもインターフェイスにしてユーザーが自由にFactoryを定義できるようにしてあるのですが、こチラに関しても DefaultColumnDrawerFactory として取り敢えず使えるようにした実装が用意されています。
DefaultColumnDrawerFactory
public sealed class DefaultColumnDrawerFactory : IColumnDrawerFactory
{
public IColumnDrawer Create(Type type)
{
if (type == typeof(bool))
{
return new ToggleColumnDrawer();
}
if (type == typeof(int))
{
return new IntColumnDrawer();
}
// 略...
}
}
中身はとてもシンプルです。
型情報に対応した具象クラスを生成して返却しているだけです。
この記事を執筆している(2025/12/19)現在では、下記の描画クラスを用意しています。
| 型 | 描画クラス |
|---|---|
bool |
ToggleColumnDrawer |
int |
IntColumnDrawer |
long |
LongColumnDrawer |
float |
FloatColumnDrawer |
double |
DoubleColumnDrawer |
Vector2 |
Vector2ColumnDrawer |
Vector3 |
Vector3ColumnDrawer |
Vector4 |
Vector4ColumnDrawer |
string |
TextColumnDrawer |
type.IsEnum |
EnumColumnDrawer |
シートで管理したくなるような標準的な型情報は一通り網羅しています。
これらの実装も至ってシンプルで、EditorGUI の機能を使って値が編集できるようにしてあるだけです。
public sealed class TextColumnDrawer : IColumnDrawer
{
public object Draw(Rect rect, object value)
{
var currentValue = (string)value;
using var scope = new EditorGUI.ChangeCheckScope();
var nextValue = EditorGUI.TextField(rect, currentValue);
return scope.changed ? nextValue : null;
}
}
例えば、これをスライダーを使って編集できるようにしたいとなったら下記のようなクラスを作れば簡単に拡張できます。
public sealed class IntSliderColumnDrawer : IColumnDrawer
{
private readonly int _min;
private readonly int _max;
public IntSliderColumnDrawer(int min, int max)
{
_min = min;
_max = max;
}
public object Draw(Rect rect, object value)
{
var currentValue = (int)value;
using var scope = new EditorGUI.ChangeCheckScope();
var nextValue = EditorGUI.IntSlider(rect, currentValue, _min, _max);
return scope.changed ? nextValue : null;
}
}
最小値と最大値の設定は少し工夫が必要になりますが、下記のように属性経由で値を設定できるようにするとデータ型の宣言と同じ場所に最小値と最大値を設定できて分かりやすいと思います。
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class IntSliderParamsAttribute : Attribute
{
public int Min { get; }
public int Max { get; }
public IntSliderParamsAttribute(int min, int max)
{
Min = min;
Max = max;
}
}
public sealed class IntSliderColumnDrawerFactory : IColumnDrawerFactory
{
private const int DefaultMin = 0;
private const int DefaultMax = 100;
public IColumnDrawer Create(Type type)
{
// サンプル実装のためエラー処理は省きます
if (type != typeof(int)) return null;
var attr = type.GetCustomAttribute<IntSliderParamsAttribute>();
return attr != null
? new IntSliderColumnDrawer(attr.Min, attrMax)
: new IntSliderColumnDrawer(DefaultMin, DefaultMax);
}
}
public sealed class ExampleData
{
// このフィールドはSheetViewで編集可能な列として扱い、IntSliderを使って編集できるようにする
// ↑というのをフィールド宣言時に指定ができる!
[SheetViewUseMember, IntSliderParams(0, 10)] private int exampleValue;
}
このように型情報に合わせて自由に描画方法を定義できるので、自身の作っているゲームやアプリの仕様に合わせて拡張しやすい構造になっています。
終わりに
今回は自身の作った EditorSheetView の列描画の設計と拡張方法について説明してみました。
次回はこれらで構成されたシート管理用のTreeViewをもう少し使いやすくするために実装した部分について解説できればと思います。
