IN THIS ARTICLE
Creating EventData Types
You create objects deriving from the
EMotionFX::EventData
interface to attach an arbitrary number of data objects to a single event.
To create an EventData type
Using the following criteria, subclass either the
EventDataclass or theEventDataSyncableclass:- For general-purpose parameters, subclass the
EventDatatype. - For parameters for the sync track, use the
EventDataSyncableclass. These parameters are for synchronizing blended motions.
The following code snippet subclasses the
EventDataSyncableclass to create aLeftFootEventfor a left footstep.class LeftFootEvent final : public EMotionFX::EventDataSyncable { public: AZ_RTTI(LeftFootEvent, "{117454DC-0675-483E-843E-841C57A4354D}", EventDataSyncable); LeftFootEvent() = default; ...- For general-purpose parameters, subclass the
Implement the
Equalfunction in the subclass. TheEqualfunction tests whether twoEventDatainstances are equal and is used for deduplication ofEventDatainstances.The following example checks whether the
EventDatapassed in is aLeftFootEvent.bool Equal(const EventData& rhs, const bool /*ignoreEmptyFields*/) const override { // All LeftFootEvents are equal. const LeftFootEvent* rhsEvent = azrtti_cast<const LeftFootEvent*>(&rhs); return rhsEvent != nullptr; }
For more information about the Equal function, refer to
More About the Equal Function
.
- Implement the
Reflectmethod to reflect the type to the serialization context and edit context .
When you reflect the event to the edit context, add the Creatable attribute to ClassElement. This makes the EventData type visible in Animation Editor in the Motion Events tab so that users can select it.
The following code example reflects the LeftFootEvent to the serialization and edit contexts.
...
static void Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (!serializeContext) return;
serializeContext->Class<LeftFootEvent, EventDataSyncable>()->Version(1);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (!editContext) return;
editContext->Class<LeftFootEvent>("LeftFootEvent", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ_CRC("Creatable", 0x47bff8c4), true);
}
...
To add the event to a motion, use the
FindOrCreateEventDatatemplate, which accepts any subclass ofEventData.... auto footstepData = GetEMotionFX().GetEventManager()->FindOrCreateEventData<LeftFootEvent>(); motion->GetEventTable()->FindTrackByName("Sound")->AddEvent(0.2f, footstepData); ...
Example
The following example shows the completed LeftFootEvent sample EventData subclass and the code to add the event to a motion.
class LeftFootEvent final
: public EMotionFX::EventDataSyncable
{
public:
AZ_RTTI(LeftFootEvent, "{117454DC-0675-483E-843E-841C57A4354D}", EventDataSyncable);
LeftFootEvent() = default;
static void Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (!serializeContext) return;
serializeContext->Class<LeftFootEvent, EventDataSyncable>()->Version(1);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (!editContext) return;
editContext->Class<LeftFootEvent>("LeftFootEvent", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ_CRC("Creatable", 0x47bff8c4), true);
}
bool Equal(const EventData& rhs, const bool /*ignoreEmptyFields*/) const override
{
// all LeftFootEvents are equal
const LeftFootEvent* rhsEvent = azrtti_cast<const LeftFootEvent*>(&rhs);
return rhsEvent != nullptr;
}
size_t HashForSyncing(bool isMirror) const override { return isMirror ? 1 : 0; }
protected:
LeftFootEvent(const size_t /*hash*/) : LeftFootEvent() {}
};
auto footstepData = GetEMotionFX().GetEventManager()->FindOrCreateEventData<LeftFootEvent>();
motion->GetEventTable()->FindTrackByName("Sound")->AddEvent(0.2f, footstepData);
More About the Equal Function
The Equal function tests whether two EventData instances are equal.
Syntax
virtual bool Equal(const EventData& rhs, bool ignoreEmptyFields = false) const = 0;
EMotion FX uses the Equal method to deduplicate instances of EventData subclasses. The AnimGraphMotionCondition class also uses it for motion event matching logic.
When Event Manager loads a .motion file and deserializes the motion events on the event tracks, EventManager::FindOrCreateEventData processes each EventData instance.
The EventManager stores a list of the EventData instances in use and attempts to find an EventData instance in which the call to Equal(loadedEventData) returns true. If the EventManager finds an EventData instance that is equal, the duplicate data is discarded.
When a call to AnimGraphMotionCondition tests a motion event, AnimGraphMotionCondition::TestCondition calls the Equal method with the ignoreEmptyFields parameter set to true. The ignoreEmptyFields parameter enables partial matching of EventData instances. For example, if one of the fields is a string and the string value is empty in the condition, any value in the field matches.
Synchronizing Blended Motions
The EMotionFX::EventDataSyncable class extends the functionality of the base EventData class and enables events that drive motion synchronization behavior. Use the EventDataSyncable class to specify parameters for synchronizing blended motions. The class calls HashForSyncing on the sync tracks of two different motions, compares the results, and finds events that are equal based on their hash value.
Mirroring
You can use EMotionFX to mirror motions programmatically. When a motion is being mirrored, its sync events must also be mirrored. To signal this mirroring, the HashForSyncing method accepts an isMirror parameter.
For example, suppose that you use an EventDataSyncable subclass to mirror the gait of a horse. You use an integer field to represent the feet of the horse with the following convention.
0=left rear
1=right rear
2=left front
3=right front
Implement HashForSyncing as in the following example.
size_t HashForSyncing(bool isMirror) const
{
if (!isMirror)
{
return m_footIndex;
}
// Translate left foot (an even foot index) to right foot, and vice
// versa
return (m_footIndex % 2 == 0) ? m_footIndex + 1 : m_footIndex - 1;
}
The default implementation for HashForSyncing returns the hash of the type ID of the type and ignores the isMirror parameter.