自定义定制特性绘制器:流程如下所示:
1.首先创建一个继承自Attribute类型并且应用范围为字段或者属性的定制特性类型;然后将该定制特性类型应用到想要进行自定义绘制的字段或者属性上面。参考代码如下所示:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class HealthBarAttribute : Attribute
{
public float MaxHealth { get; private set; }
public HealthBarAttribute(float maxHealth)
{
this.MaxHealth = maxHealth;
}
}
public class HealthBarExample : MonoBehaviour
{
// 在该字段上应用定制特性类型,并且交给该定制特性类型关联的定制特性绘制器类型来进行自定义绘制
[HealthBarAttribute(100)]
public float Health;
}
2.首先在编辑器环境下创建一个继承自OdinAttributeDrawer类型的定制特性绘制器类型;然后在泛型参数中指定该定制特性绘制器类型关联的定制特性类型和该定制特性类型应用的字段或者属性的类型;最后重写DrawPropertyLayout函数来进行自定义绘制。参考代码如下所示:
#if UNITY_EDITOR
public class HealthBarAttributeDrawer : OdinAttributeDrawer<HealthBarAttribute, float>
{
protected override void DrawPropertyLayout(GUIContent label)
{
// 调用CallNextDrawer函数就会进入Odin绘制器链,进而依次执行每个定制特性绘制器类型。
this.CallNextDrawer(label);
// 绘制一个血条.
Rect rect = EditorGUILayout.GetControlRect();
float width = Mathf.Clamp01(this.ValueEntry.SmartValue / this.Attribute.MaxHealth);
SirenixEditorGUI.DrawSolidRect(rect, new Color(0f, 0f, 0f, 0.3f), false);
SirenixEditorGUI.DrawSolidRect(rect.SetWidth(rect.width * width), Color.red, false);
SirenixEditorGUI.DrawBorders(rect, 1);
}
}
#endif
自定义值绘制器:流程如下所示:
1.首先创建一个可以被序列化的类型,该类型既可以是引用类型,也可以是值类型;然后用该类型创建一个想要进行自定义绘制的字段或者属性。参考代码如下所示:
// 自定义可以序列化的值类型MyStruct
[Serializable]
public struct MyStruct
{
public float X;
public float Y;
}
public class CustomDrawerExample : MonoBehaviour
{
// 通过MyStruct类型来创建一个想要进行自定义绘制的字段MS
public MyStruct MS;
}
2.首先在编辑器环境下创建一个继承自OdinValueDrawer类型的值绘制器类型;然后在泛型参数中指定该值绘制器类型关联的类型;最后重写DrawPropertyLayout函数来进行自定义绘制。参考代码如下所示:
#if UNITY_EDITOR
public class CustomStructDrawer : OdinValueDrawer<MyStruct>
{
protected override void DrawPropertyLayout(GUIContent label)
{
// 绘制字段或者属性的标签
var rect = EditorGUILayout.GetControlRect();
if (label != null)
{
rect = EditorGUI.PrefixLabel(rect, label);
}
// 绘制X和Y两个滑动条并将拖拽值同步到字段或者属性的数值里面
var prev = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 20;
MyStruct value = this.ValueEntry.SmartValue;
value.X = EditorGUI.Slider(rect.AlignLeft(rect.width * 0.5f), "X", value.X, 0, 1);
value.Y = EditorGUI.Slider(rect.AlignRight(rect.width * 0.5f), "Y", value.Y, 0, 1);
EditorGUIUtility.labelWidth = prev;
this.ValueEntry.SmartValue = value;
}
}
#endif
自定义组绘制器:流程如下所示:
1.首先创建一个继承自PropertyGroupAttribute类型的组定制特性类型;接着在该组定制特性类型里面提供一个只接收一个组标识参数的构造函数以及提供一个不仅接收组标识参数,而且还接收扩展参数的构造函数,进而让具有相同组标识的组定制特性类型实例不必每次都指定扩展参数的数值;然后重写该组定制特性类型的CombineValuesWith函数来将具有相同组标识的组定制特性类型实例进行混合操作;最后将该组定制特性类型应用到想要进行自定义绘制的字段或者属性或者函数上面。参考代码如下所示:
public class ColoredFoldoutGroupAttribute : PropertyGroupAttribute
{
// 由于定制特性构造函数中没法接收Struct类型参数,所以此处就将颜色对象拆成颜色分量形式。
public float R, G, B, A;
// 只接收组标识参数构造函数。目的是在使用该定制特性时,不必每次都指定颜色分量参数数值。
public ColoredFoldoutGroupAttribute(string groupId) : base(groupId)
{
}
// 接收组标识参数和颜色分量参数。
public ColoredFoldoutGroupAttribute(string groupId, float r, float g, float b, float a) : base(groupId)
{
R = r;
G = g;
B = b;
A = a;
}
// 具有相同组标识的组定制特性实例会进入该函数里面,用来进行混合操作。
protected override void CombineValuesWith(PropertyGroupAttribute other)
{
var otherAttr = (ColoredFoldoutGroupAttribute)other;
R = Math.Max(otherAttr.R, R);
G = Math.Max(otherAttr.G, G);
B = Math.Max(otherAttr.B, B);
A = Math.Max(otherAttr.A, A);
}
}
public class CustomGroupExample : SerializedMonoBehaviour
{
// 应用组定制特性类型到字段上
[ColoredFoldoutGroupAttribute("MyGroup1", 1, 0, 0, 1)]
public int MyIntData;
// 应用组定制特性类型到属性上
[ShowInInspector, ColoredFoldoutGroupAttribute("MyGroup1", 0, 1, 0, 1)]
public float MyFloatData
{
get;
set;
}
// 应用组定制特性类型到函数上
[ShowInInspector, ColoredFoldoutGroupAttribute("MyGroup1")]
public string MyStringData()
{
return "test";
}
}
2.首先在编辑器环境下创建一个继承自OdinGroupDrawer类型的组绘制器类型;接着在泛型参数中指定该组绘制器类型关联的组定制特性类型;然后重写Initialize数来进行初始化该组绘制器实例;最后重写DrawPropertyLayout函数来进行自定义绘制。参考代码如下所示:
#if UNITY_EDITOR
public class ColoredFoldoutGroupAttributeDraw : OdinGroupDrawer<ColoredFoldoutGroupAttribute>
{
private LocalPersistentContext<bool> isExpanded;
protected override void Initialize()
{
// 获取本地指定名称的持续化数值,没有找到就获取第二个参数指定的默认值
isExpanded = this.GetPersistentValue("ColoredFoldoutGroupAttributeDrawer.isExpanded",
GeneralDrawerConfig.Instance.ExpandFoldoutByDefault);
}
protected override void DrawPropertyLayout(GUIContent label)
{
// 压入一个颜色值,必须和PopColor成组使用
GUIHelper.PushColor(new Color(Attribute.R, Attribute.G, Attribute.B, Attribute.A));
SirenixEditorGUI.BeginBox();
SirenixEditorGUI.BeginBoxHeader();
GUIHelper.PopColor();
// 用指定的标签作为标题来绘制一个折叠框
isExpanded.Value = SirenixEditorGUI.Foldout(isExpanded.Value, label);
SirenixEditorGUI.EndBoxHeader();
if (SirenixEditorGUI.BeginFadeGroup(this, isExpanded.Value))
{
// 展开折叠框时就绘制该绘制器关联的组定制特性类型应用的所有成员(字段、属性、函数)
for (int i = 0; i < Property.Children.Count; i++)
{
Property.Children[i].Draw();
}
}
SirenixEditorGUI.EndFadeGroup();
SirenixEditorGUI.EndBox();
}
}
#endif
约束自定义绘制器:如下所示:
1.当从Odin绘制类型(如:OdinAttributeDrawer、OdinValueDrawer、OdinGroupDrawer等)派生自定义绘制器类型时,可以使用C#的通用约束规则来指定想要自定义绘制器绘制的类型和值。参考代码如下所示:
public abstract class Reference<TValue, TVariable> {}
public abstract class Variable<TValue> : ScriptableObject {}
public class CustomValueDrawer<TReference, TVariable, TValue> : OdinValueDrawer<TReference>
where TReference: Reference<TValue, TVariable>
where TVariable : Variable<TValue>
{}
2.通过重写自定义绘制器类型的CanDrawXXX函数(如:CanDrawTypeFilter、CanDrawValueProperty等)来判定是否启用绘制。参考代码如下所示:
public abstract class Weapon {}
public class CustomValueDrawer<T> : OdinValueDrawer<T> where T: Weapon
{
public override bool CanDrawTypeFilter(Type type)
{
return type != typeof(Sword);
}
}
3.由于使用C#通用约束比调用CanDrawXXX函数的性能要高很多,所以尽量使用C#通用约束来缩小CanDrawXXX函数的查找范围。
注意事项:如下所示:
1.OdinDrawer类型中的Property属性记录了该绘制器类型关联的类型应用到的所有成员,如:字段、属性、函数等。
2.DrawPropertyLayout函数中的label参数是可选的。该参数值既可以为空,也可以为字段或者属性或者函数的绘制标签。
3.我们可以使用EditorGUI的BeginChangeCheck函数和EndChangeCheck函数来设置只有在检查面板中的组件发生数据变更时才会最终同步该数据到关联成员。参考代码如下所示:
public long longValue;
[OnInspectorGUI]
public void OnInspectorGUI()
{
// 只有当检查面板中的int类型输入框中的数值发生变更时,才会将数值同步到关联的longValue字段上
EditorGUI.BeginChangeCheck();
long newValue = SirenixEditorFields.IntField((int)this.longValue);
if (EditorGUI.EndChangeCheck())
{
this.longValue = newValue;
}
}
4.在进行绘制时,使用Odin的Rect结构比使用Unity的IMGUI Layout结构要更加的高效。
5.可以使用DrawerPriorityAttribute的三个参数来依次设置绘制优先级。