Lumberyard
Guía del usuario (Version 1.18)

Proporcionar una lógica de interpolación propia para el componente Transform

Puede añadir lógica de interpolación propia al componente Transform mediante la utilización de un marco común disponible en C++. En este tema se muestra cómo utilizar C++, la serialización y la IU del Lumberyard Editor para escribir un modo de interpolación propio.

Agregar un modo interpolación a la interfaz de usuario del Lumberyard Editor

Puede empezar desde la interfaz de usuario en el editor para el componente Transform, específicamente con las opciones de interpolación. En la imagen siguiente se muestran las opciones None (Ninguno) o Linear (Lineal) del componente Transform en el Entity Inspector.


                Opciones de Position interpolation (Interpolación de posición) para el componente Transform

Ver cómo se aplican estas opciones en el código puede ayudar a comprender los cambios que son necesarios para agregar opciones propias.

Las opciones de interpolación None (Ninguno) y Linear (Lineal) del Entity Inspector proceden de la serialización de AzToolsFramework:: TransformComponent:: Reflect, que es la variación del editor de TransformComponent.

El siguiente código fuente relacionado se encuentra en el archivo dev\Code\Framework\AzToolsFramework\AzToolsFramework\ToolsComponents\TransformComponent.cpp.

namespace AzToolsFramework { void TransformComponent::Reflect(AZ::ReflectContext* context) { // Reflect data for script, serialization, editing. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) { ... if (AZ::EditContext* ptrEdit = serializeContext->GetEditContext()) { ptrEdit->Class<TransformComponent>("Transform", "Controls the placement of the entity in the world in 3d")-> ... ClassElement(AZ::Edit::ClassElements::Group, "Network Sync")-> Attribute(AZ::Edit::Attributes::AutoExpand, true)-> DataElement(AZ::Edit::UIHandlers::Default, &TransformComponent::m_netSyncEnabled, "Sync to replicas", "Sync to network replicas.")-> DataElement(AZ::Edit::UIHandlers::ComboBox, &TransformComponent::m_interpolatePosition, "Position Interpolation", "Enable local interpolation of position.")-> EnumAttribute(AzFramework::InterpolationMode::NoInterpolation, "None")-> EnumAttribute(AzFramework::InterpolationMode::LinearInterpolation, "Linear")-> DataElement(AZ::Edit::UIHandlers::ComboBox, &TransformComponent::m_interpolateRotation, "Rotation Interpolation", "Enable local interpolation of rotation.")-> EnumAttribute(AzFramework::InterpolationMode::NoInterpolation, "None")-> EnumAttribute(AzFramework::InterpolationMode::LinearInterpolation, "Linear"); ... } } ... } }

Una línea de código como la siguiente añade estas opciones al Lumberyard Editor. Contiene un valor enum y un nombre de cadena para el valor que aparece en el editor. El código de reflexión para las opciones de posición y rotación es similar.

EnumAttribute(AzFramework::InterpolationMode::NoInterpolation,"None")->

AzFramework:: InterpolationMode:: NoInterpolation es un valor enum del espacio de nombres AzFramework. Puede encontrar el código fuente relacionado en el archivo dev\Code\Framework\AzFramework\AzFramework\Math\InterpolationSample.h.

namespace AzFramework { /** * Behavior types for smoothing of transform between network updates. */ enum class InterpolationMode : AZ::u32 { NoInterpolation, LinearInterpolation, }; }

Siguiendo este ejemplo podrá agregar su propio valor enum. En el siguiente ejemplo se llama MyInterpolation.

namespace AzFramework { /** * Behavior types for smoothing of transform between network updates. */ enum class InterpolationMode : AZ::u32 { NoInterpolation, LinearInterpolation, MyInterpolation, // <--- NEW CONTENT }; }

Después, actualice AzToolsFramework:: TransformComponent ::Reflect, como en el siguiente ejemplo.

namespace AzToolsFramework { void TransformComponent::Reflect(AZ::ReflectContext* context) { // Reflect data for script, serialization, editing. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) { ... DataElement(AZ::Edit::UIHandlers::ComboBox, &TransformComponent::m_interpolatePosition, "Position Interpolation", "Enable local interpolation of position.")-> EnumAttribute(AzFramework::InterpolationMode::NoInterpolation, "None")-> EnumAttribute(AzFramework::InterpolationMode::LinearInterpolation, "Linear")-> EnumAttribute(AzFramework::InterpolationMode::MyInterpolation, "My Mode")-> // <--- NEW CONTENT ... } } } }

Implementación de la lógica de interpolación en C++

Ahora que Lumberyard Editor tiene la nueva opción, puede implementarla en el código.

AzToolsFramework:: TransformComponent es simplemente un componente que el editor utiliza para crear el homólogo del componente del juego AzFramework:: TransformComponent. Examine el siguiente código fuente para la clase TransformComponent. El código indica dónde se guardan las muestras de interpolación y se calcula el valor interpolado. El código está en el archivo dev\Code\Framework\AzFramework\AzFramework\Components\TransformComponent.h.

namespace AzFramework { class TransformComponent : public AZ::Component , public AZ::TransformBus::Handler , public AZ::TransformNotificationBus::Handler , public AZ::EntityBus::Handler , public AZ::TickBus::Handler , private AZ::TransformHierarchyInformationBus::Handler , public NetBindable { ... private: ... void CreateTranslationSample(); void CreateRotationSample(); AZStd::unique_ptr<Sample<AZ::Vector3>> m_netTargetTranslation; // <--- Sample<> is the base templated class for all interpolation logic. AZStd::unique_ptr<Sample<AZ::Quaternion>> m_netTargetRotation; AZ::Vector3 m_netTargetScale; ... }; } // namespace AZ

Sample<> es la clase base abstracta para la lógica de interpolación. Observe que se generaliza para poder usar vectores de posición o cuaterniones de rotación. En el código siguiente se muestra la fuente de la clase Sample en el archivo dev\Code\Framework\AzFramework\AzFramework\Math\InterpolationSample.h.

namespace AzFramework { template<typename Value> class Sample { public: virtual ~Sample() = default; using TimeType = unsigned int; Sample() : m_targetValue() , m_targetTimestamp(0) , m_previousValue() , m_previousTimestamp(0) { } void SetNewTarget(Value newValue, TimeType timestamp) // <---- Network stack provides you these values every time it gets them. { m_targetValue = newValue; m_targetTimestamp = timestamp; } virtual Value GetInterpolatedValue(TimeType time) = 0; // <---- Provide your own interpolation logic here. Value GetTargetValue() const { return m_targetValue; } TimeType GetTargetTimestamp() const { return m_targetTimestamp; } protected: void SetPreviousValue(Value previousValue, TimeType previousTimestamp) { m_previousValue = previousValue; m_previousTimestamp = previousTimestamp; } Value m_targetValue; TimeType m_targetTimestamp; Value m_previousValue; TimeType m_previousTimestamp; }; }

La implementación más sencilla es no usar interpolación, para lo que el código ya está escrito. Puede encontrarlo en el mismo archivo: dev\Code\Framework\AzFramework\AzFramework\Math\InterpolationSample.h.

template<typename T> class UninterpolatedSample; template<> class UninterpolatedSample<AZ::Vector3> final : public Sample<AZ::Vector3> { public: AZ::Vector3 GetInterpolatedValue(TimeType /*time*/) override final { return GetTargetValue(); } };

En el código siguiente se muestra una implementación de interpolación lineal que se incluye con Lumberyard (dev\Code\Framework\AzFramework\AzFramework\Math\InterpolationSample.h).

namespace AzFramework { template<typename T> class LinearlyInterpolatedSample; template<> class LinearlyInterpolatedSample<AZ::Vector3> final : public Sample<AZ::Vector3> { public: AZ::Vector3 GetInterpolatedValue(TimeType time) override final { AZ::Vector3 interpolatedValue = m_previousValue; if (m_targetTimestamp != 0) { if (m_targetTimestamp <= m_previousTimestamp || m_targetTimestamp <= time) { interpolatedValue = m_targetValue; } else if (time > m_previousTimestamp) { float t = float(time - m_previousTimestamp) / float(m_targetTimestamp - m_previousTimestamp); // lerp translation AZ::Vector3 deltaPos = t * (m_targetValue - m_previousValue); interpolatedValue = m_previousValue + deltaPos; AZ_Assert(interpolatedValue.IsFinite(), "interpolatedValue is not finite!"); } } SetPreviousValue(interpolatedValue, time); return interpolatedValue; } }; }

En el siguiente ejemplo se muestra una implementación completada llamada MyInterpolatedSample que proporciona únicamente la interpolación vectorial de la posición.

namespace AzFramework { template<typename T> class MyInterpolatedSample; template<> class MyInterpolatedSample<AZ::Vector3> final : public Sample<AZ::Vector3> { ... } }

Por último, debe habilitar TransformComponent para elegir la implementación de la clase. Puede hacerlo en la instrucción switch case en AzFramework:: TransformComponent:: CreateTranslationSample(). El código fuente está en el archivo dev\Code\Framework\AzFramework\AzFramework\Components\TransformComponent.cpp.

void AzFramework::TransformComponent::CreateTranslationSample() { switch(m_interpolatePosition) { case InterpolationMode::LinearInterpolation: m_netTargetTranslation = AZStd::make_unique<LinearlyInterpolatedSample<AZ::Vector3>>(); break; case InterpolationMode::NoInterpolation: default: m_netTargetTranslation = AZStd::make_unique<UninterpolatedSample<AZ::Vector3>>(); break; } }

Proporcione una instrucción case nueva para la opción enum. La opción crea una instancia de la clase MyInterpolationSample.

void AzFramework::TransformComponent::CreateTranslationSample() { switch(m_interpolatePosition) { case InterpolationMode::MyInterpolation: // <--- NEW CONTENT m_netTargetTranslation = AZStd::make_unique<MyInterpolatedSample<AZ::Vector3>>(); // <--- NEW CONTENT break; // <--- NEW CONTENT case InterpolationMode::LinearInterpolation: m_netTargetTranslation = AZStd::make_unique<LinearlyInterpolatedSample<AZ::Vector3>>(); break; case InterpolationMode::NoInterpolation: default: m_netTargetTranslation = AZStd::make_unique<UninterpolatedSample<AZ::Vector3>>(); break; } }

Y ya está! Ahora puede seleccionar la nueva opción en Entity Inspector y la lógica de interpolación personalizada lleva a cabo el trabajo.

Interpolación de rotación

El ejemplo anterior muestra cómo proporcionar una interpolación de posición propia. La interpolación de rotación es similar, pero con las siguientes diferencias menores:

  • Debe proporcionar la implementación de plantilla de MyInterpolationSample<Quaternion>.

  • Tiene que escribir código de similar en AzFramework:: TransformComponent:: CreateRotationSample(), en lugar de en ::CreateTranslationSample().

  • En la ubicación de las opciones de interpolación, AzToolsFramework:: TransformComponent tiene una definición de interpolación de rotación independiente.