Project Icon

UnityRuntimeInspector

实时检视器和层级视图工具为Unity游戏开发提供运行时调试

该工具为Unity开发者提供了运行时检视和调试功能。通过直观的用户界面,开发者可以实时检查和修改游戏对象属性。支持多选、拖放等操作,并针对性能进行了优化。外观和行为可自定义,适用于多种平台。对需要在游戏运行过程中进行调试和优化的开发者很有帮助。

Unity 3D 运行时检查器和层级视图

截图

Asset Store 链接: https://assetstore.unity.com/packages/tools/gui/runtime-inspector-hierarchy-111349

论坛讨论: https://forum.unity.com/threads/runtime-inspector-and-hierarchy-open-source.501220/

Discord: https://discord.gg/UJJt549AaV

GitHub 赞助 ☕

A. 简介

这是一个简单但功能强大的 Unity 3D 运行时检查器层级视图解决方案,适用于 Unity 支持的几乎所有平台,包括移动平台。

B. 许可证

运行时检查器和层级视图使用 MIT 许可证Asset Store 版本Asset Store EULA 管理)。请注意,该资产使用了一个外部资产,该资产使用 BSD 3-Clause 许可证

C. 安装

有 5 种方式安装此插件:

  • 通过 Assets-Import Package 导入 RuntimeInspector.unitypackage
  • 克隆/下载此仓库,并将 Plugins 文件夹移动到你的 Unity 项目的 Assets 文件夹中
  • Asset Store 导入
  • (通过包管理器) 将以下行添加到 Packages/manifest.json
    • "com.yasirkula.runtimeinspector": "https://github.com/yasirkula/UnityRuntimeInspector.git",
  • (通过 OpenUPM) 安装 openupm-cli 后,运行以下命令:
    • openupm add com.yasirkula.runtimeinspector

常见问题

  • 新输入系统在 Unity 2019.2.5 或更早版本不受支持

ENABLE_INPUT_SYSTEM 编译指令添加到 Player Settings/Scripting Define Symbols(这些符号是特定于平台的,因此如果稍后更改活动平台,你需要再次添加编译指令)。

  • Unity 2018.4 或更早版本无法解析 "Unity.InputSystem" 程序集

RuntimeInspector.Runtime 程序集定义文件的 Assembly Definition References 列表中删除 Unity.InputSystem 程序集。

D. 使用方法

  • 要在场景中使用层级视图,将 RuntimeHierarchy 预制体拖放到你的画布上
  • 要在场景中使用检查器,将 RuntimeInspector 预制体拖放到你的画布上

你可以将检查器连接到层级视图,这样每当层级视图中的选择发生变化时,检查器就会检查新选择的对象。要做到这一点,将检查器分配给层级视图的 Connected Inspector 属性。

你也可以将层级视图连接到检查器,这样每当检查器中的对象引用被突出显示时,层级视图中的选择就会更新。要做到这一点,将层级视图分配给检查器的 Connected Hierarchy 属性。

请注意,这些连接是单向的,这意味着将检查器分配给层级视图不会自动将层级视图分配给检查器,反之亦然。还要注意,检查器和层级视图不是单例,因此你可以在场景中同时拥有多个具有不同配置的实例。

E. 特性

  • 两个面板在 GC 方面都经过了大量优化,以避免任何不必要的内存分配。默认情况下,检查器和层级视图每秒刷新 4 次,以几乎立即反映其用户界面的任何变化。检查器的每次刷新都会为 GC 生成一些垃圾,因为大多数情况下,被检查的对象具有值类型的变量。这些变量在通过反射访问时会被装箱,这种装箱会创建一些不可避免的垃圾。然而,通过增加检查器和/或层级视图的 Refresh Interval,可以大大优化这个过程
  • 包含内置的颜色选择器和引用选择器:

截图

  • 可以通过更改检查器和层级视图的 Skin 来调整其视觉外观。Skins 目录中包含两个预制皮肤:LightSkinDarkSkin。你可以使用 Assets-Create-yasirkula-RuntimeInspector-UI Skin 上下文菜单创建自己的皮肤

截图

  • 层级视图支持多选:

截图

E.1. 检查器

截图

RuntimeInspector 的工作方式类似于编辑器检查器。它可以直接暴露常用的 Unity 类型,以及标有 System.Serializable 属性的自定义类和结构体。还支持一维数组和泛型 List。

  • Refresh Interval:顾名思义,这是检查器的刷新间隔。每次刷新时,所有暴露的字段和属性的值都会被刷新。这会为装箱的值类型生成一些垃圾(不可避免),因此,即使略微增加这个值也应该对 GC 有很大帮助
  • Expose Fields:决定应该暴露被检查对象的哪些字段:NoneSerializable OnlyAll
  • Expose Properties:决定应该暴露被检查对象的哪些属性
  • Array Indices Start At One:启用时,暴露的数组和列表的索引从 1 开始而不是 0(仅视觉上的变化)
  • Use Title Case Naming:启用时,变量名以标题大小写格式显示(例如,m_myVariable 变为 My Variable
  • Show Add Component Button:启用时,检查 GameObject 时会出现 Add Component 按钮
  • Show Remove Component Button:启用时,被检查的组件下会出现 Remove Component 按钮
  • Show Tooltips:启用时,将鼠标悬停在变量名上一段时间会显示一个工具提示,显示变量的名称。对于名称部分被遮挡的变量可能有用
  • Tooltip Delay:决定光标在变量名上保持静止多长时间后才显示工具提示。如果禁用了 Show Tooltips,则无效
  • Nest Limit:想象一下暴露一个链表。这个变量定义了从初始节点开始,你可以在检查器中暴露多少个节点,直到检查器停止暴露任何进一步的节点
  • Inspected Object Header Visibility:如果被检查的对象有一个可折叠的头部,决定该头部的可见性
  • Pool Capacity:UI 元素被池化以避免不必要的 InstantiateDestroy 调用。这个值为每个 UI 元素单独定义池容量。在独立平台上,你可以增加这个值以获得更好的性能
  • Settings:检查器的设置数组。可以使用 Assets-Create-yasirkula-RuntimeInspector-Settings 上下文菜单创建新的设置资产。设置资产存储 4 种不同的内容:
    • Standard DrawersReference Drawers:drawer 是用于在检查器中暴露单个变量的预制体。对于扩展 UnityEngine.Object 的变量,会创建一个引用 drawer,对于其他变量,会创建一个标准 drawer
      • 在为变量搜索合适的 drawer 时,会从下到上遍历相应的 drawer 列表,直到找到支持该变量类型的 drawer。如果找不到这样的 drawer,则不会暴露该变量
    • Hidden Variables:允许你为给定类型及其所有扩展/实现的类型隐藏检查器中的某些变量。你可以输入星号字符 (*) 来隐藏该类型的所有变量
    • Exposed Variables:允许你暴露(反制)一些隐藏的变量。一个变量在暴露之前要经过一系列过滤:
    1. 其类型必须可序列化
    2. 它不能有 System.ObsoleteSystem.NonSerializedHideInInspector 属性
    3. 如果它在 Exposed Variables 中,则会被暴露
    4. 它不能在 Hidden Variables
    5. 它必须通过 Expose FieldsExpose Properties 过滤器
    • 因此,要仅暴露给定类型的特定变量集,你可以通过在其 Hidden Variables 中输入星号来隐藏其所有变量,然后在其 Exposed Variables 中输入要暴露的变量集

在更改检查器的设置时,建议不要触碰 InternalSettings;而是创建一个单独的 Settings 资产并将其添加到检查器的 Settings 数组中。否则,当 InternalSettings 在更新中发生变化时,你的设置可能会被覆盖。

E.2. 层级视图

截图

RuntimeHierarchy 简单地将场景中的对象暴露给用户界面。除了在层级视图中暴露当前活动的 Unity 场景外,你还可以在层级视图中暴露一组特定的对象,这被称为伪场景。伪场景可以帮助你对场景中的对象进行分类。只能通过脚本 API 和辅助组件向伪场景添加/从中移除对象。

  • 刷新间隔:层级结构的刷新间隔。每次刷新时,被销毁的对象会从层级中移除,而新创建的对象会添加到层级中。对象的同级索引也会在每次刷新时与Unity层级同步

  • 对象名称刷新间隔:访问GameObject.name属性会产生垃圾。因此,层级中对象的名称不会在每个刷新间隔同步,而是在每个对象名称刷新间隔同步,以帮助避免过多的垃圾产生

  • 搜索刷新间隔:搜索结果的刷新间隔。每次刷新时,都会检查每个GameObject的名称是否匹配搜索词,因此这个过程会产生一些垃圾

  • 允许多选:禁用时,层级中只能选择单个Transform

  • 显示Unity场景:禁用时,Unity场景不会在层级中显示。当你只想使用层级来展示伪场景时,这很有用

  • 显示的Unity场景子集:通过名称指定在层级中显示的场景。为空时,显示所有场景

  • 显示DontDestroyOnLoad场景:启用时,DontDestroyOnLoad对象将在层级中显示

  • 伪场景顺序:伪场景在层级中从上到下的顺序。注意,在这里输入伪场景并不会在应用程序启动时自动创建它。伪场景只能通过脚本API创建

  • 指针长按动作:决定当点击并长按一个对象时会发生什么:

    • :什么都不做 ¯\_(ツ)_/¯
    • 创建拖拽引用项:创建一个拖拽引用项,可以将其拖放到检视器中的引用抽屉上,以将所持对象分配给该变量(类似于Unity的拖放引用分配)
    • 显示多选切换:在每个对象前显示多选切换。这在没有CTRL和Shift键的移动设备上特别有用。如果禁用了允许多选,则无效
    • 先显示多选切换再创建拖拽引用项:如果多选切换不可见,则显示它们。否则,创建一个拖拽引用项
  • 指针长按持续时间:决定需要按住对象多长时间才能执行指针长按动作

  • 双击阈值:当在层级中双击一个对象时,会触发OnItemDoubleClicked事件(参见脚本API)。此值决定两次点击之间允许的最大延迟,以注册为双击

  • 可重组项目:启用时,将持有Transform的拖拽引用项放到层级中的对象上会改变被拖拽Transform的父级(类似于Unity层级中的父子关系调整)

  • 可将拖拽的父级放到子级上:启用时,拖拽引用项可以放到其子对象之一上。在这种情况下,子对象将解除父子关系,然后拖拽引用项将成为它的子级。如果禁用了可重组项目,则无效

  • 可将拖拽对象放到伪场景:启用时,将拖拽引用项放到伪场景或伪场景中根对象的上方/下方会自动将其添加到该伪场景。如果禁用了可重组项目,则无效

  • 显示工具提示:启用时,将鼠标悬停在对象上一段时间会显示一个工具提示,显示对象的名称。对于名称很长的对象很有用

  • 工具提示延迟:决定光标在对象上保持静止多长时间后才显示工具提示。如果禁用了显示工具提示,则无效

  • 显示水平滚动条:启用时,如果层级中显示的名称不适合可用空间,将显示一个水平滚动条。注意,只有可见项目的宽度值用于确定可滚动区域的大小

  • 与编辑器层级同步选择:简单地在Unity层级和此RuntimeHierarchy之间同步选中的对象

可重组项目的其他设置可以在RuntimeHierarchy/ScrollView/Viewport对象中找到:

截图

  • 同级索引修改区域:当拖拽引用项被放置在层级中Transform的顶部或底部边缘附近时,它将被插入到目标Transform的上方或下方。此值决定顶部和底部边缘附近区域的大小

  • 可滚动区域:当光标悬停在滚动视图顶部或底部边缘附近并持有拖拽引用项时,滚动视图将自动滚动以显示该方向的内容。此值决定滚动视图顶部和底部边缘附近区域的大小

  • 滚动速度:决定当光标悬停在可滚动区域上时滚动视图的滚动速度

F. 脚本API

E.1E.2部分中提到的变量值可以在运行时通过它们对应的属性进行调整。对这些属性的任何更改都会立即反映到UI上。在这里,你会发现一些可以通过脚本对检视器和层级进行的有趣操作:

  • 你可以使用以下函数更改检视器中检查的对象:
public void Inspect( object obj );
public void StopInspect();
  • 你可以通过检视器的InspectedObject属性访问当前检查的对象
  • 你可以使用以下函数更改层级中选中的对象:
// SelectOptions是一个枚举标志,意味着它可以使用|(或)运算符接受多个值。这些值是:
// - Additive:新选择将附加到当前选择,而不是替换它
// - FocusOnSelection:滚动视图将对准选中的对象
// - ForceRevealSelection:通常,当选择改变时,新选择将在层级中完全展开(即选择的所有父级将展开以显示选择)。如果选择没有改变,这不会自动发生。
//   当设置此标志时,即使选择没有改变,选中的对象也会被完全显示/展开
public bool Select( Transform selection, SelectOptions selectOptions = SelectOptions.None ); // 选择指定的Transform。成功更改选择时返回true
public bool Select( IList<Transform> selection, SelectOptions selectOptions = SelectOptions.None ); // 选择指定的Transform(s)

public void Deselect(); // 取消选择所有Transform
public void Deselect( Transform deselection ); // 只取消选择指定的Transform
public void Deselect( IList<Transform> deselection ); // 只取消选择指定的Transform(s)

public bool IsSelected( Transform transform ); // 如果选择包括该Transform,则返回true
  • 你可以通过CurrentSelection属性访问层级中当前选中的对象
  • 可以通过MultiSelectionToggleSelectionMode属性手动启用层级的多选切换
  • 你可以在检视器和/或层级上调用Refresh()函数来手动刷新它们
  • 你可以通过IsLocked属性锁定检视器和/或层级
  • 你可以注册层级的OnSelectionChanged事件,以在选择改变时得到通知
  • 你可以注册检视器的OnInspectedObjectChanging委托,以在检查的对象即将改变时得到通知,并且如果你愿意,可以完全更改检查的对象。例如,如果你只想检查附加了Renderer组件的对象,可以使用以下函数:
private object OnlyInspectObjectsWithRenderer( object previousInspectedObject, object newInspectedObject )
{
	GameObject go = newInspectedObject as GameObject;
	if( go != null && go.GetComponent<Renderer>() != null )
		return newInspectedObject;

	// 不检查没有Renderer组件的对象
	return null;
}
  • 你可以注册检视器的ComponentFilter委托来过滤检视器中GameObject的可见组件列表(例如隐藏某些组件)
runtimeInspector.ComponentFilter = ( GameObject gameObject, List<Component> components ) =>
{
    // 只需从'components'列表中移除不需要的Components
};
  • 你可以注册层级的GameObjectFilter委托来从层级中隐藏某些对象(或者,你可以将这些对象添加到RuntimeInspectorUtils.IgnoredTransformsInHierarchy中,它们将从所有层级中隐藏;只需确保在销毁它们之前从这个HashSet中移除它们)
runtimeHierarchy.GameObjectFilter = ( Transform obj ) =>
{
    if( obj.CompareTag( "Main Camera" ) )
        return false; // 从层级中隐藏主摄像机
 
    return true;
};
  • 您可以注册到层级结构的OnItemDoubleClicked事件,以便在层级中的对象被双击时收到通知
  • 您可以在函数上添加RuntimeInspectorButton属性,以将它们作为按钮暴露在检查器中。当检查该类型的对象时,这些按钮会出现。此属性有3个参数:
    • string label: 按钮上显示的文本
    • bool isInitializer: 如果设置为true,并且函数返回的对象可赋值给定义该函数的类型,则函数的结果值将被赋回给被检查的对象。换句话说,此函数可用于初始化空对象或更改结构体的变量
    • ButtonVisibility visibility: 确定按钮何时可见。带有ButtonVisibility.InitializedObjects的按钮只有在被检查的对象不为空时才会出现,而带有ButtonVisibility.UninitializedObjects的按钮只有在被检查的对象为空时才会出现。您可以使用ButtonVisibility.InitializedObjects | ButtonVisibility.UninitializedObjects来始终在检查器中显示按钮
  • 虽然您不能将RuntimeInspectorButton属性添加到Unity的内置函数中,但您可以通过扩展方法在内置Unity类型下显示按钮。您必须在单个静态类中编写所有此类扩展方法,用RuntimeInspectorButton属性标记这些方法,然后通过以下方式将这些函数引入RuntimeInspector: RuntimeInspectorUtils.ExposedExtensionMethodsHolder = typeof( 包含扩展方法的脚本 );

F.1. 伪场景

您可以使用以下函数将对象添加到层级中的伪场景:

public void AddToPseudoScene( string scene, Transform transform );
public void AddToPseudoScene( string scene, IEnumerable<Transform> transforms );

如果相关的伪场景不存在,这些函数会自动创建它们。

您可以使用以下函数从层级中的伪场景中移除对象:

public void RemoveFromPseudoScene( string scene, Transform transform, bool deleteSceneIfEmpty );
public void RemoveFromPseudoScene( string scene, IEnumerable<Transform> transforms, bool deleteSceneIfEmpty );

您可以使用以下函数手动创建或删除伪场景:

public void CreatePseudoScene( string scene );
public void DeletePseudoScene( string scene );
public void DeleteAllPseudoScenes();

F.1.1. PseudoSceneSourceTransform

这个辅助组件允许您将对象的子对象添加到层级中的伪场景。当子对象被添加到或从对象中移除时,此组件会自动刷新伪场景。如果启用了HideOnDisable,则在禁用对象时,其子对象将从伪场景中移除。

F.2. 颜色选择器

您可以通过ColorPicker.Instance访问内置的颜色选择器,然后使用以下函数显示它:

public void Show( ColorWheelControl.OnColorChangedDelegate onColorChanged, ColorWheelControl.OnColorChangedDelegate onColorConfirmed, Color initialColor, Canvas referenceCanvas );
  • onColorChanged: 当用户更改颜色时定期调用。ColorWheelControl.OnColorChangedDelegate接受一个Color32参数
  • onColorConfirmed: 当用户通过确定按钮提交颜色时调用
  • initialColor: 颜色选择器的初始值
  • referenceCanvas: 如果指定,参考画布的属性将被复制到颜色选择器画布

您可以通过将UISkin分配给其Skin属性来更改颜色选择器的视觉外观。

F.3. 对象引用选择器

您可以通过ObjectReferencePicker.Instance访问内置的对象引用选择器,然后使用以下函数显示它:

public void Show( ReferenceCallback onReferenceChanged, ReferenceCallback onSelectionConfirmed, NameGetter referenceNameGetter, NameGetter referenceDisplayNameGetter, object[] references, object initialReference, bool includeNullReference, string title, Canvas referenceCanvas );
  • onReferenceChanged: 当用户从列表中选择引用时调用。ReferenceCallback接受一个object参数
  • onSelectionConfirmed: 当用户通过确定按钮提交选定的引用时调用
  • referenceNameGetter: NameGetter接受一个object参数并返回该对象的名称作为字符串。传递的函数将用于对引用列表进行排序并将引用的名称与搜索字符串进行比较
  • referenceDisplayNameGetter: 传递的函数将用于获取引用的显示名称。通常,相同的函数会传递给此参数和referenceNameGetter参数
  • references: 要从中选择的引用数组
  • initialReference: 初始选定的引用
  • includeNullReference: 如果设置为true,空引用选项将被添加到引用列表的顶部
  • title: 对象引用选择器的标题
  • referenceCanvas: 如果指定,参考画布的属性将被复制到对象引用选择器画布

您可以通过将UISkin分配给其Skin属性来更改对象引用选择器的视觉外观。

F.4. 拖动引用项

E.2节中提到,您可以从层级中拖放对象到检查器中的变量,以将这些对象分配给这些变量。但是,您不仅限于层级。有两个辅助组件可用于为其他对象创建拖动引用项:

  • DraggedReferenceSourceCamera: 当附加到摄像机时,在每次鼠标点击时向场景投射射线,如果您在物体上长按一段时间,则创建一个拖动引用项。您可以注册此组件的ProcessRaycastHit委托来过滤可以创建拖动引用项的对象。例如,如果您只希望标签为NPC的对象能够创建拖动引用项,您可以使用以下函数:
private Object CreateDraggedReferenceItemForNPCsOnly( RaycastHit hit )
{
	if( hit.collider.gameObject.CompareTag( "NPC" ) )
		return hit.collider.gameObject;

	// 非NPC对象不能创建拖动引用项
	return null;
}
  • DraggedReferenceSourceUI: 当分配给UI元素时,该元素可以在被点击并长按一段时间后为其References对象创建拖动引用项

您还可以使用自己的脚本通过调用RuntimeInspectorUtils类中的以下函数来创建拖动引用项:

public static DraggedReferenceItem CreateDraggedReferenceItem( Object reference, PointerEventData draggingPointer, UISkin skin = null );
public static DraggedReferenceItem CreateDraggedReferenceItem( Object[] references, PointerEventData draggingPointer, UISkin skin = null, Canvas referenceCanvas = null );

G. 自定义绘制器(编辑器)

注意: 如果您只想从RuntimeInspector中隐藏一些字段/属性,只需使用Settings资源的Hidden Variables列表(在E.1节中提到)。

您可以向RuntimeInspector引入自己的自定义绘制器。然后这些绘制器将用于在RuntimeInspector中绘制被检查对象的属性。如果没有为类型指定自定义绘制器,内置的ObjectField将用于绘制该类型的所有属性。有两种方法可以创建自定义绘制器:

  • 创建一个绘制器预制件并将其添加到E.1节中提到的Settings资源中。每个绘制器都继承自InspectorField基类。还有一个ExpandableInspectorField抽象类,允许您创建可展开/折叠的绘制器,如数组。最后,继承ObjectReferenceField类允许您创建可通过引用选择器或拖放分配值的绘制器
    • 这个选项提供了最大的灵活性,因为您可以根据需要自定义绘制器预制件。缺点是,您必须创建一个预制件资源并手动将其添加到RuntimeInspector的Settings资源中。所有内置绘制器都使用这种方法;它们可以像BoolFieldTransformField那样简单,也可以像BoundsFieldGameObjectFieldArrayField那样复杂
  • 扩展IRuntimeInspectorCustomEditor接口并用RuntimeInspectorCustomEditor属性装饰类/结构
    • 这个选项更简单,因为您不必为绘制器创建预制件资源。创建的自定义绘制器将在内部被ObjectField用来填充其子绘制器。这个选项对于大多数用例应该足够了。但是想象一下,您想为Matrix4x4创建一个自定义绘制器,其中单元格显示在4x4网格中。在这种情况下,您必须使用第一个选项,因为您需要一个自定义预制件,其中包含16个InputField组织在4x4网格中。但是,如果您可以通过使用内置绘制器的组合来表示您想要的自定义绘制器,那么这第二个选项应该足够了

G.1. InspectorField

为了在所有绘制器中有一个标准化的视觉外观,每个绘制器都有一些共同的变量:

  • Layout Element: 用于设置绘制器的高度。当前活动的Inspector皮肤的Line Height属性设置了标准高度。这个值乘以绘制器的虚拟HeightMultiplier属性。对于高度未知的ExpandableInspectorField,这个变量应该保持未分配状态
  • Variable Name Text: 显示暴露变量名称的Text对象
  • Variable Name Mask: 要理解这一点,您可能需要检查一个简单的绘制器,如BoolField。在Variable Name Text上方绘制了一个Image,以便以有效的方式遮罩其可见区域。这个遮罩被分配给这个变量

每个绘制器都可以访问以下属性:

  • object Value: 此抽屉绑定的变量的最新值。它会在每个检视器刷新间隔时更新。更改此属性也会更改绑定的对象。
  • RuntimeInspector Inspector: 当前使用此抽屉的RuntimeInspector。
  • UISkin Skin: 分配给此抽屉的皮肤。
  • Type BoundVariableType: 绑定对象的类型。
  • int Depth: 此抽屉绘制的深度。随着深度增加,应在左侧为此抽屉的内容应用填充(在OnDepthChanged函数中)。
  • string Name: 绑定变量的名称。设置时,如果检视器中启用了"使用标题大小写命名",则变量名会转换为标题大小写格式。
  • string NameRaw: 设置时,变量名会按原样使用,不会转换为标题大小写格式。
  • float HeightMultiplier: 影响抽屉的高度。

抽屉有一些在特定情况下调用的特殊函数:

  • void Initialize(): 应该用来代替 Awake/Start 来初始化抽屉。
  • bool SupportsType( Type type ): 返回此抽屉是否能在检视器中显示(支持)某种类型。
  • bool CanBindTo( Type type, MemberInfo variable ): 返回此抽屉是否能显示提供的 variable。只有当 SupportsType 返回 true 时才会调用此函数。这个函数对于只能显示具有特定属性的变量的抽屉很有用(例如,NumberRangeField 查询RangeAttribute)。请注意,variable 参数可能null。默认情况下,此函数返回true。
  • void OnBound( MemberInfo variable ): 当抽屉通过反射绑定到变量时调用。请注意,variable 参数可能null
  • void OnUnbound(): 当抽屉从其绑定的变量解绑时调用。
  • void OnInspectorChanged(): 当抽屉的 Inspector 属性改变时调用。
  • void OnSkinChanged(): 当抽屉的 Skin 属性改变时调用。您的自定义抽屉必须在此处调整其UI元素的视觉外观,以符合分配的皮肤标准。
  • void OnDepthChanged(): 当抽屉的 Depth 属性改变时调用。在这里,您的自定义抽屉必须从左侧为其内容添加填充,以符合嵌套标准。当 Skin 改变时也会调用此函数。
  • void Refresh(): 当绑定对象的值刷新时调用。抽屉必须在此处刷新其UI元素的值。由RuntimeInspector每 Refresh Interval 秒调用一次。

G.2. ExpandableInspectorField

扩展 ExpandableInspectorField 的自定义抽屉可以访问以下属性:

  • bool IsExpanded: 返回抽屉是展开还是折叠。设置为 true 时,抽屉展开并在其下方绘制其内容。
  • HeaderVisibility HeaderVisibility: 设置此抽屉头部的可见性:CollapsibleAlwaysVisibleHidden。默认值为 Collapsible
  • int Length: 此抽屉旨在绘制的元素数量。如果其值与此抽屉拥有的子抽屉数量不匹配,则会重新生成抽屉的内容。

ExpandableInspectorField 有以下特殊函数:

  • void GenerateElements(): 必须在此处生成此抽屉的子抽屉。
  • void ClearElements(): 必须在此处清除此抽屉的子抽屉。

ExpandableInspectorField的子抽屉应存储在 protected List<InspectorField> elements 变量中,因为ExpandableInspectorField使用此列表来比较子抽屉的数量与 Length 属性。当调用 Refresh() 时,此列表中的子抽屉会自动刷新,当调用 ClearElements() 时,此列表中的子抽屉会自动清除。

您可以使用 RuntimeInspector.CreateDrawerForType( Type type, Transform drawerParent, int depth, bool drawObjectsAsFields = true ) 函数创建子抽屉。如果找不到可以显示此类型的抽屉,该函数返回 null。对于ExpandableInspectorFields,drawerParent 参数应设置为ExpandableInspectorField的 drawArea 变量。如果 drawObjectsAsFields 参数设置为true,且类型继承自 UnityEngine.Object,则会在 Reference Drawers 中搜索支持此类型的抽屉。否则,会搜索 Standard Drawers

创建子抽屉后,ExpandableInspectorField 必须手动将其子抽屉绑定到相应的变量。这是通过 InspectorField 类的以下 BindTo 函数完成的:

  • BindTo( InspectorField parent, MemberInfo variable, string variableName = null ): 将对象绑定到 MemberInfo(可以通过反射获得)。这里,parent 参数应设置为此 ExpandableInspectorField。如果 variableName 设置为null,其值将直接从MemberInfo参数获取。
  • BindTo( Type variableType, string variableName, Getter getter, Setter setter, MemberInfo variable = null ): 这允许您为此子抽屉定义自己的getter和setter函数。例如,ArrayField 使用此函数,因为没有直接的MemberInfo来访问数组的元素。使用此方法,您可以使用自定义函数而不是MemberInfos来获取/设置绑定对象的值(ArrayField为其元素的getter函数使用 Array.GetValue,为其元素的setter函数使用 Array.SetValue)。

ExpandableInspectorField中还有一些辅助函数,可以轻松创建子抽屉,而无需手动调用 CreateDrawerForTypeBindTo

  • InspectorField CreateDrawerForComponent( Component component, string variableName = null ): 为组件创建一个 Standard Drawer
  • InspectorField CreateDrawerForVariable( MemberInfo variable, string variableName = null ): 为 MemberInfo 存储的变量创建一个抽屉。这个变量必须在检查对象的类/结构或其基类之一中声明。
  • InspectorField CreateDrawer( Type variableType, string variableName, Getter getter, Setter setter, bool drawObjectsAsFields = true ): 类似于带有 GetterSetter 参数的 BindTo 函数,允许您使用自定义函数来获取和设置子抽屉绑定的对象的值。

G.3. ObjectReferenceField

扩展 ObjectReferenceField 类的抽屉可以访问 void OnReferenceChanged( Object reference ) 函数,当分配给该抽屉的引用发生变化时会调用此函数。

G.4. 辅助类

PointerEventListener: 这是一个简单的辅助组件,当其UI GameObject被按下时触发 PointerDown 事件,释放时触发 PointerUp 事件,点击时触发 PointerClick 事件。

BoundInputField: 大多数内置抽屉使用此组件作为它们的输入字段。这个辅助组件允许您在输入时验证输入,并在提交输入时得到通知。它有以下属性和函数:

  • string DefaultEmptyValue: 当输入为空时,输入字段将具有的默认值。例如,NumberField将此值设置为"0"。
  • string Text: 用于刷新输入字段当前值的属性。如果输入字段当前被聚焦并正在编辑,则此属性不会立即更改其文本,而是将值存储在变量中,以便在输入字段不再聚焦时使用。此外,设置此属性不会触发 OnValueChanged 事件。
  • UISkin Skin: 此输入字段使用的皮肤。设置时,输入字段将相应调整其UI。
  • OnValueChangedDelegate OnValueChanged: 在编辑输入字段的值时调用(每次更改输入时调用)。OnValueChangedDelegate 具有以下签名:bool OnValueChangedDelegate( BoundInputField source, string input )。注册到此事件的函数应解析 input 并在输入有效时返回 true,否则返回 false
  • OnValueChangedDelegate OnValueSubmitted: 当用户完成编辑输入字段的值时调用。与 OnValueChanged 类似,注册到此事件的函数应解析 input 并仅在输入有效时返回 true
  • bool CacheTextOnValueChange: 确定当用户在输入字段内容无效时(即其背景变为红色)停止编辑输入字段时会发生什么。如果此变量设置为 true,输入字段的文本将恢复为最新的在OnValueChanged中返回 true 的值。否则,文本将恢复为输入字段聚焦时的值。

G.5. RuntimeInspectorCustomEditor 属性

要创建抽屉而无需为其创建预制体,您可以声明一个继承自 IRuntimeInspectorCustomEditor 并具有一个或多个 RuntimeInspectorCustomEditor 属性的类/结构。

RuntimeInspectorCustomEditor 属性具有以下属性:

  • Type inspectedType: 此自定义抽屉支持(可以显示)的类型。
  • bool editorForChildClasses: 如果设置为true,从 inspectedType 派生的类型也可以使用此抽屉绘制。默认值为 false

IRuntimeInspectorCustomEditor 具有以下函数:

  • void GenerateElements( ObjectField parent ):由内置的 ObjectFieldGenerateElements 函数调用。子抽屉应该在这个函数中添加到 ObjectField
  • void Refresh():由 ObjectFieldRefresh 函数调用
  • void Cleanup():由 ObjectFieldClearElements 函数调用。如果抽屉创建了一些可释放的资源,必须在这里释放它们。不需要在这里销毁创建的子抽屉,因为这由 ObjectField 自动处理,正如在 ExpandableInspectorField 部分所解释的

GenerateElements 函数内,你可以调用 parent 参数的 CreateDrawerForComponentCreateDrawerForVariableCreateDrawer 函数来创建子抽屉。除此之外,你还可以调用 ObjectField 的以下辅助函数:

  • void CreateDrawersForVariables( params string[] variables ):为被检查对象的指定变量创建抽屉。如果没有提供特定变量,将为被检查对象的所有公开变量创建抽屉
  • void CreateDrawersForVariablesExcluding( params string[] variablesToExclude ):为被检查对象的所有公开变量创建抽屉,除了 variablesToExclude 列表中指定的变量。如果没有排除任何变量,将为被检查对象的所有公开变量创建抽屉

以下是一些自定义抽屉的例子:

截图

// Collider类型及其派生类型的自定义抽屉
[RuntimeInspectorCustomEditor( typeof( Collider ), true )]
public class ColliderEditor : IRuntimeInspectorCustomEditor
{
    public void GenerateElements( ObjectField parent )
    {
        // 只公开 Colliders 的 "enabled" 和 "isTrigger" 属性
        // 注意,我们也可以通过修改 RuntimeInspector 的 Settings 资源中的 "Hidden Variables" 和 "Exposed Variables" 列表来实现同样的效果
        parent.CreateDrawersForVariables( "enabled", "isTrigger" );
    }

    public void Refresh() { }
    public void Cleanup() { }
}

截图

// MeshRenderer类型的自定义抽屉(但不包括其派生类型)
[RuntimeInspectorCustomEditor( typeof( MeshRenderer ), false )]
public class MeshRendererEditor : IRuntimeInspectorCustomEditor
{
    public void GenerateElements( ObjectField parent )
    {
        // 获取我们正在检查的 MeshRenderer 对象
        MeshRenderer renderer = (MeshRenderer) parent.Value;

        // 不公开 MeshRenderer 的属性,而是公开其 sharedMaterial 的属性
        ExpandableInspectorField materialField = (ExpandableInspectorField) parent.CreateDrawer( typeof( Material ), "", () => renderer.sharedMaterial, ( value ) => renderer.sharedMaterial = (Material) value, false );

        // 材质的抽屉默认是 ExpandableInspectorField。在这个例子中,我们不想绘制其可折叠的标题
        materialField.HeaderVisibility = RuntimeInspector.HeaderVisibility.Hidden;
    }

    public void Refresh() { }
    public void Cleanup() { }
}

截图

// Camera类型的自定义抽屉(但不包括其派生类型)
[RuntimeInspectorCustomEditor( typeof( Camera ), false )]
public class CameraEditor : IRuntimeInspectorCustomEditor
{
    // 在 GenerateElements 中创建的一些子抽屉
    private BoolField isOrthographicField;
    private NumberField orthographicSizeField, fieldOfViewField;

    public void GenerateElements( ObjectField parent )
    {
        // 为 Camera 的 "orthographic"、"orthographicSize" 和 "fieldOfView" 属性创建子抽屉,并将它们存储在变量中
        isOrthographicField = (BoolField) parent.CreateDrawerForVariable( typeof( Camera ).GetProperty( "orthographic", BindingFlags.Public | BindingFlags.Instance ), "Is Orthographic" );
        orthographicSizeField = (NumberField) parent.CreateDrawerForVariable( typeof( Camera ).GetProperty( "orthographicSize", BindingFlags.Public | BindingFlags.Instance ) );
        fieldOfViewField = (NumberField) parent.CreateDrawerForVariable( typeof( Camera ).GetProperty( "fieldOfView", BindingFlags.Public | BindingFlags.Instance ) );

        // 为 "orthographicSize" 和 "fieldOfView" 子抽屉添加额外的缩进
        orthographicSizeField.Depth++;
        fieldOfViewField.Depth++;

        // 为 Camera 的其余公开属性创建子抽屉
        parent.CreateDrawersForVariablesExcluding( "orthographic", "orthographicSize", "fieldOfView" );
    }

    public void Refresh()
    {
        // 检查 Camera 当前是否使用正交投影
        bool isOrthographicCamera = (bool) isOrthographicField.Value;

        // 根据相机当前的投影类型,显示 "orthographicSize" 子抽屉或 "fieldOfView" 子抽屉
        // (这里,我们首先通过 'activeSelf' 检查子抽屉是否已经处于激活/非激活状态,以进行优化,因为 GameObject.SetActive
        // 会导致大量的 GC 分配,而且不幸的是,至少在一些 Unity 版本中,它不会自动检查 GameObject 是否已经处于激活/非激活状态)
        if( orthographicSizeField.gameObject.activeSelf != isOrthographicCamera )
            orthographicSizeField.gameObject.SetActive( isOrthographicCamera );
        if( fieldOfViewField.gameObject.activeSelf == isOrthographicCamera )
            fieldOfViewField.gameObject.SetActive( !isOrthographicCamera );
    }

    public void Cleanup() { }
}
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

白日梦AI

白日梦AI提供专注于AI视频生成的多样化功能,包括文生视频、动态画面和形象生成等,帮助用户快速上手,创造专业级内容。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

讯飞绘镜

讯飞绘镜是一个支持从创意到完整视频创作的智能平台,用户可以快速生成视频素材并创作独特的音乐视频和故事。平台提供多样化的主题和精选作品,帮助用户探索创意灵感。

Project Cover

讯飞文书

讯飞文书依托讯飞星火大模型,为文书写作者提供从素材筹备到稿件撰写及审稿的全程支持。通过录音智记和以稿写稿等功能,满足事务性工作的高频需求,帮助撰稿人节省精力,提高效率,优化工作与生活。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号