Version:

第10章 在编辑器中配置组件

第10章 在编辑器中配置组件

注意:
本章的源代码可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch10_reflection

介绍

本章将涵盖以下主题:

  • 如何为组件提供编辑器配置。
  • 什么是序列化上下文?
  • 什么是编辑上下文?

第 9 章,使用 AZ::TickBus 实现 OscillatorComponent。它确实完成了任务。但是,一旦您开始使用 CTRL+G 在编辑器中测试它,您将注意到一个痛点:如果您不喜欢该行为并想修改它,则必须返回 C++,进行更改,重新编译代码,重新启动编辑器,以最终查看您的更改是否达到了您想要的效果。

改变一个变量需要付出太多的努力和时间。具体来说,OscillatorComponent 有两个变量,可以向 Editor 公开:period 和 amplitude。

class OscillatorComponent
{
  ...
  float m_period = 3.f;
  float m_amplitude = 10.f;
};

Editor 中的组件属性

到目前为止,我们实现的组件没有向 Editor 公开任何内容。

没有编辑器参数的组件我们可以修改它的反射,以便我们可以在 Editor 中更改它的某些行为。通过向 Editor 公开 period 和 amplitude 值,Entity Inspector 将显示具有两个可修改字段的组件。

这样,您就可以输入新值并立即对其进行测试,而无需重新编译并重新启动 Editor。这可以通过组件的 Reflect 方法中的 O3DE 反射来实现。

反射层

魔力在于 OscillatorComponent::Reflect。从上一章开始,除了基本注册之外,它没有为 Editor 提供任何额外的配置。

void OscillatorComponent::Reflect(AZ::ReflectContext* reflection)
{
  auto sc = azrtti_cast<AZ::SerializeContext*>(reflection);
  if (!sc) return;
  sc->Class<OscillatorComponent, Component>()
    ->Version(1);
  AZ::EditContext* ec = sc->GetEditContext();
  if (!ec) return;
  using namespace AZ::Edit::Attributes;
  // reflection of this component for O3DE Editor
  ec->Class<OscillatorComponent>("Oscillator Component", "[oscillates the entity]")
    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
    ->Attribute(AppearsInAddComponentMenu, AZ_CRC("Game"))
    ->Attribute(Category, "My Project");
}

O3DE 中的反射以层(称为上下文)进行描述。第一个也是主层是 SerializeContext。在其上构建了两个层:EditContext(用于 Editor)和 BehaviorContext(用于脚本)。

目前,OscillatorComponent 的反射数据可以这样表示:

图 10.1.基本序列化

SerializeContext

例 10.1. AzCore\Serialization\SerializeContext.h

/**
* Serialize context is a class that manages information
* about all reflected data structures. You will use it
* for all related information when you declare your data
* for serialization.
* In addition it will handle data version control.
*/
class SerializeContext

SerializeContext 是数据结构的反射信息的全局存储。在本章中,我们将仅限于将自定义组件及其数据反映到其中的讨论。反映组件的起点是将其声明为 SerializeContext 中的类:

sc->Class<OscillatorComponent, Component>()

Class 遵循一种称为 Builder 模式的编程方法,在该方法中,您可以对其返回值连续调用 meth ods,因为它们的返回值始终是 ClassBuilder。

/**
* Internal structure to maintain class information while
* we are describing a class. User should call variety of
* functions to describe class features and data.
*/
class ClassBuilder

ClassBuilder 有很多方法,但我们将专注于我们在 OscillatorComponent 中使用的方法。这些方法也是最常见的。 ClassBuilder: Version.

sc->Class<OscillatorComponent>()
  ->Version(1);

这指定了组件在反射上下文中的版本。您可以将其用作版本控制的一种形式。在开发过程中,每当您对组件进行重大更改时,建议增加版本。

注意:

这是 Version() 声明:

/// Add version control to the structure with optional converter.
/// If not controlled a version of 0 is assigned.
ClassBuilder* Version(
  unsigned int version,
  VersionConverter converter = nullptr)

EditContext

例 10.2. AzCore\Serialization\EditContext.h

/**
* EditContext is bound to serialize context. It uses it for data
* manipulation. Its role is to be an abstract way to generate and
* describe how a class should be edited.
  */
  class EditContext
注意:
在 EditContext 中描述类之前,必须在 SerializeContext 中描述它。 EditContext 中也使用了类似的类生成器模式。
  EditContext: Display Name and Description.
  ec->Class<OscillatorComponent>("Oscillator Component",
  "[oscillates the entity]")

Class 是一个模板化方法,用于为 Editor 声明一个组件。第一个参数是将显示在 Add Component 菜单中的名称。它不需要匹配确切的类名,并且可以包含空格。第二个是描述 ,当您在 Editor 中将鼠标悬停在其 UI 元素上时,它将显示为工具提示。

注意:

您可以在EditContext.h 中找到Class() 的声明:

  template<class T>
  EditContext::ClassBuilder
  EditContext::Class(const char* displayName,
  const char* description)

接下来是类的各种特定于 Editor 的属性。但在指定 Attribute 之前,必须声明其 EditorData:

  ->ClassElement(AZ::Edit::ClassElements::EditorData, "")

这告诉序列化系统我们将描述特定于编辑器的数据。

注意:

如果没有以下 Attribute,该组件将不会显示在 Editor 中!

  ->Attribute(AppearsInAddComponentMenu, AZ_CRC("Game"))

“Game” 这个值有点遗憾,因为它实际上意味着这个组件将出现在 Editor 的 Add Component 菜单中。如果您希望在 Editor 中隐藏您的组件,您可以完全省略此属性。

注意:

您可能想知道 Editor 旁边是否有其他 Add Component 菜单。是的,还有一些类型,例如 System 组件 (“System”)、Level 组件 (“Level”) 和 User Interface 组件 (“UI”) 以及 Canvas User Interface 组件 (“CanvasUI”)。例如,可以使用以下命令指定系统组件:

->Attribute(AppearsInAddComponentMenu, AZ_CRC("System"))

这些组件将在后面的章节中讨论。

另一个有用的属性是 Category。它允许您在 Add Component 菜单中将组件分组在一起。

->Attribute(Category, "My Project")

这是使 OscillatorComponent 显示在 Editor 中的 My Project 下的属性。

Add Component (添加组件) 菜单中的 Oscillator (振荡器) 组件

编辑器中的可配置字段

本章扩展了 OscillatorComponent 的反射,包括它的两个成员变量: m_amplitude 和 m_period。我们还将版本从 1 增加到 2,以便进行版本控制。

图 10.2.SerializeContext 和 EditContext

注意:
在 Editor 中声明可配置字段之前,必须在 SerializeContext 中定义它。另请注意,名称必须相同。在这种情况下,不同的上下文会提供同一字段的附加描述。从某种意义上说,SerializeContext 声明并创建一个字段,而 EditContext 使用 Editor 配置扩展了该定义。

例 10.3.带有编辑器配置的 reflect()

void OscillatorComponent::Reflect(AZ::ReflectContext* reflection)
{
  auto sc = azrtti_cast<AZ::SerializeContext*>(reflection);
   if (!sc) return;
      sc->Class<OscillatorComponent, Component>()
   // serialize m_period
          ->Field("Period", &OscillatorComponent::m_period)
   // serialize m_amplitude
          ->Field("Amplitude", &OscillatorComponent::m_amplitude)
          ->Version(2);
      AZ::EditContext* ec = sc->GetEditContext();
   if (!ec) return;
   using namespace AZ::Edit::Attributes;
   // reflection of this component for O3DE Editor
      ec->Class<OscillatorComponent>("Oscillator Component",
   "[oscillates the entity]")
        ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
          ->Attribute(AppearsInAddComponentMenu, AZ_CRC("Game"))
          ->Attribute(Category, "My Project")
   // expose the setting to the editor
          ->DataElement(nullptr, &OscillatorComponent::m_period,
   "Period", "[the period of oscillation]")
   // expose the setting to the editor
          ->DataElement(nullptr, &OscillatorComponent::m_amplitude,
   "Amplitude", "[the height of oscillation]");
 }

与之前的 Reflect 实现相比,有两个新的变化:Field 和 DataElement。

Field

回想一下,OscillatorComponent 有一个成员变量:m_period。鉴于此,可以使用以下命令将其声明为序列化字段:

->Field("Period", &OscillatorComponent::m_period)

Field 的第一个参数是 const char*,第二个参数是指向成员变量的点。

DataElement

Editor(和 Project Configurator)的对应项是 DataElement:

->DataElement(Default, &OscillatorComponent::m_period,
"Period", "[the period of oscillation]")

第一个元素是自定义 UI 标识符。通常,AZ::Edit::UIHandlers::D efault(或简称 nullptr)适用于大多数用例。这包括常规的单个值,例如整数、浮点数、字符串和向量(AZ::Vector3 等)。如果您必须明确指定默认 UI 处理程序,则可以参考 AZ::Edit::UIHandlers 下的可用选项列表。

例 10.4. AzCore\Serialization\EditContextConstants.inl

namespace UIHandlers
{
/**
* Helper for explicitly designating the default UI handler.
* i.e. DataElement(DefaultHandler, field, ...)
    */
 const static AZ::Crc32 Default = 0;
 const static AZ::Crc32 Button = AZ_CRC("Button", 0x3a06ac3d);
 const static AZ::Crc32 Color = AZ_CRC("Color", 0x665648e9);
 ...

DataElement 的第二个参数是指向组件的成员变量的指针。第三个参数是之前使用 Field 分配给成员变量的名称。最后一个参数是 description。

小结

通过本章中的更改,现在可以在 Editor 中配置该组件。

Editor 中的可配置选项

注意:
本章的源代码可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch10_reflection

您可以通过修改值并使用快捷键 CTRL+G 立即在 Editor 中测试行为来快速测试更改。(ESC 在 Editor 中退出该剧。