Unity Odin从入门到精通(六):自定义绘制器

自定义定制特性绘制器:流程如下所示:
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的三个参数来依次设置绘制优先级。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity3D是一款非常流行的游戏引擎,它广泛应用于游戏开发、虚拟现实、增强现实等领域。对于初学者来说,学习Unity3D需要了解其基本概念、操作技巧和流程。为了从入门到精通,需要掌握以下三个阶段: 第一阶段是入门阶段。这个阶段的关键是掌握Unity3D的基本概念和操作方法。首先,需要了解Unity3D的界面结构和菜单命令,熟悉不同面板的功能,包括场景窗口、资源窗口、控制台、层次结构等。其次,需要掌握Unity3D中的对象、组件和预制件的概念。通过学习Object和Component的结构,了解它们的特性和作用,明白游戏对象的层级关系、物理模拟和碰撞检测等原理。最后,需要掌握脚本编写的基础语法,理解脚本与其他组件的交互方式和生命周期。 第二阶段是中级阶段。在入门阶段的基础上,需要进一步深入了解Unity3D引擎的高级技术和实际应用。主要包括:游戏设计模式、界面布局和UI设计、动画控制和剪辑编辑、材质和着色的使用、粒子系统和特效处理等。此外,需要了解Unity3D的性能优化和调试方法,运用Profiler、Frame Debugger等工具分析游戏引擎的内部运行机制,减少游戏卡顿和崩溃的情况。 第三阶段是精通阶段。这个阶段的关键是掌握Unity3D引擎的高级特性和复杂游戏的开发流程。主要涉及:脚本优化和高级算法的实现、网络游戏开发和多人游戏场景同步、人工智能和路径规划等。此外,需要了解Unity3D的插件开发和资产管理、持续集成和版本控制、移动平台和AR/VR领域的开发规范等。最终,达到像Unity3D官方开发者一样的能力水平,能够独立完成复杂游戏的开发和运营,或参与到较大规模的团队开发项目中,成为一名优秀的Unity3D开发者。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值