Odin特性解析
- 一、前言
- 二、特性大全
- 2.1 限制相关
- 2.2 通用特性
- 2.2.1 【ShowInInspector】显示在Inspector面板
- 2.2.2 【OnValueChanged】值变化回调
- 2.2.3 【Delayed】&【DelayedProperty】延迟
- 2.2.4 【GUIColor】变色
- 2.2.4 【ColorPalette】颜色调色板
- 2.2.5 【Title】& 【HideLabel】增加标题&隐藏字段名
- 2.2.6 【DisplayAsString】显示内容
- 2.2.7 【PropertyOrder】字段优先级
- 2.2.7 【PropertySpace】字段间隔
- 2.2.8 【Searchable】查找
- 2.2.9 【MultiLineProperty】多行输入框
- 2.2.10 【EnumPaging】枚举分页
- 2.2.11 【EnumToggleButtons】枚举下拉框转水平布局
- 2.2.12 【SuffixLabel】Label增加后缀
- 2.2.13 【InlineButton】在末尾增加按钮
- 2.2.14 【LabelText】重命名
- 2.3 较特殊的特性
- 2.4 Group 组
- 2.5 Collections 收集器
- 2.6 Conditionals 条件
- 三、窗口
一、前言
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;
- 效果图:
2.6.2 DisableInEditorMode/DisableInPlayMode
- 功能:
DisableInEditorMode & DisableInPlayMode 分别是在编辑器和运气时禁用字段 - 示例:
[Title("Disabled in edit mode")]
[DisableInEditorMode]
public GameObject A;
[DisableInPlayMode]
public Material B;
- 效果图:
当前为编辑器模式
三、窗口
Odin除了有丰富的特性来显示字段和方法,还支持创建窗口
3.1 显示原生EditorWindow
现在我们可以回顾一下怎么创建一个原生的EditorWindow
- 创建类继承EditorWindow
public class OneWindow : EditorWindow - 在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来创建窗口。需要进行一点小改造
- 改成继承OdinEditorWindow(OdinEditorWindow其实也继承了EditorWindow)
- 去掉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,我们可以实现一个包含多个窗口的窗口;
- 改成继承OdinMenuEditorWindow
- 覆盖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;
}
- 效果图