Odin Inspector教程 | (二)使用 Attributes(特性)

📂 Unity 开发资源汇总 | 插件 | 模型 | 源码

💓 欢迎访问 Unity 打怪升级大本营

💯 系列教程索引

📄(一)开始使用Odin Inspector
📄(二)使用 Attributes(特性)
📄(三)自定义编辑器窗口
📄(四)创建自定义 Drawers(抽屉)
📄(五)使用自定义特性处理器(Attribute Processors)
📄(六)Value解析器 和 Action解析器

Odin Inspector 是 Unity 的一个插件,让您可以享受拥有强大,自定义和用户友好编辑器的所有工作流程优势,而无需编写任何自定义编辑器代码。

Odin 包含许多功能,例如 Static Inspector,Project Validation,Odin Editor Windows 和开源的 Odin Serializer,它允许您在需要多态对象结构时扩展Unity的序列化功能,或者希望在运行时序列化和反序列化数据。

在这里插入图片描述

Odin Inspector and Serializer 最新版 (0积分)免费下载

华丽的分割线

💯 引言

Odin Inspector是一个强大的Unity编辑器扩展,它通过提供一系列自定义属性(Attributes),允许开发者以声明式的方式增强Unity Inspector窗口的交互性和视觉效果。这些Attributes不仅可以改善开发体验,还能提升最终用户的使用体验。

💯 什么是Attributes?

在编程中,Attributes是一种特殊类型的类,用于提供关于类、属性、方法等的元数据。在Unity和Odin Inspector中,Attributes用于自定义Unity Inspector中的属性表现,包括但不限于显示方式、编辑器行为等。

💯 简单Attributes示例

使用 Odin Inspector ,您可以完全仅使用特性来设计您的 Inspector 。您可以实现分组、排序、复杂功能、输入验证、各种便捷功能、按钮等等。
在这里插入图片描述

// 记得在您的类的顶部添加以下using语句。这将使您能够访问Odin的所有 Attributes。
using Sirenix.OdinInspector;

public class ExampleScript : MonoBehaviour
{
	[FilePath(Extensions = ".unity")]
	public string ScenePath;

	[Button(ButtonSizes.Large)]
	public void SayHello()
	{
		Debug.Log("Hello button!");
	}
}

您可以使用 HideInInspector 隐藏字段,并使用 ShowInInspector 显示字段和属性。
在这里插入图片描述

[HideInInspector]
public int NormallyVisible;

[ShowInInspector]
private bool normallyHidden;

[ShowInInspector]
public ScriptableObject Property { get; set; }

您可以组合多个 attributes,并使用 PropertyOrder 更改顺序。
在这里插入图片描述

[PreviewField, Required, AssetsOnly]
public GameObject Prefab;

[HideLabel, Required, PropertyOrder(-5)]
public string Name { get; set; }

[Button(ButtonSizes.Medium), PropertyOrder(-3)]
public void RandomName()
{
    this.Name = Guid.NewGuid().ToString();
}

您可以使用group attributes来完全更改属性的布局。
在这里插入图片描述

[HorizontalGroup("Split", Width = 50), HideLabel, PreviewField(50)]
public Texture2D Icon;

[VerticalGroup("Split/Properties")]
public string MinionName;

[VerticalGroup("Split/Properties")]
public float Health;

[VerticalGroup("Split/Properties")]
public float Damage;

此外,许多 Attributes 允许您引用其他字段、属性或方法,以使用自定义行为来扩展Inspector,以适应您的用例。
在这里插入图片描述

[LabelText("$IAmLabel")]
public string IAmLabel;

[ListDrawerSettings(
    CustomAddFunction = "CreateNewGUID",
    CustomRemoveIndexFunction = "RemoveGUID")]
public List<string> GuidList;

private string CreateNewGUID()
{
	return Guid.NewGuid().ToString();
}

private void RemoveGUID(int index)
{
    this.GuidList.RemoveAt(index);
}

💯 Group Attributes(组特性)

您是否曾经有过一个非常大的脚本,其中包含许多Inspector变量,并且发现在检查器中跟踪所有这些变量有点困难或乏味? Odin 中的群组可以解决这个问题,并极大地帮助您的检查员保持易于管理且美观。

它们的作用在于名称:组允许您将相关属性组合在一起,并以各种方式绘制它们,从简单的标记框和下拉列表到多页标签组。 使用它们很容易;只需将同名的组属性放在要组合在一起的成员上即可。
在这里插入图片描述

[TabGroup("First Tab")]
public int FirstTab;

[ShowInInspector, TabGroup("First Tab")]
public int SecondTab { get; set; }

[TabGroup("Second Tab")]
public float FloatValue;

[TabGroup("Second Tab"), Button]
public void Button()
{
	...
}

很酷,但是如果你可以将 groups 组合在一起呢?嗯, 你可以的。
在这里插入图片描述

[Button(ButtonSizes.Large)]
[FoldoutGroup("Buttons in Boxes")]
[HorizontalGroup("Buttons in Boxes/Horizontal", Width = 60)]
[BoxGroup("Buttons in Boxes/Horizontal/One")]
public void Button1() { }

[Button(ButtonSizes.Large)]
[BoxGroup("Buttons in Boxes/Horizontal/Two")]
public void Button2() { }

[Button]
[BoxGroup("Buttons in Boxes/Horizontal/Double")]
public void Accept() { }

[Button]
[BoxGroup("Buttons in Boxes/Horizontal/Double")]
public void Cancel() { }

Odin 使将您自己的组类型添加到Inspector变得简单。

💯 Meta Attributes(元特性)

某些 Attributes 不会做任何有形的事情,例如更改值的绘制方式、位置或时间。 相反,它们在本质上更具反应性,并为Inspector增加了一些基本情报。 它们允许您执行更复杂的任务,例如验证输入、 当值发生更改时调用方法,以及许多其他类似的事情。
在这里插入图片描述

[ValidateInput("IsValid")]
public int GreaterThanZero;

private bool IsValid(int value)
{
	return value > 0;
}

[OnValueChanged("UpdateRigidbodyReference")]
public GameObject Prefab;

private Rigidbody prefabRigidbody;

private void UpdateRigidbodyReference()
{
	if (this.Prefab != null)
	{
		this.prefabRigidbody = this.Prefab.GetComponent<Rigidbody>();
	}
	else
	{
		this.prefabRigidbody = null;
	}
}

使用这样的元特性,任何程序员都有一套强大、易于使用的工具套件,可以帮助执行必要的规则, 以及通过提供反馈、相关信息和 在正确的时间提供工具提示,使整体工作流程更顺畅,不易出错和失误。
在这里插入图片描述

[Required]
public GameObject RequiredReference;

[InfoBox("This message is only shown when MyInt is even", "IsEven")]
public int MyInt;

private bool IsEven()
{
	return this.MyInt % 2 == 0;
}

💯 OdinSerialize 和 ShowInInspector

OdinSerializeSerializeFieldShowInInspector 之间存在非常大的差异。 虽然一开始可能并不明显。它们通常都会导致检查员显示某些内容, 但是使用 ShowInInspector,显示的任何内容都不会被保存。如果您希望在Inspector中显示某些内容,并希望保存它,请使用 OdinSerialize

如果您已将 OdinSerialize 放在成员上,但它仍然没有显示在Inspector 中, 这是因为该属性实际上并未导致成员被序列化,例如, non-auto 属性或虚拟或抽象 auto 属性永远不会被 Odin 序列化。 同样在由定义类实现的接口上声明的属性也不会, 或其继承的类之一(因为这会导致属性方法变为虚拟)。

如果您希望保存某些内容,但不希望在Inspector中显示,则可以结合使用 HideInInspectorOdinSerialize

💯 更改属性的顺序

您可以通过应用 PropertyOrderAttribute 轻松更改事物的显示顺序。 成员首先按其顺序号(默认为 0)进行排序,然后按它们在脚本本身中的写入顺序进行排序, 尽管不同的成员类型将始终单独显示(见下段)。优先级按升序排序,这意味着首先显示较小的数字。
在这里插入图片描述

[PropertyOrder(1)]
public int Last;

[PropertyOrder(-1)]
public int First;

// All properties default to 0.
public int Middle;

如上所述,使用 Odin,您可以在检查器中展示各种东西;不仅是字段,还有属性和方法。 通常,此类成员会单独显示,但是通过属性排序,您可以轻松地将各种成员相互交织在一起。
在这里插入图片描述

[PropertyOrder(2)]
public int Second;

[Button]
[PropertyOrder(1)]
public void First()
{
	// ...
}

💯 Attribute 表达式

许多 Attributes 允许您传入字符串参数,这些参数可以引用成员或包含要计算的 C# 表达式。 这非常有用,因为它使您可以快速轻松地将基本逻辑注入到 Inspector 中。 属性表达式由以 @ 符号开头的字符串表示。

支持表达式的众多 Attributes 之一是 InfoBox,它接受 一个表达式,其计算结果为字符串值,该值用作框的内容。

以下 Attribute 声明是 Attribute 表达式的最简单可能用法之一,并且 将生成一个信息框,显示 myStr 字段的未修改内容:

[InfoBox("@myStr")]
public string myStr;

在这里插入图片描述
您还可以编写更复杂的逻辑。以下 Attribute 声明将生成一个始终显示当前时间的信息框:

[InfoBox(@"@""The current time is: "" + DateTime.Now.ToString(""HH:mm:ss"")")]
public string myStr;

在这里插入图片描述
ShowIfHideIf 等 Attributes 也支持表达式:

[ShowIf("@this.someNumber >= 0f && this.someNumber <= 10f")]
public string myStr;

public float someNumber;

还有一些特殊的表达式关键字,可让您从 Attribute 表达式中访问各种上下文值。例如 $property 关键字允许您访问正在计算表达式的成员的 InspectorProperty 实例:

[Serializable]
public class Example
{
	[InfoBox(@"@""This member's parent property is called "" + $property.Parent.NiceName")]
    public string myStr;
}

// 现在,无论您在何处声明它,myStr 现在都会动态地知道其父级的名称。
public Example exampleInstance;

在这里插入图片描述
还有 $value 关键字,它允许您访问表达式所在的成员的值,而无需这样做 键入成员的名称。这使您可以在多个不同的成员上重用相同的表达式(可能在常量中声明)。 例如,本教程中第一个示例中 InfoBox 表达式的更通用版本可能是:

[InfoBox("@$value")]
public string myStr;

这些值称为“命名表达式参数”,对应于通过以下方式传递到表达式系统的命名值 处理表达式的 ValueResolver 或 ActionResolver。

还可以使用特殊的属性查询语法轻松检索表达式范围内其他成员的属性:#(memberName)

public List<string> someList;

[OnValueChanged("@#(someList).State.Expanded = $value")]
public bool expandList;

上述表达式查找 someList 成员的属性,并在更改 expandList 成员的值时修改其展开状态。

Odin 的 Attribute 表达式由轻量级编译器提供支持,该编译器支持大多数 C# 的表达式语法。表达式的性能相当出色; 它们不会被解释,而是被完全编译成发出的 IL,然后进一步 JIT 转换为本机机器代码。如果您编写了无效的 表达式,您将获得一个有用的编译器错误,指示出了什么问题:
在这里插入图片描述
正如您可能推测的那样,Attribute 表达式是一种非常灵活和强大的工具,并且使向Odin的 Attributes 添加自定义逻辑和行为变得容易得多,而无需创建和引用无数的额外成员。

💯 Unit Attributes

一般来说,没有人将某物描述为 75 m/s 的速度。他们将其描述为 270 公里/小时!或者 167 英里/小时,如果你愿意的话。

输入 Unit Attribute。对您最有意义的值进行编码和编辑。该Attribute 允许您定义“基础”和“显示”单位:

[Unit(Units.MetersPerSecond, Units.KilometersPerHour)]
public float Speed;

在这里,我们使用 MetersPerSecond 作为基本单位。因此,代码中的速度值始终以米/秒为单位。通过为显示单位设置 KilometersPerHour,当在Inspector中绘制时,速度值将转换为公里/小时。 现在,当您在字段中键入 270 时,它会转换为 75 m/s。或者输入“167 mph”,这也转换为 ~75 m/s。

作为替代示例,您可以使用较小的显示单元:

[Unit(Units.Meter, Units.Centimeters)]
public float SuspensionLength;

有时,对值进行微小的调整可能具有挑战性,尤其是在使用鼠标拖动时。上述示例就是这种情况,因此,通过将单位显示为厘米,可以更轻松地进行细微的调整。 您以后可能会认为厘米对您来说太精细了,并切换到毫米 - 所有这些都不会影响您的代码的其余部分。

不确定,哪些单位适合编辑?简单:通过右键单击来定义基本单元并更改其在Inspector中的显示方式。

[Unit(Units.Kilogram)]
public float Weight;

在这里,我们刚刚将重量定义为千克。而且,当我们与 Inspector 合作时,我们可以根据需要在其他重量单位之间快速切换。当然,还可以输入另一个单位的重量,例如磅,然后让检查员为我们将其转换为千克。 如果您进行任何模拟或使用现实生活中的值,那真是天赐之物。

单元的一个很好的用途是在播放测试时显示调试值。在以下示例中,我们获取刚体的速度,并将其转换为每小时千米,供 Inspector 使用。它提供了一个易于阅读的值,以帮助您了解代码正在执行的操作,并使用 DisplayAsString 选项仅将值显示为文本。

public Rigidbody myRigidbody;

[ShowInInspector, HideInEditorMode, Units(Units.MetersPerSecond, Units.KilometersPerHour, DisplayAsString = true)]
public float DebugSpeed => myRigidbody.velocity.magnitude;

此外,还可以选择 ForceDisplayUnit 选项,以防止用户在检查器中更改显示的单位。

如果奥丁没有你需要的单位怎么办?您可以自己添加!在此示例中,我们将添加一个自定义 Odin 单元。我们将 1 Odin 定义为 42 米。

[InitializeOnLoadMethod]
public static void AddCustomUnit()
{
	UnitNumberUtility.AddCustomUnit(
		name: "Odin",
		symbols: new string[] { "odin" },
		unitCategory: UnitCategory.Distance,
		multiplier: 1m / 42m);
}

// Use the custom unit by referencing it by name.
[Unit(Units.Meter, "Odin")]
public float Odins;

Unity 的 InitializeOnLoadMethod 在编辑器以 UnitNumberUtility.AddCustomUnit 开始时调用一个方法,我们添加 Odin 单元。 为了简单起见,我们在这里只添加一个符号“odin”,但您可以添加更多。我们使用距离类别,以便我们可以转换为其他距离单位。Odin 只会在同一类别的单位之间进行转换。最后,我们将乘数设置为 1/42;将 1 米转换为奥丁所需的乘数。

最后,我们有一些注意事项。由于浮点数的性质以及浮点数、双精度和小数之间的转换,精度可能会成为您的一个问题,尤其是当您想要从非常高的值转换为非常低的值,反之亦然。将1000光年转换为纳米充其量是不稳定的。请记住这一点。

public class UnitsExamples : MonoBehaviour
{
	[Unit(Units.MetersPerSecond, Units.KilometersPerHour)]
	public float Speed;

	[Unit(Units.Meter, Units.Centimeter)]
	public float SuspensionLength;

	[Unit(Units.Kilogram)]
	public float Weight;
	
	public Vector3 Velocity; // Stand in for rigidbody.velocity
	
	// [HideInEditorMode] // Use to show the debug value only in play mode.
	[ShowInInspector, Unit(Units.MetersPerSecond, Units.KilometersPerHour, DisplayAsString = true)]
	public float DebugSpeed => Velocity.magnitude;

    [InitializeOnLoadMethod]
    public static void AddCustomUnit()
    {
		UnitNumberUtility.AddCustomUnit(
			name: "Odin",
			symbols: new string[] { "odin" },
			unitCategory: UnitCategory.Distance,
			multiplier: 1m / 42m);
    }

    // Use the custom unit by referencing it by name.
    [Unit(Units.Meter, "Odin")]
    public float Odins;
}

💯 属性状态

在 3.0 中,我们引入了属性状态系统,它是用于抽屉(以及附加到属性的其他事物,例如解析器和处理器)的一种方式 创建和公开可以从外部查询和修改的命名状态。3.0 还在 Attribute 表达式中增加了属性查询语法,与状态系统具有强大的协同作用。属性状态 包含在 InspectorProperty 实例的 State 成员中,该实例的类型为 PropertyState

默认情况下,所有属性都具有三个硬编码状态,因为它们非常常用。这些状态是 Visible 状态、Enabled 状态和 Expanded 状态。

Visible 状态控制相关属性的可见性。简而言之,如果 Visible 状态为 false,则调用 InspectorProperty.Draw() 将 几乎立即返回,从未绘制过任何东西。所有组都具有与“可见”状态相关的特殊默认行为:如果所有组都包含 属性的可见状态为 false,则该组会将其自己的可见状态切换为 false,从而在其所有子项都被隐藏时自动隐藏自身。

Enabled 状态控制默认情况下是启用还是禁用属性的 GUI。请注意,各个抽屉可以覆盖此内容,另请注意 启用状态永远不会导致 GUI 从禁用切换到启用,反之亦然。许多其他因素,例如 ReadOnly、 可能会导致属性的 GUI 被禁用,而 Enabled 状态不会影响。当 Enabled 状态为 false 时,调用 InspectorPropert.Draw() 将在绘制该属性的抽屉链时将 GUI.enabled 设置为 false。

Expanded 状态控制属性是否在 UI 中展开,前提是属性的抽屉在其 实现。所有随附的 Odin 抽屉都使用此状态来控制属性是否扩展,建议您也使用此状态 说明您是否编写自己的自定义抽屉,而不是使用本地抽屉字段来控制它。与“可见”和“已启用”状态不同,“扩展”状态为 持续;如果修改,新值将通过 PersistentContext 缓存在重新加载时保留。

并非所有属性都使用所有这些状态!例如,字符串的简单属性将具有 Expanded 状态,但由于其抽屉都不使用 对于此状态,更改状态不会影响字符串的绘制方式。

让我们看一下以简单方式使用状态系统的属性声明示例:一个控制基于列表的 Expanded 状态的枚举 关于它是否有一定的标志。此示例使用 Odin 的属性表达式特性 #(exampleList) 的属性查询语法来方便地工作 替换为列表成员的 InspectorProperty 实例,并修改其状态。

// It is generally recommended to use the OnStateUpdate attribute to control the state of properties
[OnStateUpdate("@#(exampleList).State.Expanded = $value.HasFlag(ExampleEnum.UseStringList)")]
public ExampleEnum exampleEnum;

public List<string> exampleList;

[Flags]
public enum ExampleEnum
{
    None,
    UseStringList = 1 << 0,
    // ...
}

在这里插入图片描述
也可以创建自定义状态。任何希望创建自定义状态的抽屉、处理器或解析器都需要调用 InspectorProperty.State.Create() 一次才能执行此操作。之后,可以通过 InspectorProperty.State.Get()InspectorProperty.State.Set() 方法访问和修改自定义状态。

例如,TabGroup 属性的抽屉公开了三种自定义状态:CurrentTabNameCurrentTabIndexTabCount。 允许查询和更改当前选定的选项卡,如以下示例所示:

// All groups silently have "#" prepended to their path identifier to avoid naming conflicts with members.
// Thus, the "Tabs" group is accessed via the "#(#Tabs)" syntax.
[OnStateUpdate("@#(#Tabs).State.Set<int>(\"CurrentTabIndex\", $value + 1)")]
[PropertyRange(1, "@#(#Tabs).State.Get<int>(\"TabCount\")")]
public int selectedTab = 1;

[TabGroup("Tabs", "Tab 1")]
public string exampleString1;

[TabGroup("Tabs", "Tab 2")]
public string exampleString2;

[TabGroup("Tabs", "Tab 3")]
public string exampleString3;

在这里插入图片描述

💯 Odin 下载地址

Odin Inspector and Serializer 最新版 (0积分)免费下载


《Odin Inspector 系列教程》文章索引:
📄(一)开始使用Odin Inspector
📄(二)使用 Attributes(特性)
📄(三)自定义编辑器窗口
📄(四)创建自定义 Drawers(抽屉)
📄(五)使用自定义特性处理器(Attribute Processors)
📄(六)Value解析器 和 Action解析器


TheEnd


跳跃

📂 Unity 开发资源汇总 | 插件 | 模型 | 源码

💓 欢迎访问 Unity 打怪升级大本营

🍉🍉🍉 如果觉得这篇文对你有帮助的话,请点个赞👍、收藏⭐️下吧,非常感谢! 💕💕💕
关注我

博主头像
【博主简介】:10年以上软件开发经验,精通 C语言C++C#Java 等开发语言,开发过大型 Android 项目,现主要自主开发经营 休闲益智类小游戏

【粉丝福利】:博主收藏了大量游戏开发资源和素材。这些资源经过博主多年整理沉淀,现筛选一批精品资源,分享给大家学习研究。

Unity打怪军团 广招天下勇士加入 Unity学习互助小组 需要进群的同学联系我,互3互推也请联系我…
联系我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Unity打怪升级

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值