My Understanding of the Unreal Animation Framework in 5.6
About two months ago, the technical demonstration of The Witcher 4 at Unreal Fest showcased the next-generation animation system of the Unreal Engine (Unreal Animation Framework, hereafter referred to as UAF, while the previous animation blueprint system will be referred to as ABP). This sparked my strong curiosity, and I felt it was time to dive into understanding this system.
This article will analyze the system from a architecture perspective, primarily introducing its components, the meanings of various types, and their logical relationships. I hope this will help everyone grasp and get started with this new animation system, but it will not cover details such as animation blending calculations or animation retargeting.
Simple demo
Let’s start with a simple demo:
The video shows a simple layered blending effect, with the upper body coming from the static frame of the bow drawing animation
and the lower body using a looping sprint animation
.
BlendMask
utilizes a HierarchyTable
:
It is a general-purpose hierarchical data container, used here as
BlendProfile
From the left view, it’s clear that the two models completely overlap, and the animation effects are identical:
This is because both are running the same animation graph of UAF
:
The upper left is the
lower body
animation, the lower left is theupper body
animation, and the right is thelayered blending
The difference is that the animation graph on the left is updated using the UAF framework:
These nodes here may not be the optimal implementation, but they are sufficient for simple demonstration
while on the right side, the animation graph is updated using ABP :
animation graph of UAF can be integrated into animation blueprints through this special animation node
Unified Workspace Interface
UAF has integrated the Workspace Editor
, providing a unified view of multiple assets.
The workspace itself also has a corresponding asset, classified as UAF Workspace
, which should
be used to store metadata related to the workspace.
The
Workspace Editor
module comes from the new experimental pluginWorkspace
, which allows multiple assets to be edited in a unified interface.UAF has integrated this feature, which specifies that UAF-related asset types can be edited in the same workspace.
See:
UAnimNextWorkspaceSchema
,IWorkspaceEditorModule::RegisterObjectDocumentType
The workspace
tab in the upper left corner lists the assets opened in the current workspace:
Namely, AG_SequencePlayer
, UAFM_Module
, and the workspace’s own asset: UAFW_Workspace
:
System Composition
These raw cpp types bellow are basically located in the
UE::AnimNext
namespace
The logical carriers of UAF currently consist of two main components: Module
and AnimationGraph
, both running within RigVM
, supporting multithreaded
execution.
Data exchange between threads is accomplished through UAnimNextComponent::PublicVariablesProxy
.
The comment in FAnimNextPublicVariablesProxy
mentions that currently, it copies dirty-marked data every frame, with plans to change it to a double-buffered array
in the future (refer to USkinnedMeshComponent::ComponentSpaceTransformsArray
).
See:
FAnimNextModuleInstance::CopyProxyVariables
IAnimNextVariableProxyHost::FlipPublicVariablesProxy
UAnimNextComponent::SetVariable
UAnimNextComponentWorldSubsystem::Register
Module
The module
here is where various functions
are used to write logical business
, similar to the blueprint section
in ABP / UAnimInstance::NativeUpdateAnimation
, UAnimInstance::NativeThreadSafeUpdateAnimation
, but more powerful and flexible.
FRigUnit_AnimNextModuleEventBase
:
Through the interface provided by the base class, each module
can choose whether it needs an independent TickFunction
, which Tick Group
to run in, whether to operate on the game thread
, and other functionalities.
The UAF compiler will also automatically generate some modules
, such as variable binding-related FRigUnit_AnimNextExecuteBindings_GT
and FRigUnit_AnimNextExecuteBindings_WT
.
AnimationGraph
The animation graph
is a collection of animation logic
and its data
, similar to the animation tree
in ABP.
The difference is that in UAF, there are no longer various animation nodes
; instead, there is a TraitStack
node combined with various Trait
combinations.
The animation graph itself acts as a UObject
, also holding references to UObject
references that referenced by shared data
within the graph, preventing them from being garbage collected.
See:
UAnimNextAnimationGraph::GraphReferencedObjects
UAnimNextAnimationGraph::GraphReferencedSoftObjects
Additionally, animated charts can have multiple entries
, not just Root
TraitStack and TraitStack Node
TraitStack
: As the name suggests, this is a stack
structure composed of Traits
, which includes 1
base trait
and several additive traits
.
The corresponding node
is simply a standard RigUnit
node (struct):
A TraitStack node can contain one or more
TraitStacks.
In the editor, it appears as shown in the layered blending animation graph above.
The node form is just for the convenience of
visualization
in the editor. Aftercompiling
, the correspondingTraitStack
will be serialized into the animation graph. ThisRigUnit
node will not be executed
Trait
“trait” or feature, it refers to reusable functionalities in animation logic, similar to animation nodes
in ABP, but again, more powerful and flexible.
FTrait
FTrait
is the base class for all Traits
, defining the necessary basic interface, such as obtaining its unique ID
.
Derived traits are composed of FBaseTrait
or FAdditiveTrait
along with the derived interface class
of ITraitInterface
.
ITraitInterface
is the base class for all trait interfaces
.
It contains only one method for getting UID
, meaning each trait interface
also has a unique ID
.
Currently, these two unique IDs are derived by applying the FNV1a
hash algorithm to the class name
. This algorithm is characterized by the fact that for the same character combination, whether the characters are normal characters
or wide characters
, it does not affect the hash result, producing the same hash value, and is simple and efficient.
See:
FTraitUID::MakeUID
FTraitInterfaceUID::MakeUID
Trait objects themselves cannot have internal state, meaning they are stateless
, as their logic runs in worker threads (for example, multiple objects reusing the same animation graph within the same frame execute in different threads).
Their state data should be declared using the type aliases FSharedData
and FInstanceData
, which the UAF system will allocate externally for the Trait objects.
FSharedData is read-only data that can be shared among multiple instances of the same animation graph; it is a USTRUCT
that will serialize and save
to a file, typically consisting of some hardcoded configurations.
FInstanceData contains the dynamic/instanced data required by the nodes in each animation graph instance and is a raw CPP structure.
FSharedData is similar to
FoldProperty
in ABP,while the mechanism of InstanceData is almost the same as
FInstanceDataType
/UInstanceDataType
inStateTree
Code Generation
UAF uses several macros to quickly and easily generate the code required for the framework, reducing repetitive work.
Macros for Trait Interface
The trait interface part is relatively simple, with only two macros.
DECLARE_ANIM_TRAIT_INTERFACE
declares and implements GetInterfaceUID
, returning a compile-time constant
:
AUTO_REGISTER_ANIM_TRAIT_INTERFACE
statically registers
the shared pointer of the trait interface class to the global trait interface registry
:
Macros for Trait
The trait section is considerably more complex:
First, you need to use the DECLARE_ANIM_TRAIT
macro within the trait class to declare
some virtual function overrides:
This includes several nested macros:
ANIM_NEXT_IMPL_DECLARE_ANIM_TRAIT_BASIC
declares and implements GetTraitUID
, returning a compile-time constant
; GetTraitName
returns the trait name; declares an alias for TraitSuper
.
ANIM_NEXT_IMPL_DECLARE_ANIM_TRAIT_INSTANCING_SUPPORT
add declarations related to trait data.
ANIM_NEXT_IMPL_DECLARE_ANIM_TRAIT_INTERFACE_SUPPORT
add declarations for accessing the trait interface.
ANIM_NEXT_IMPL_DECLARE_ANIM_TRAIT_EVENT_SUPPORT
add declarations related to trait events.
ANIM_NEXT_IMPL_DECLARE_ANIM_TRAIT_LATENT_PROPERTY_SUPPORT
add declarations related to Latent Property (see below for the meaning of Latent Property
).
Then, use the GENERATE_ANIM_TRAIT_IMPLEMENTATION
macro to define the above interfaces.
Notably, the parameters InterfaceEnumeratorMacro, RequiredInterfaceEnumeratorMacro, EventEnumeratorMacro
are all EnumeratorMacros
, which are macros used for enumeration, with their prefixes indicating what they enumerate: trait interface
, required trait interface
, and trait events
.
The enumeration macro
has one parameter, which is also a macro that takes the enumerated item
as an argument and performs the corresponding operations.
Taking FBlendTwoWayTrait
as an example:
The locally defined TRAIT_INTERFACE_ENUMERATOR
macro enumerates all the trait interfaces implemented by FBlendTwoWayTrait
and passes these interfaces to the GeneratorMacro
parameter.
Combining with the nested macros in GENERATE_ANIM_TRAIT_IMPLEMENTATION
:
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT
defines the memory size and alignment for shared and instance data, as well as the constructor and destructor.
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT_GET_LATENT_PROPERTY_MEMORY_LAYOUT
defines the function to retrieve the memory layout information for Latent Property.
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT_IS_PROPERTY_LATENT
defines the function to determine whether the property with the corresponding name is a Latent Property.
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT_GET_INTERFACE
defines the function to retrieve a pointer to the specified trait interface.
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT_GET_INTERFACES
defines the function to retrieve the IDs of all implemented trait interfaces.
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT_GET_REQUIRED_INTERFACES
defines the function to retrieve the IDs of all required trait interfaces.
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT_ON_TRAIT_EVENT
defines the function to respond to the required trait event callbacks.
ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT_GET_TRAIT_EVENTS
defines the function to retrieve the IDs of all responsive trait events.
At this point, the necessary trait definitions for the framework has been automatically generated.
AUTO_REGISTER_ANIM_TRAIT
, similar to AUTO_REGISTER_ANIM_TRAIT_INTERFACE
, registers the trait this time.
Trait registration does not use shared pointers, but is constructed from the callback of
UE::AnimNext::TraitConstructorFunc
with theDestPtr
address passed in.
Latent Property
Latent Property
is the part of shared data that needs to be instantiated, placed after the FInstanceData
section.
For shared data that inherits from FAnimNextTraitSharedData
, you need to use the GENERATE_TRAIT_LATENT_PROPERTIES
macro to manually
、selectively
register the properties that should be marked as Latent Property
:
This macro also uses an enumeration macro as a parameter, within which it nests the macro:
ANIM_NEXT_IMPL_DEFINE_LATENT_CONSTRUCTOR
uses placement new
to individually
construct Latent Property
(the memory address is discussed in the following section on FNodeInstance
).
See:
FExecutionContext::AllocateNodeInstance
for how it allocates memory, constructsinstance data
, andLatent Property
GENERATE_ANIM_TRAIT_IMPLEMENTATION
-ANIM_NEXT_IMPL_DEFINE_ANIM_TRAIT
-ConstructTraitInstance
GENERATE_TRAIT_LATENT_PROPERTIES
-ANIM_NEXT_IMPL_DEFINE_LATENT_CONSTRUCTOR
-ConstructLatentProperties
ANIM_NEXT_IMPL_DEFINE_LATENT_DESTRUCTOR
Destruct
Latent Property
one by one
ANIM_NEXT_IMPL_DEFINE_GET_LATENT_PROPERTY_INDEX
Query the index of the Latent Property
for the corresponding offset
FAnimNextTraitSharedData::GetLatentPropertyIndex
comments mentioned: If theLatent Property
corresponding to the offset can be found, it returns the index starting from 1; if not found, it returns the number ofLatent Property
, which is less than or equal to 0 (needs to be negative)
ANIM_NEXT_IMPL_DEFINE_LATENT_GETTER
generates a getter
function for retrieving property values from FTraitBinding
for each Latent Property
Magic of Macro ! It uses the
constexpr
functionGetLatentPropertyIndex
to get theLatentPropertyIndex
, and then get theLatent Property
reference from theBinding
.
TraitEvent Trait Events
FAnimNextTraitEvent
is the base class for trait events
.
DECLARE_ANIM_TRAIT_EVENT
, similar to traits and trait interfaces, declares and defines EventUID
, and additionally supports the IsA
functionality, serving as a simple alternative mechanism for RTTI
.
Since FAnimNextTraitEvent is
USTRUCT
, theIsA
should not be necessary. This may be for performance or other considerations.
Trait events are similar to UI click events and can be marked as Handled
, with the option to set a valid duration or an infinite duration, among other settings.
Global Registry
FTraitRegistry
The global registry for trait
objects.
When registering traits using macros, FTraitRegistry
prioritizes the default allocated 8KB
size StaticTraitBuffer
to store traits
. If this limit is exceeded, it uses DynamicTraits
for storing new trait, which is an optimization for memory locality
.
There is an interesting little detail here. The DynamicTraits array stores
uintptr_t
instead ofvoid*
orFTrait*
, which meansusing integers to store pointers
.Because integers are used,
index of array
can be stored at the same time to implement the subsequentFreeList
mechanism:When
DynamicTraitFreeIndexHead
is valid,DynamicTraits[DynamicTraitFreeIndexHead]
stores the next reusable array element
Additionally, several Map
s are stored to speed up queries.
FTraitRegistry::Register
is used to register traits to DynamicTraits.
FTraitInterfaceRegistry
The global registry for trait interface
objects.
In comparison, FTraitInterfaceRegistry
is quite straightforward, simply a map from interface IDs to smart pointers.
Node
This section introduces some key types related to nodes.
FNodeTemplate
A FNodeTemplate
is a combination of a set of traits
, which can include multiple sets of base + additive
traits. All animation graphs can share the same template object.
The traits
that make up the node template are stored as FTraitTemplate
objects at the end of the contiguous memory
of the FNodeTemplate
object.
FNodeTemplateBuilder::BuildNodeTemplate
constructs an FNodeTemplate object and the FTraitTemplate contained in it in a contiguous buffer ofTArray<uint8>
From it, you can obtain the UID
, base address of the trait array
, the number of traits, and other information.
FNodeTemplate::NodeSharedDataSize
is the size of theshared data
for all traits (including theFLatentPropertiesHeader
of the base trait) after alignment.FNodeTemplate::NodeInstanceDataSize
is the size of theinstance data
for all traits after alignment.
FTraitTemplate
FTraitTemplate
is the trait
within the node template. In addition to providing basic information about the trait such as UID
, trait type, shared/instance data, number of subtraits, and number of Latent Properties
, it also allows you to obtain the offset of the shared data
, the offset of the pointer to the shared latent property array
, and the offset of the instance data
.
See:
FNodeTemplate::Finalize
I think the naming of the two member functions
FTraitTemplate::GetTraitDescription
is a bit confusing. The more understandable name should beGetTraitSharedData
, which is used to get the shared data pointer of the trait. It may be a typo or a name change.
FNodeDescription
The only read-only data within an animation graph
. Although the object itself is 8 bytes in size, when allocating memory, it includes the size of the shared data from the traits on the node, making it an object whose size varies in usage
.
See:
FTraitReader::ReadGraphSharedData
and some other details:
FNodeDescription::Serialize
FTrait::SerializeTraitSharedData
FTraitWriter::WriteNode
FTrait::SaveTraitSharedData
After reading, it is stored in
UAnimNextAnimationGraph::SharedDataBuffer
. For usage, refer toFExecutionContext::GetNodeDescription
FNodeDescription::TemplateHandle
is used to obtain an instance of theFNodeTemplate
object fromFNodeTemplateRegistry
.FNodeDescription::NodeInstanceDataSize
contains the size of theinstantiation data
, plus thetotal size
of allLatent Properties
.
Note the difference between
FNodeDescription::NodeInstanceDataSize
andFNodeTemplate::NodeInstanceDataSize
In addition,
FNodeDescription
andFNodeTemplate
aremany-to-one
, which can be understood from their usage.
FNodeInstance
The instantiated data of the node, dynamically created at runtime, has a size of 16 bytes itself. When allocating memory, it includes the size of the instantiated data of the traits and their Latent Property
size, which is also an object that varies in size depending on usage
; built-in reference counting.
For usage, refer to :
FAnimationAnimNextRuntimeTest_TraitSerialization::RunTest
,FExecutionContext::AllocateNodeInstance
FNodeTemplateRegistry
The global registry of the FNodeTemplate
object, ensuring that all FNodeTemplate
instances are contiguous in memory.
FTraitStackBinding
Describes the data required for a set of traits
(one base trait and its children/additive traits) used to query traits or the trait interface
.
See:
FTraitStackBinding::FTraitStackBinding
,especially the last few lines
For example, FTraitStackBinding::GetInterfaceImpl
: attempts to find the trait that implements the specified InterfaceUID
from the trait stack
and returns the binding
of that trait.
FTraitBinding
Describes the data of a specific trait
within a set of traits, allowing you to query whether the current trait implements the specified trait interface
.
TTraitBinding
Strongly typed/type-safe FTraitBinding
.
FExecutionContext
Since traits are stateless, the dynamic data
/instance data
during execution requires an object to hold it, which is the FExecutionContext
, the execution context object.
It is used to bind
to a graph instance and provides a unified trait query
interface for nodes.
The Update
and Evaluate
processes of the graph are encapsulated in the execution function of a RigVM node: FRigUnit_AnimNextRunAnimationGraph_v2_Execute()
.
FUpdateTraversalContext
The derived context object used when updating
the graph.
Internally, it uses a stack (LIFO) allocated on the MemStack
to implement a depth-first traversal
of the trait tree
, rather than the recursive
method used in ABP.
See:
UE::AnimNext::UpdateGraph
. Each trait is executed twice in the while loop, corresponding toIUpdate::PreUpdate
andIUpdate::PostUpdate
.
IUpdate::OnBecomeRelevant
is also called here.
FEvaluateTraversalContext
The derived context object used when evaluating
the graph.
The internal FEvaluationProgram
is used to store the FAnimNextEvaluationTask
that each node needs to execute while traversing the graph.
It then calls FEvaluationProgram::Execute
to perform each evaluation task on the FEvaluationVMStack
.
See:
UE::AnimNext::EvaluateGraph
, the execution process is similar toUpdateGraph
FAnimNextEvaluationTask
FAnimNextEvaluationTask
is a logical object that can be reused between traits, representing micro-instructions running on the evaluation virtual machine
, which can handle input and output at the same time through the internal state of the virtual machine (also a stack
).
UAF Modules
UAF consists of multiple modules:
On the ue5-main
branch, the prefix of the series of module names has changed from AnimNext
to UAF
.
Among them:
UAF
/AnimNext
: Provides core animation utility functions, defines base class interfaces, etc. For example: UE::AnimNext::FDecompressionTools::GetAnimationPose
UAFAnimGraph
/AnimNextAnimGraph
: Implements RigVM-based functionality related to animation graphs.
These two modules are the main focus of this article.
Other modules introduce functionalities from other modules/systems
to UAF
, such as incorporating StateTree
, PoseSearch
, etc.
SoA (struct of array)
Each utility function in TransformArrayOperations.h
has both AoS
and SoA
versions, with the code prioritizing the use of AoS
. This indicates that in terms of data structure design, UAF is more data-oriented
.
Conclusion
UAF is a redesigned, data-oriented, composition-oriented, high-performance, flexible, concise, and easily extensible animation framework.
It completely abandons the ABP framework and embraces RigVM.
Due to its feature set still being in development, it is currently in an experimental phase.
Miscellaneous
Writing Animation Blueprints in Code
Refer to code of test cases
from UAFTestSuite
/AnimNextTestSuite
and UAFAnimGraphTestSuite
/AnimNextAnimGraphTestSuite
modules.
Playing “Montages” in UAF
Due to the length of this article, I decide to not to elaborate further.
The relevant nodes are UInjectionCallbackProxy
, UPlayAnimCallbackProxy
.
The related code can be found in UE::AnimNext::FInjectionUtils::Inject
.