CustomPropertyDrawer
Unity3D CustomPropertyDrawer 自定义属性绘制器
api文档
- 该文档中的```EditorGUI.BeginProperty()```和```EditorGUI.EndProperty()```,不好用
- 参考案例:
- 直接看Unity中你感兴趣的渲染方式的实现方式:
Packages/com.unity.ugui/Editor/UI/PropertyDrawers/...
FontDataDrawer
SpriteStateDrawer
ColorBlockDrawer
NavigationDrawer
- 也可以直接在
Project
视图搜索Drawer
,搜索范围选择In Packagers
- 直接看Unity中你感兴趣的渲染方式的实现方式:
需要做的事
- 绘制
- 重写```GetPropertyHeight``` 返回真正的高度
- 不要忘记标题占了1行
- 考虑展开 / 收缩 的情况, 返回不同的高度,
- 收缩时,高度=```EditorGUIUtility.singleLineHeight```
- 展开时,高度=(1 + 标题以外所有内容的行数) x ```EditorGUIUtility.singleLineHeight ``` + ```EditorGUIUtility.standardVerticalSpacing``` x (所有行数-1)
基本常识:
- ```OnGUI(Rect position, SerializedProperty property, GUIContent label)```中
- ```position``` 是这个变量的起始位置
- ```property``` 是这个变量的```SerializedProperty```对象
- 通过这个对象,可以获取到变量的值 ```property.FindPropertyRelative(paramName).(intValue / stringValue / vector3Value / enumValueIndex / ...)```
- ```lable.text``` 是变量名称
- 默认每行高度是: ```EditorGUIUtility.singleLineHeight```
- 默认行间距是: ```EditorGUIUtility.standardVerticalSpacing```
- 想要缩进/反缩进 1个单位, 可以使用: ```EditorGUI.indentLevel++``` 和 ```EditorGUI.indentLevel--```
- 对于不需要自定义渲染方式的字段 使用 ```EditorGUI.PropertyField```执行默认的渲染方案,
- 所有要绘制的内容,推荐使用```EditorGUI```类, 而不是```GUI```类
- ```EditorGUI```类的方法, 会自动处理缩进的问题
- ```GUI```类的方法, 不会自动处理缩进的问题
- 比如``` GUI.Button(rect, "↑"))```,不受缩进的影响,所有要额外把```rect.x```加上缩进的距离```35```比较合适
小技巧
- 绘制展开 / 折叠按钮
- 使用```EditorGUI.Foldout``` -> ``` if (foldout = EditorGUI.Foldout(rect, foldout,$"{label.text} 更多内容:{}")```
- 标题行除了显示变量名称以外, <font color="#aaaa00">其实可以显示更多信息, 以便在没有展开的情况下就可以把 关键信息 显示到标题行的标题后面</font>
⚠️ 注意:
- 必须十分清除, 自己的绘制方案, 占用了多少高度, 否则会出现绘制不全的情况
- 绘制时
- 关于```layout```周边的方法
- 在```EditorGUI.BeginProperty()```和```EditorGUI.EndProperty```包裹的范围里,不可以使用 ```layout``` 相关的方法, 否则会报错:```ArgumentException: Getting control 1's position in a group with only 1 controls when doing repaint```
- 在```EditorGUI.BeginProperty()```外面, 执行```layout```相关的方法, 不生效不显示
简单举例
[Serializable]
public class RotaAtAxisData
{
public Transform self;
public Vector3 axis = Vector3.up;
public float angleTotal;
public float duration = 1;
public Space space = Space.World;
}
默认显示如下:
重写后↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓如下:
-
变量名字后面直接把关键信息,显示在变量名称后面,即便不展开,也可以看到关键信息
-
允许点击按钮,直接选择6个标准轴向作为旋转轴
-
使用2个
Toggle
来渲染space
,要选择Space.World
/Space.Self
时,直接点就行,下拉框需要点2次,不方便 -
Bug:
- 当视图过于小时,
Axis
的XYZ
会被挤到下一行,导致覆盖下一行内容,这个问题暂时没有解决方案
- 当视图过于小时,
-
完整代码如下:
using System;
using System.Collections;
using UnityEngine;
using UnityEditor;
namespace BaseToolsForUnity
{
/// <summary>
/// RotaAtAxisData 检视视图个性化渲染器<br/>
/// </summary>
[CustomPropertyDrawer(typeof(TransformTween.RotaAtAxisData))]
public class RotaAtAxisDataDrawer : PropertyDrawer
{
// 自身的所有变量
private SerializedProperty self;
private SerializedProperty axis;
private SerializedProperty angleTotal;
private SerializedProperty duration;
private SerializedProperty space;
private float axisButtonWidth = 20;
private float axisButtonSpaceHor = 5;
private float spaceToggleWidth = 70;
/// <summary>
/// <see langword="true"/>:展开<br/>
/// </summary>
private bool foldout = false;
private void Init(SerializedProperty property)
{
self = property.FindPropertyRelative("self");
axis = property.FindPropertyRelative("axis");
angleTotal = property.FindPropertyRelative("angleTotal");
duration = property.FindPropertyRelative("duration");
space = property.FindPropertyRelative("space");
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Init(property);
var rect = position;
rect.height = EditorGUIUtility.singleLineHeight;
//if (foldout = EditorGUI.Foldout(rect, foldout, new GUIContent($"{label.text}({typeof(TransformTween.RotaAtAxisData).Name})")))
//if (foldout = EditorGUI.Foldout(rect, foldout, new GUIContent($"{label.text} Axis:[{axis.vector3Value}] Angle:[{angleTotal.floatValue}] Time:[{duration.floatValue}]")))
// 折叠 / 展开 (附加关键信息到标题行后面)
if (foldout = EditorGUI.Foldout(rect, foldout, EditorGUIUtility.TrTextContent($"{label.text} Axis:[{axis.vector3Value}] Angle:[{angleTotal.floatValue}] Time:[{duration.floatValue}] Space:[{((Space)space.enumValueIndex)}]")))
{
//// 绘制标题
//EditorGUI.LabelField(rect, $"{label.text}({typeof(TransformTween.RotaAtAxisData).Name})");
// 开始缩进 // 开始绘制内部的字段
++EditorGUI.indentLevel;
// 绘制 self 字段
rect.y += rect.height;
rect.y += EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(rect, self);
rect.y += rect.height;
rect.y += EditorGUIUtility.standardVerticalSpacing;
#region 绘制 轴向选择 按钮 到1行
var rectBackUp = rect;
rect.x = 35;
rect.width = axisButtonWidth;
if (GUI.Button(rect, "↑"))
{
axis.vector3Value = Vector3.up;
}
rect.x += rect.width;
rect.x += axisButtonSpaceHor;
if (GUI.Button(rect, "↓"))
{
axis.vector3Value = Vector3.down;
}
rect.x += rect.width;
rect.x += axisButtonSpaceHor;
if (GUI.Button(rect, "F"))
{
axis.vector3Value = Vector3.forward;
}
rect.x += rect.width;
rect.x += axisButtonSpaceHor;
if (GUI.Button(rect, "B"))
{
axis.vector3Value = Vector3.back;
}
rect.x += rect.width;
rect.x += axisButtonSpaceHor;
if (GUI.Button(rect, "←"))
{
axis.vector3Value = Vector3.left;
}
rect.x += rect.width;
rect.x += axisButtonSpaceHor;
if (GUI.Button(rect, "→"))
{
axis.vector3Value = Vector3.right;
}
// 恢复 rect
rect = rectBackUp;
#endregion
rect.y += rect.height;
rect.y += EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(rect, axis);
// 绘制要旋转的总角度
rect.y += rect.height;
rect.y += EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(rect, angleTotal);
// 绘制动画时间
rect.y += rect.height;
rect.y += EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(rect, duration);
// 绘制空间类型
rect.y += rect.height;
rect.y += EditorGUIUtility.standardVerticalSpacing;
// 绘制空间类型的 名称
EditorGUI.LabelField(rect, $"{space.displayName}");
// 绘制空间类型 世界空间
rect.width = spaceToggleWidth;
rect.x += EditorGUIUtility.labelWidth;
var isWorld = EditorGUI.ToggleLeft(rect, Space.World.ToString(), (Space)space.enumValueIndex == Space.World);
space.enumValueIndex = isWorld ? (int)Space.World : (int)Space.Self;
// 绘制空间类型 自身空间
rect.x += spaceToggleWidth;
var isLocal = EditorGUI.ToggleLeft(rect, Space.Self.ToString(), (Space)space.enumValueIndex == Space.Self);
space.enumValueIndex = !isLocal ? (int)Space.World : (int)Space.Self;
// 结束缩进 // 结束绘制内部的字段
--EditorGUI.indentLevel;
}
}
/// <summary>
/// 重写 GetPropertyHeight 方法
/// </summary>
/// <param name="property"></param>
/// <param name="label"></param>
/// <returns></returns>
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
Init(property);
// 折叠状态
if (!foldout)
{
return EditorGUIUtility.singleLineHeight;
}
// 展开状态
// 行数 = 视觉上看到的行数 + 1(标题行)
// 间隙数量 = 行数 -1
var height = EditorGUIUtility.singleLineHeight * 8 +
EditorGUIUtility.standardVerticalSpacing * 7;
return height;
}
}
}