Unity Odin特性解析

Odin特性解析

一、前言

Odin是Unity里一款非常强大的编辑器插件,它提供了很多丰富的特性(Attribute),可以用来丰富编辑器的显示和交互。下面列举一些好用的特性。

二、特性大全

2.1 限制相关

2.1.1 【AssetsOnly】仅Project资源

  • 功能:
    限制仅可选择Project下的资源,Scene下的不能选
  • 示例:
    [AssetsOnly]
    public GameObject SomePrefab;

2.1.2 【SceneObjectsOnly】仅Scene资源

  • 功能:
    限制仅可选择Scene下的资源,Project下的不能选
  • 示例:
    [SceneObjectsOnly]
    public GameObject SomePrefab;

2.1.3 【MinValue】&【MaxValue】最小值&最大值

  • 功能:
    限制最小指和最大值
  • 示例:
    [MinValue(0)] [MaxValue(5)]
    public int val;

2.1.4 【MinMaxSlider】最小最大滑动条(修饰Vector2)

  • 功能:
    也是限制最小值和最大值,但有一个滚动条。用于修饰Vector2
  • 示例:
    [MinMaxSlider(-10, 10)]
    public Vector2 MinMaxValueSlider = new Vector2(-7, -2);
  • 效果图:
    在这里插入图片描述

2.1.5 【Range】&【PropertyRange】字段&属性范围

  • 功能:
    Range用来限制字段范围;
    PropertyRange 更强大一点,还可以限制属性范围;正常Unity内置的功能是显示不了字段的,这里我们用了Odin里的【ShowInInspector】特性,来把Property 属性显示出来

  • 示例:
    [Range(0, 10)]
    public int Field = 2;

    [ShowInInspector,PropertyRange(0, 10)]
    public int Property { get; set; }

  • 效果图:
    在这里插入图片描述

  • 进阶用法:
    我们上面的最小最大值都是固定值,其实还可以用动态的字段值来作范围,如下面的例子

  • 进阶示例:
    [PropertyRange(0, “Max”)]
    public int Dynamic = 6;

    public int Max = 100;

2.1.6 【Required】限制资源不能空

  • 功能:
    Required可以用来限制一个资源不能为空

  • 语法:
    [Required(“当资源为空时的提示,可不填”)]

  • 示例:
    [Required]
    public GameObject MyGameObject;

    [Required(“Custom error message.”)]
    public Rigidbody MyRigidbody;

  • 效果图:
    -

2.1.7 【ValidateInput】赋值验证

  • 功能:
    ValidateInput是个很实用的功能,当用户对字段赋值后,会触发回调方法。然后我们可以写代码逻辑来对用户赋值的实体进行检查。如果检查不通过,可以给予提示

  • 语法:
    [ValidateInput(“回调方法名”, “默认提示”)]

  • 示例

[ValidateInput("HasMeshRendererDynamicMessageAndType", "Prefab must have a MeshRenderer component")]
public GameObject DynamicMessageAndType;
 
//返回类型为bool,表示是否通过检测
private bool HasMeshRendererDynamicMessageAndType(GameObject gameObject, ref string errorMessage, ref InfoMessageType? messageType)
    {
    	//没有赋值时,通过检测
        if (gameObject == null) return true;

		//如果赋值的GameObject没有MeshRenderer,return false检测不通过
        if (gameObject.GetComponentInChildren<MeshRenderer>() == null)
        {
            // 设置提示信息
            errorMessage = "\"" + gameObject.name + "\" should have a MeshRenderer component";

            // //设置警告类型
            messageType = InfoMessageType.Warning; 

            return false;
        }

        return true;
    }

2.1.8 【TypeFilter】限制类型

  • 功能:
    TypeFilter 可以用来限制只能选择指定的类型集合
  • 示例
    [ShowInInspector]
    [TypeFilter("GetFilteredTypeList")]
    public BaseClass A, B;

    [ShowInInspector]
    [TypeFilter("GetFilteredTypeList")]
    public BaseClass[] Array = new BaseClass[3];

    public IEnumerable<Type> GetFilteredTypeList()
    {
        var q = typeof(BaseClass).Assembly.GetTypes()
            .Where(x => !x.IsAbstract)                                          // 排除 BaseClass
            .Where(x => !x.IsGenericTypeDefinition)                             // 排除 泛型,如C1<>
            .Where(x => typeof(BaseClass).IsAssignableFrom(x));                 // 排除没有继承BaseClass的类型

        // 增加特定的类
        q = q.AppendWith(typeof(C1<>).MakeGenericType(typeof(GameObject)));
        q = q.AppendWith(typeof(C1<>).MakeGenericType(typeof(AnimationCurve)));
        q = q.AppendWith(typeof(C1<>).MakeGenericType(typeof(List<float>)));

        return q;
    }

    public abstract class BaseClass
    {
        public int BaseField;
    }

    public class A1 : BaseClass { public int _A1; }
    public class A2 : A1 { public int _A2; }
    public class A3 : A2 { public int _A3; }
    public class B1 : BaseClass { public int _B1; }
    public class B2 : B1 { public int _B2; }
    public class B3 : B2 { public int _B3; }
    public class C1<T> : BaseClass { public T C; }
  • 效果图
    在这里插入图片描述

2.1.9 【ReadOnly】只读

  • 功能:
    ReadOnly 可以让显示在面板上的属性不能修改

  • 示例:
    [ReadOnly]
    public string MyString = “This is displayed as text”;

    [ReadOnly]
    public int MyInt = 9001;

    [ReadOnly]
    public int[] MyIntList = new int[] { 1, 2, 3, 4, 5, 6, 7, };

  • 效果图:
    在这里插入图片描述

2.2 通用特性

这里列举一些实用但不好归类的特性

2.2.1 【ShowInInspector】显示在Inspector面板

  • 功能:
    ShowInInspector 可以让属性,静态变量等平常不能再Inspector显示的字段显示在面板。挺使用的一个功能

  • 示例:
    [ShowInInspector]
    public string DelayedProperty { get; set; }

    [ShowInInspector]
    public static string testStr;

  • 效果图:
    在这里插入图片描述

2.2.2 【OnValueChanged】值变化回调

  • 功能:
    OnValueChanged 可以在字段的值变化后,调用回调方法

  • 语法:
    [OnValueChanged(“回调方法名”)]

  • 示例:
    [OnValueChanged(“OnValueChanged”)]
    public int val;

    private void OnValueChanged()
    {
    Debug.Log(“Value changed!”);
    }

  • 效果图:
    在这里插入图片描述

2.2.3 【Delayed】&【DelayedProperty】延迟

  • 功能:
    Delayed 和 DelayedProperty 可以搭配上面的OnValueChanged使用,OnValueChanged能在值变化后回调,但每次变化都会回调,比如稍微拖拉一下值,你看上面的打印,回调了174次。这时Delayed 就派上用场了,可以在值变化完成后才回调,就不会有那么多回调了。Delayed 修饰通常字段,DelayedProperty 可以用来修饰属性

  • 示例:

    [Delayed]
    [OnValueChanged(“OnValueChanged”)]
    public int DelayedField;

    [ShowInInspector, DelayedProperty]
    [OnValueChanged(“OnValueChanged”)]
    public string DelayedProperty { get; set; }

    private void OnValueChanged()
    {
    Debug.Log(“Value changed!”);
    }

  • 效果图:
    在这里插入图片描述

2.2.4 【GUIColor】变色

  • 功能:
    GUIColor 可以让字段变色,也可以让按钮变色,还能颜色渐变。接下来我们看下官方给的示例
  • 示例:
    [GUIColor(0.3f, 0.8f, 0.8f, 1f)]
    public int ColoredInt1;

    [GUIColor(0.3f, 0.8f, 0.8f, 1f)]
    public int ColoredInt2;

    [ButtonGroup]
    [GUIColor(0, 1, 0)]
    private void Apply()
    {
    }

    [ButtonGroup]
    [GUIColor(1, 0.6f, 0.4f)]
    private void Cancel()
    {
    }

    [InfoBox("You can also reference a color member to dynamically change the color of a property.")]
    [GUIColor("GetButtonColor")]
    [Button("I Am Fabulous", ButtonSizes.Gigantic)]
    private static void IAmFabulous()
    {
    }

    [Button(ButtonSizes.Large)]
    [GUIColor("@Color.Lerp(Color.red, Color.green, Mathf.Abs(Mathf.Sin((float)EditorApplication.timeSinceStartup)))")]
    private static void Expressive()
    {
    }

    private static Color GetButtonColor()
    {
        Sirenix.Utilities.Editor.GUIHelper.RequestRepaint();
        return Color.HSVToRGB(Mathf.Cos((float)UnityEditor.EditorApplication.timeSinceStartup + 1f) * 0.225f + 0.325f, 1, 1);
    }
  • 效果图:
    在这里插入图片描述

2.2.4 【ColorPalette】颜色调色板

嘿嘿,说到颜色,我们讲下颜色调色板,颜色调色板也是好用的功能。因为一个游戏通用是由3~4种主色调形成的。把这些颜色放在一个调色板里,一来不会出错,二来可以更方便的选择颜色

  • 功能:
    ColorPalette 用来修饰Color字段,可以在旁边列出一个调色板,供用户选择颜色

  • 语法:
    [ColorPalette] 可以在下拉框里任意选择调色板,接着可以选择该调色板下颜色

    [ColorPalette(“调色板名称”)] 可以指定调色板名称,这样直接出现该调色板的颜色

  • 示例:
    //不指定调色板名称
    [ColorPalette]
    public Color ColorOptions;

    //加点间距,不然太挤,不太好说明
    [PropertySpace(SpaceBefore = 60)]

    指定调色板名称为Underwater
    [ColorPalette(“Underwater”)]
    public Color UnderwaterColor;

  • 效果图:
    在这里插入图片描述

2.2.5 【Title】& 【HideLabel】增加标题&隐藏字段名

  • 功能:
    Title可以用来增加标题,HideLabel可以用来隐藏字段名

  • 示例:
    [Title(“Wide Colors”)]
    [HideLabel]
    [ColorPalette(“Fall”)]
    public Color WideColor1;

  • 效果图:
    可以看到原字段名隐藏掉了,只显示我们定义的Title和调色板

2.2.6 【DisplayAsString】显示内容

  • 功能:
    DisplayAsString可以把string字段的内容显示在Inspector面板上
  • 示例:
    [HideLabel]
    [DisplayAsString]
    public string Title = “I am content”;
  • 效果图:
    可以看到字段名称隐藏了,而字段内容显示了出来
    在这里插入图片描述

2.2.7 【PropertyOrder】字段优先级

  • 功能:
    PropertyOrder 可以控制字段的优先级,优先级值越小排在越上面

  • 示例:
    [PropertyOrder(1)]
    public int Second;

    [PropertyOrder(-1)]
    public int First;

  • 效果图:
    在这里插入图片描述

2.2.7 【PropertySpace】字段间隔

  • 功能:
    PropertySpace 可以用来设置字段间隔,前面在讲颜色调色板时用到了PropertySpace

  • 示例:
    public int Space;

    [PropertySpace(SpaceBefore = 10, SpaceAfter = 20)]
    public int Space2;

    public int Space3;

  • 效果图:
    看到效果图就一目了然了
    在这里插入图片描述

2.2.8 【Searchable】查找

  • 功能:
    Searchable 可以用来标记类和列表,会出现一个搜索框,来搜索字段名或内容与输入内容匹配的项。我们来看下官方的示例
  • 示例:
    [Searchable]
    public ExampleClass searchableClass = new ExampleClass();
    

    [Serializable]
    public class ExampleClass
    {
        public string SomeString = "Saehrimnir is a tasty delicacy";
        public int SomeInt = 13579;

        public DataContainer DataContainerOne = new DataContainer() { Name = "Example Data Set One" };
        public DataContainer DataContainerTwo = new DataContainer() { Name = "Example Data Set Two" };
    }

    [Serializable, Searchable] // You can also apply it on a type like this, and it will become searchable wherever it appears
    public class DataContainer
    {
        public string Name;
        public List<ExampleStruct> Data = new List<ExampleStruct>(Enumerable.Range(1, 10).Select(i => new ExampleStruct(i)));
    }

    [Serializable]
    public struct FilterableBySquareStruct : ISearchFilterable
    {
        public int Number;

        [ShowInInspector, DisplayAsString, EnableGUI]
        public int Square { get { return this.Number * this.Number; } }

        public FilterableBySquareStruct(int nr)
        {
            this.Number = nr;
        }

        public bool IsMatch(string searchString)
        {
            return searchString.Contains(Square.ToString());
        }
    }

    [Serializable]
    public struct ExampleStruct
    {
        public string Name;
        public int Number;
        public ExampleEnum Enum;

        public ExampleStruct(int nr) : this()
        {
            this.Name = "Element " + nr;
            this.Number = nr;

            this.Enum = (ExampleEnum)ExampleHelper.RandomInt(0, 5);
        }
    }

    public enum ExampleEnum
    {
        One, Two, Three, Four, Five
    }

  • 效果图:
    请添加图片描述

2.2.9 【MultiLineProperty】多行输入框

  • 功能:
    MultiLineProperty 可以用来修饰string字段和属性,让文本输入框变成指定的行数

  • 示例:
    [Multiline(10)]
    public string UnityMultilineField = “”;

    [InfoBox(“Odin supports properties, but Unity’s own Multiline attribute only works on fields.”)]
    [ShowInInspector]
    [MultiLineProperty(10)]
    public string OdinMultilineProperty { get; set; }

  • 效果图:
    可以看到都有了10行的输入框,MultiLineProperty 能修饰属性,内置的MultiLine不可以
    在这里插入图片描述

2.2.10 【EnumPaging】枚举分页

  • 功能:
    EnumPaging可以用来修饰枚举字段和属性,让枚举增加左右切换按钮

  • 示例:
    [EnumPaging] public SomeEnum SomeEnumField;

    public enum SomeEnum
    {
    A,
    B,
    C
    }

  • 效果图:
    请添加图片描述

2.2.11 【EnumToggleButtons】枚举下拉框转水平布局

  • 功能:
    EnumToggleButtons 可以去掉枚举本来的下拉框,转而以水平布局的形式列出所有选项
  • 示例:
    //不加修饰的枚举
    public SomeEnum OriginSomeEnum;
    
    //去掉下拉框,把枚举选项横向排列显示出来
    [EnumToggleButtons] 
    public SomeEnum SomeEnumField;

    public enum SomeEnum
    {
        First,
        Second,
        Third,
        Fourth,
        AndSoOn
    }
  • 效果图:
    请添加图片描述

2.2.12 【SuffixLabel】Label增加后缀

  • 功能:
    SuffixLabel 可以在输入框的末尾增加后缀,让使用工具的人知道当前是以什么为单位

  • 示例:
    [SuffixLabel(“ms”, Overlay = false)]
    public float Speed1;

    //Overlay表示后缀是否在输入框内
    [SuffixLabel(“ms”, Overlay = true)]
    public float Speed2;

  • 效果图:
    在这里插入图片描述

2.2.13 【InlineButton】在末尾增加按钮

  • 功能:
    InlineButton 修饰字段,可以在字段末尾处增加按钮
  • 语法:
    [InlineButton(“方法名”,“按钮名”)]
    按钮名可以为空,为空时以方法名命名按钮
  • 示例:
    [InlineButton("A")]
    public int InlineButton;
    
    [InlineButton("A")]
    [InlineButton("B", "Custom Button Name")]
    public int ChainedButtons;

    private void A()
    {
        Debug.Log("A");
    }

    private void B()
    {
        Debug.Log("B");
    }
  • 效果图:
    在这里插入图片描述

2.2.14 【LabelText】重命名

  • 功能
    LabelText修饰字段,是一个很实用的属性。可以把字段显示成自定义的内容。因为字段一般是定义成英文,我们可以使用LabelText来重命名成中文,这样更方便策划使用
  • 语法:
    [LabelText(“自定义内容”)]
  • 示例:
    public string str1 = "str1";

    [LabelText("使用中文显示str2")]
    public string str2 = "str2";
  • 效果图
    在这里插入图片描述

2.3 较特殊的特性

啥叫"较特殊的特性",跟"通用特性"有什么区别。也许是靠自己的感觉,把它们归类到这里;也许是上面已经列举了不少,感觉得列到新的组里面…

2.3.1 【Button】按钮

  • 功能:
    Button 用来修饰方法,会在Inspector窗口出现一个按钮,点击后执行方法的逻辑

  • 示例:
    [Button(“Hello”)]
    private void DefaultSizedButton()
    {
    Debug.Log(“Hello”);
    }

    [Button(“Hello,Doraemon”,buttonSize:ButtonSizes.Large),GUIColor(0,1,0)]
    private void Button2()
    {
    Debug.Log(“Hello ~ Doraemon”);
    }

  • 效果图:
    在这里插入图片描述

2.3.2 【FilePath】选择文件

  • 功能:
    FilePath 可以打开一个文件窗口来选择文件。好像容易重名,完整的命名空间是这个:[Sirenix.OdinInspector.FilePath],在重名时可以指定

  • 示例:
    //默认返回相对路径
    [FilePath]
    public string UnityProjectPath;

    //可以指定打开的目录(会返回与该目录的相对路径),指定文件格式,指定是否返回全路径,默认为false,即相对路径
    [FilePath(ParentFolder = “Assets/OdinLearn”, Extensions = “cs”, AbsolutePath = true)]
    public string ResourcePath;

  • 效果图:
    在这里插入图片描述
    在这里插入图片描述

2.3.3 【FolderPath】选择文件夹

FolderPath 用法和上面的FilePath差不多,只是选择的是文件夹,而上面选择的是文件

2.3.4 【OnInspectorInit】Inspector初始化回调

  • 功能:
    OnInspectorInit 可以在Inspector初始化回调时调用
  • 示例:
    //在Inspector窗口初始化绘制
    [OnInspectorInit("@TimeFoldoutWasOpened = DateTime.Now.ToString()")]
    public string TimeFoldoutWasOpened;
  • 效果图:
    在这里插入图片描述

2.3.5 【OnInspectorGUI】Inspector激活时一直回调

  • 功能:
    OnInspectorGUI 在Inspector窗口激活时,可以一直回调。可以修饰字段,也可以修饰方法。可以用来帮我们增加一些UI
  • 示例:
    //修饰字段
    [OnInspectorInit("@Texture = EditorIcons.OdinInspectorLogo")]
    [OnInspectorGUI("DrawPreview", append: true)]//append = true,表示回调方法DrawPreview在该字段绘制之后,再调用;false,则表示在原始绘制之前
    public Texture2D Texture;
	
	//把选择的图标再绘制一遍出来
    private void DrawPreview()
    {
        if (this.Texture == null) return;

        GUILayout.BeginVertical(GUI.skin.box);
        GUILayout.Label(this.Texture);
        GUILayout.EndVertical();
    }
    
    
    //修饰方法,直接绘制
    [OnInspectorGUI]
    private void OnInspectorGUI()
    {
        UnityEditor.EditorGUILayout.HelpBox("OnInspectorGUI can also be used on both methods and properties", UnityEditor.MessageType.Info);
    }
  • 效果图:
    在这里插入图片描述

2.3.6 【OnStateUpdate】每帧回调

  • 功能:
    OnStateUpdate 每帧回调,修饰字段,可以执行一些方法
  • 示例:
    public List<string> list;

    [OnStateUpdate("@#(list).State.Expanded = $value")]
    public bool ExpandList;

    [OnStateUpdate("@UnityEngine.Debug.Log(\"OnStateUpdate event invoked!\")")]
    public bool Test;
  • 效果图:

2.3.7 【CustomValueDrawer】自定义绘制

  • 功能:
    CustomValueDrawer 修饰字段,可以把本来的绘制方法改成自定义的绘制方法
  • 语法:
    CustomValueDrawer(“方法名”)
  • 示例:
    //原始
    public float From = 2, To = 7;
    
    //自定义显示字段1
    [CustomValueDrawer("MyCustomDrawerStatic")]
    public float CustomDrawerStatic;
    
    //自定义显示方法1,使用固定值
    private static float MyCustomDrawerStatic(float value, GUIContent label)
    {
        return EditorGUILayout.Slider(label, value, 0f, 10f);
    }
    

    //自定义显示字段2
    [CustomValueDrawer("MyCustomDrawerInstance")]
    public float CustomDrawerInstance;
    
    //自定义显示方法2,使用From & To 作为变量
    private float MyCustomDrawerInstance(float value, GUIContent label)
    {
        return EditorGUILayout.Slider(label, value, this.From, this.To);
    }

    //自定义显示字段3
    [CustomValueDrawer("MyCustomDrawerAppendRange")]
    public float AppendRange;
    
    //自定义显示方法3,增加盒子和Label
    private float MyCustomDrawerAppendRange(float value, GUIContent label, Func<GUIContent, bool> callNextDrawer)
    {
        SirenixEditorGUI.BeginBox();
        callNextDrawer(label);
        var result = EditorGUILayout.Slider(value, this.From, this.To);
        SirenixEditorGUI.EndBox();
        return result;
    }
    
    //自定义显示字段4,修饰数组
    [CustomValueDrawer("MyCustomDrawerArrayNoLabel")]
    public float[] CustomDrawerArrayNoLabel = new float[] { 3f, 5f, 6f };
    
    //自定义显示方法4,去掉Label
    private float MyCustomDrawerArrayNoLabel(float value)
    {
        return EditorGUILayout.Slider(value, this.From, this.To);
    }
  • 效果图:
    在这里插入图片描述

2.4 Group 组

2.4.1 修饰字段

2.4.1.1 【BoxGroup】方框组
  • 功能:
    BoxGroup 可以让同组的字段在一个方框里

  • 示例:
    // Box with a title.
    [BoxGroup(“Some Title”)]
    public string A;

    [BoxGroup(“Some Title”)]
    public string B;

    // Box with a centered title.
    [BoxGroup(“Centered Title”, centerLabel: true)]
    public string C;

    [BoxGroup(“Centered Title”)]
    public string D;

  • 效果图:
    在这里插入图片描述

2.4.1.2 【HorizontalGroup】水平布局组
  • 功能:
    HorizontalGroup 可以把字段水平排列起来
  • 示例:
// LabelWidth can be helpfull when dealing with HorizontalGroups.
    [HorizontalGroup("Group 1", LabelWidth = 20)]
    public int C;

    [HorizontalGroup("Group 1")]
    public int D;

    [HorizontalGroup("Group 1")]
    public int E;

// Having multiple properties in a column can be achived using multiple groups. Checkout the "Combining Group Attributes" example.
    [HorizontalGroup("Split", 0.5f, LabelWidth = 20)]
    [BoxGroup("Split/Left")]
    public int L;

    [BoxGroup("Split/Right")]
    public int M;

    [BoxGroup("Split/Left")]
    public int N;

    [BoxGroup("Split/Right")]
    public int O;
  • 效果图:
    在这里插入图片描述
2.4.1.3 【VerticalGroup】数值布局组
  • 功能:
    VerticalGroup 跟HorizontalGroup用法差不多,实现的是竖直布局

  • 示例:
    [HorizontalGroup(“Split”)]
    [VerticalGroup(“Split/Left”)]
    public InfoMessageType First;

    [VerticalGroup(“Split/Left”)]
    public InfoMessageType Second;

    [HideLabel]
    [VerticalGroup(“Split/Right”)]
    public int A;

    [HideLabel]
    [VerticalGroup(“Split/Right”)]
    public int B;

  • 效果图:
    在这里插入图片描述

2.4.2 修饰按钮

2.4.2.1 【ButtonGroup】按钮组
  • 功能:
    ButtonGroup 修饰的是方法,会把同一个组的方法按钮排成整洁的HorizontalGroup水平布局。
    注意点:修饰的是方法,这是和HorizontalGroup不同的地方
  • 示例:
    //不带组名字的一个组
    [ButtonGroup]
    private void A()
    {
    }

    [ButtonGroup]
    private void B()
    {
    }

    [ButtonGroup]
    private void C()
    {
    }

    [ButtonGroup]
    private void D()
    {
    }
    

    //带组名字的一个组
    [Button(ButtonSizes.Large)]
    [ButtonGroup("Button Group1")]
    private void E()
    {
    }

    [GUIColor(0, 1, 0)]
    [ButtonGroup("Button Group1")]
    private void F()
    {
    }

  • 效果图:
    在这里插入图片描述
2.4.2.2 【ResponsiveButtonGroup】流式布局组
  • 功能:
    ResponsiveButtonGroup也是用来修饰方法,不过使用的是流式布局,从左至右水平布局,排满了就从上至下换行继续布局
  • 示例:
    [OnInspectorGUI]
    private void Space1()
    {
        GUILayout.Space(20);
    }

    [ResponsiveButtonGroup]
    public void Foo()
    {
    }

    [ResponsiveButtonGroup]
    public void Bar()
    {
    }

    [ResponsiveButtonGroup]
    public void Baz()
    {
    }

    [OnInspectorGUI]
    private void Space2()
    {
        GUILayout.Space(20);
    }

    [ResponsiveButtonGroup("UniformGroup", UniformLayout = true)]
    public void Foo1()
    {
    }

    [ResponsiveButtonGroup("UniformGroup")]
    public void Foo2()
    {
    }

    [ResponsiveButtonGroup("UniformGroup")]
    public void LongesNameWins()
    {
    }

    [ResponsiveButtonGroup("UniformGroup")]
    public void Foo4()
    {
    }

    [ResponsiveButtonGroup("UniformGroup")]
    public void Foo5()
    {
    }

    [ResponsiveButtonGroup("UniformGroup")]
    public void Foo6()
    {
    }

    [OnInspectorGUI]
    private void Space3()
    {
        GUILayout.Space(20);
    }

    [ResponsiveButtonGroup("DefaultButtonSize", DefaultButtonSize = ButtonSizes.Small)]
    public void Bar1()
    {
    }

    [ResponsiveButtonGroup("DefaultButtonSize")]
    public void Bar2()
    {
    }

    [ResponsiveButtonGroup("DefaultButtonSize")]
    public void Bar3()
    {
    }

    [Button(ButtonSizes.Large), ResponsiveButtonGroup("DefaultButtonSize")]
    public void Bar4()
    {
    }

    [Button(ButtonSizes.Large), ResponsiveButtonGroup("DefaultButtonSize")]
    public void Bar5()
    {
    }

    [ResponsiveButtonGroup("DefaultButtonSize")]
    public void Bar6()
    {
    }
  • 效果图:
    在这里插入图片描述

2.4.3 有效拓展显示空间的组

2.4.3.1 【FoldoutGroup】可折叠组
  • 功能:
    FoldoutGroup 可以把同组的字段折叠起来,可直接修饰字段
  • 示例:
    //Group 1
    [FoldoutGroup("Group 1")]
    public int A;

    [FoldoutGroup("Group 1")]
    public int B;

    [FoldoutGroup("Group 1")]
    public int C;
    
    //Collapsed group,默认不展开
    [FoldoutGroup("Collapsed group", expanded: false)]
    public int D;

    [FoldoutGroup("Collapsed group")]
    public int E;
    
    //使用‘$GroupTitle’字段的内容作为组名字,默认展开
    [FoldoutGroup("$GroupTitle", expanded: true)]
    public int One;

    [FoldoutGroup("$GroupTitle")]
    public int Two;

    public string GroupTitle = "Dynamic group title";
  • 效果图:
    请添加图片描述
  • FoldoutGroup也可以修饰按钮,要同【Button】一起使用
  • 示例:
    [FoldoutGroup("测试按钮折叠")]
    [Button("按钮1")]
    public void FlodTest1()
    {
        
    }
    
    [FoldoutGroup("测试按钮折叠")]
    [Button("按钮2")]
    public void FlodTest2()
    {
        
    }
  • 效果图:
    在这里插入图片描述
2.4.3.2 【TabGroup】页签组
  • 功能:
    TabGroup可以通过页签来拓展显示空间
    TabGroup 包含2个重要的字段: GroupName(组名字)和 TabName(页签名字)
    同 GroupName 组的会形成一个页签,可以在有限的空间里切换显示大大的容量

  • 语法:
    1个参数,相当于GroupName 为 空"",会被分配到同一个页签组里
    [TabGroup(“TabName 页签名字”)]

    2个参数,可以指定GroupName,同组的会被分配到同一个页签组里
    [TabGroup(“GroupName 组名字”,“TabName 页签名字”)]

  • 示例:

    [TabGroup("Tab A")]
    public int One;

    [TabGroup("Tab A")]
    public int Two;

    [TabGroup("Tab A")]
    public int Three;

    [TabGroup("Tab B")]
    public string MyString;

    [TabGroup("Tab B")]
    public float MyFloat;

    [TabGroup("Tab C")]
    [HideLabel]
    public MyTabObject TabC;

    [TabGroup("New Group", "Tab A")]
    public int A;

    [TabGroup("New Group", "Tab A")]
    public int B;

    [TabGroup("New Group", "Tab A")]
    public int C;

    [TabGroup("New Group", "Tab B")]
    public string D;

    [TabGroup("New Group", "Tab B")]
    public float E;

    [TabGroup("New Group", "Tab C")]
    [HideLabel]
    public MyTabObject F;

    [Serializable]
    public class MyTabObject
    {
        public int A;
        public int B;
        public int C;
    }
  • 效果图:
    请添加图片描述

2.5 Collections 收集器

2.5.1 【TableList】列表/数组渲染器

  • 功能:
    TableList 可以用来渲染List和数组,可以搭配TableColumnWidth,HideInTables等属性来使用
  • 示例:
    // ShowIndexLabels是否显示序号,默认不显示
    // DrawScrollView显示滑动条
    // MaxScrollViewHeight 当高度高于多少时开始出现滚动条
    [TableList(ShowIndexLabels = true,DrawScrollView = true, MaxScrollViewHeight = 200)]
    public List<CustomItem> List = new List<CustomItem>()
    {
        new CustomItem(),
        new CustomItem(),
        new CustomItem(),
    };

    [Serializable]
    public class CustomItem
    {    
        //指定宽度
        [PreviewField(Height = 20)]
        [TableColumnWidth(30, Resizable = false)]
        public Texture2D Icon;

        //指定宽度
        [TableColumnWidth(60)]
        public int ID;
        
        //隐藏该列
        [HideInTables]
        public string Name;
        
        //图标默认值
        [OnInspectorInit]
        private void CreateData()
        {
            Icon = ExampleHelper.GetTexture();
        }
    }
  • 效果图:
    我们搭配了TableColumnWidth和HideInTables还有TableList自身字段的配置来定义列表的渲染
    在这里插入图片描述

2.5.2 【ValueDropdown】下拉框

  • 功能:
    ValueDropdown 可以用来显示下拉框
  • 语法:
    [ValueDropdown(“方法名”)],修饰字段
  • 示例:
    // 普通下路框
    [ValueDropdown("TextureSizes")] 
    public int SomeSize1;
    
    private static int[] TextureSizes = new int[] {256, 512, 1024};

    // 下拉框是string描述,值是int的下拉框
    [ValueDropdown("FriendlyTextureSizes")]
    public int SomeSize2;

    private static IEnumerable FriendlyTextureSizes = new ValueDropdownList<int>()
    {
        {"Small", 256},
        {"Medium", 512},
        {"Large", 1024},
    };

    //下拉框是某个目录下的GameObject集合
    [ValueDropdown("GetAllSirenixAssets", IsUniqueList = true)]
    public List<GameObject> UniqueGameobjectList;

    private static IEnumerable GetAllSirenixAssets()
    {
        var root = "Assets/Plugins/Sirenix/";

        return UnityEditor.AssetDatabase.GetAllAssetPaths()
            .Where(x => x.StartsWith(root))
            .Select(x => x.Substring(root.Length))
            .Select(x => new ValueDropdownItem(x, UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(root + x)));
    }

    //下拉框是树形控件
    [ValueDropdown("TreeViewOfInts", ExpandAllMenuItems = true)]
    public List<int> IntTreview = new List<int>() {1, 2, 7};

    private IEnumerable TreeViewOfInts = new ValueDropdownList<int>()
    {
        {"Node 1/Node 1.1", 1},
        {"Node 1/Node 1.2", 2},
        {"Node 2/Node 2.1", 3},
        {"Node 3/Node 3.1", 4},
        {"Node 3/Node 3.2", 5},
        {"Node 1/Node 3.1/Node 3.1.1", 6},
        {"Node 1/Node 3.1/Node 3.1.2", 7},
    };
  • 效果图:
    在这里插入图片描述

2.6 Conditionals 条件

2.6.1 DisableIf/EnableIf/ShowIf/HideIf

  • 功能:
    上面这几个特性用法差不多,可以根据名字猜到用途。这里以DisableIf为例,DisableIf修饰字段,可以在条件满足时,使修饰的字段禁用,无法修改
  • 示例:
    public UnityEngine.Object SomeObject;

    [EnumToggleButtons]
    public InfoMessageType SomeEnum;

    public bool IsToggled;
    
    //1. 根据bool值
    //当IsToggled = true;此字段Disable
    [DisableIf("IsToggled")] [ShowIf()()]
    public int DisableIfToggled;

    //2. 根据枚举值
    //当枚举 SomeEnum = InfoMessageType.Info;此字段Disable
    [DisableIf("SomeEnum", InfoMessageType.Info)]
    public Vector2 Info;

    //当枚举 SomeEnum = InfoMessageType.Error;此字段Disable
    [DisableIf("SomeEnum", InfoMessageType.Error)]
    public Vector2 Error;


    
    //3. 根据GameObject
    //当GameObject SomeObject != null; 此字段Disable
    [DisableIf("SomeObject")]
    public Vector3 EnabledWhenNull;
    
    //4. 根据表达式
    [DisableIf("@this.IsToggled && this.SomeObject != null || this.SomeEnum == InfoMessageType.Error")]
    public int DisableWithExpression;
  1. 效果图:
    请添加图片描述

2.6.2 DisableInEditorMode/DisableInPlayMode

  1. 功能:
    DisableInEditorMode & DisableInPlayMode 分别是在编辑器和运气时禁用字段
  2. 示例:
    [Title("Disabled in edit mode")]
    [DisableInEditorMode]
    public GameObject A;

    [DisableInPlayMode] 
    public Material B;
  1. 效果图:
    当前为编辑器模式
    在这里插入图片描述

三、窗口

Odin除了有丰富的特性来显示字段和方法,还支持创建窗口

3.1 显示原生EditorWindow

现在我们可以回顾一下怎么创建一个原生的EditorWindow

  1. 创建类继承EditorWindow
    public class OneWindow : EditorWindow
  2. 在OnGUI方法里进行绘制
  • 示例
public class OneWindow : EditorWindow
{
    public string Text1;
    
    [MenuItem("Tools/打开Window")]
    private static void OpenWindow()
    {
       GetWindow<OneWindow>().Show();
    }

    private void OnGUI()
    {
        Text1 = EditorGUILayout.TextField("输入Text1:",Text1);
        if (GUILayout.Button("按钮"))
        {
            Debug.Log("按下按钮");
        }
    }
}
  • 效果图
    在这里插入图片描述

3.2 显示OdinEditorWindow

接下来我们使用Odin来创建窗口。需要进行一点小改造

  1. 改成继承OdinEditorWindow(OdinEditorWindow其实也继承了EditorWindow)
  2. 去掉OnGUI方法,把绘制代码用Odin特性来修饰
  • 示例:
public class OneWindow : OdinEditorWindow
{
    [LabelText("输入Text1")]
    public string Text1;

    [Button("按钮")]
    public void Btn()
    {
        Debug.Log("按下按钮"); 
    }
    
    [MenuItem("Tools/打开Window")]
    private static void OpenWindow()
    {
       GetWindow<OneWindow>().Show();
    }
    
}
  • 效果图
    在这里插入图片描述
    可以看到Odin实现了原生的窗口效果。总的感觉来说,Odin相对于原生编辑拓展有2个好处:
    1.在功能庞大时,使用Odin写出来的代码维护性比较好
    2.使用Odin可以轻易实现一些交互体验比较好的效果

3.2 显示OdinMenuEditorWindow

使用OdinMenuEditorWindow,我们可以实现一个包含多个窗口的窗口;

  1. 改成继承OdinMenuEditorWindow
  2. 覆盖BuildMenuTree()方法,在里面构建菜单
  • 示例
public class OdinMenuWindow : OdinMenuEditorWindow
{

   [MenuItem("Tools/打开MeneuWindow")]
   private static void OpenWindow()
   {
      GetWindow<OdinMenuWindow>().Show();
   }
   
   protected override OdinMenuTree BuildMenuTree()
   {
      var tree = new OdinMenuTree();
      tree.Add("窗口1",new SubWindow());//添加OdinEditorWindow
      tree.Add("窗口2",new TestClass());//添加一个类
      tree.Add("窗口2/子窗口",new ChildClass());//通过'/'来增加子窗口
      return tree;
   }
   
}

public class SubWindow : OdinEditorWindow
{
   [LabelText("窗口-SubWindow")]
   public string Text;
}

public class TestClass
{
   [LabelText("窗口-TestClass")]
   public string Text;
}

public class ChildClass
{
   [LabelText("窗口-ChildClass")]
   public string Text;
}
  • 效果图
    请添加图片描述
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值