Animation Graph Programming

Since SDK release 3.5 this is obsolete. The Animation Graph has been superseded by the Mannequin system.

C++ Implementation Classes

The animation graph code side consists of two parts:

  • Static part (class =CAnimationGraph=)
  • Dynamic part (class =CAnimationGraphState=)

The static part represents the structure of the graph and holds the properties for each of its components. There is one instance for each loaded animation graph file, all of which are owned by the animation graph manager (class =CAnimationGraphManager=).

The dynamic part represents the current state of one character entity instance in the level. This part of the animation graph is owned by the animated character (class =CAnimatedCharacter=).

If two animation graphs are used for a character, there are two separated dynamic parts of the animation graph that are encapsulated in one wrapper class which behaves same as a single CAnimationGraphState class. The CAnimatedCharacter class owns the wrapper class and the wrapper class owns both dynamic animation graph parts.

Setting Input Values

Values of the animation graph inputs can be modified by any module of the engine, as well as from Lua scripts.

Using C++ code

A pointer to the character's IAnimationGraphState object (which is the interface to the dynamic part of the animation graph) will be needed and then one of these methods can be called on it:

bool SetInput( InputID, float, TAnimationGraphQueryID * pQueryID = 0 );
bool SetInput( InputID, int, TAnimationGraphQueryID * pQueryID = 0 );
bool SetInput( InputID, const char *, TAnimationGraphQueryID * pQueryID = 0 );

The first parameter is the ID of the input, the second is the value to be set on it (the value will be converted to match the type of the input). The third parameter is optional and used to obtain a query id – used in case of additional feedback about this operation is needed (e.g. when the animation matching that input value begins).

The return value will be true if the operation has succeeded.

The following is same as SetInput except that it will not set the default input value in case a non-existing value is passed. So this might be useful when you want to change only an upper body animation without affecting the current full body animation.

bool SetInputOptional( InputID, const char *, TAnimationGraphQueryID * pQueryID = 0 );

Inputs can also be set by specifying their name instead of the ID, using this method:

template <class T> inline bool SetInput( const char * name, T value, TAnimationGraphQueryID * pQueryID = 0 );

However, using the ID saves one string lookup. Therefore the preferred way to reference inputs is to store their IDs in variables, to be used later. Input names can be converted to input IDs using this method:

InputID GetInputId( const char * input ) = 0;

Using Lua Scripting

Changing animation graph input values from scripts is not recommended but still possible. To do so, this Lua script function can be used:

entity.actor:SetAnimationInput( name, value );

... where entity is the entity script table, name is the name of the input and value is the value to be set on it.

Implementing a New Modifier

This section is only valid for AnimationGraph Versions 1.5 an up.

To add a modifier with new functionality to the graph, two things are needed. An Editor frontend handling the parameters and values that can be set in the graph. And a new Node Type that implements the actual functionality in the runtime of the graph in CryAction. For implementing the latter, see Implementing a New Node Type.

For creating an Editor frontend for the new Node Type and make it available in the Animation Graph Editor, you need access to the Sandbox Code.

Creating a new interface panel

The Modifier's Interface consists of two things - a dialog resource and an implementation of the base class:

CAG2ModifierBase

For both it is easiest to duplicate an existing modifier whose functionality comes closest to the one you are about to implement. The dialog resources for modifiers all start with

IDD_AG2_MODIFIER_{ModifierName}

All Modifiers available in the Editor can be found in the Solution inside Sandbox project->HyperGraph->AnimationGraph 1.5->Modifier Nodes. Add a new class in here (or rather copy an existing one).
The CAG2ModifierBase class has a few mandatory and a couple of optional overridable functions.
These function are documented in the header file, but here's a short, sumarized list:

// Mandatory Functions to implement
// =====================================
const CString GetHumanReadableName() // This is the name as it will appear in the modifier itself and in the menu
const CString GetClassName() // This is the internally used name, it must be unique and may contain no whitespaces
void Save(XmlNodeRef modifierNode) const // Is called when the Animation Graph is saved to xml (NOT the ag file)
void Load(XmlNodeRef modifierNode) // Is called when the AG Editor opens a graph from xml (read in what Save wrote)
void Export(XmlNodeRef node) const // This saves only the data needed for the Node in CryAction (there is a corresponding loading function)
CAG2ModifierBase* Duplicate() const /* Needed to create new instances, just implement with return new yourClassName;*/

Registering the new Modifier

Once the new CAG2ModifierBase class is done, it only needs to be registered with the Modifier Manager once. The Manager will create one instance of each Modifier, and then only use the Duplicate function to create new ones when a new Modifier is to be added to the graph via the menu. If the Modifier is marked as a singleton, the Modifier Manager will not create a new instance, but rather return the original one.

Registering the new Modifier with the Manager will automatically make the Modifier appear in the Graph Menu and it's details panel will be loaded upon selecting an instance of it.

CModifierManager::Init()

This function creates an instance of all existing and known modifiers. To register your custom modifier, include the header file of your new modifier and add a new line in this function:

AddModifierToClassMap(new yourClassName());

Implementing a New Node Type

For each type of animation graph node a factory class needs to be registered with the animation graph manager. This can be done in the CAnimationGraphManager::RegisterFactories function.

The system needs to store one initialized instance of all used property combinations for any node type. To be able to distinguish unique property combinations and sort already created instances it is crucial to implement the virtual bool IsLessThan( IAnimationStateNodeFactory* pFactory ) method of the node factory class. This method should return the comparison result of all node property values stored in data members either directly or as derived values. Using preprocessor macros can simplify the implementation, like in this example taken from CAGFacial C++ class:

virtual bool IsLessThan( IAnimationStateNodeFactory * pFactory )
{
  AG_LT_BEGIN_FACTORY(CAGFacial);
    AG_LT_ELEM(m_expressionNameIdle);
    AG_LT_ELEM(m_expressionNameAlerted);
    AG_LT_ELEM(m_expressionNameCombat);
  AG_LT_END();
}

New nodes are created by calling factory's method Create() . A new instance of the corresponding node class needs to be created and returned to the caller. In case when the node is implemented in a way that it doesn't store any dynamic data (so it's a node that can be shared) then the factory instance can also be the node instance at same time. In such case this function can simply be implemented as:

IAnimationStateNode * CAGSomeFactoryAndNodeClass::Create()
{
   return this;
}

Some of the nodes, depending on their type or sometimes depending of their property values, need to be updated each frame (only while the state to which the node belong is the current state). If that's the case then the node after its initialization is done has to be marked with the eASNF_Update flag. Then later each frame while the node is active its virtual void Update( SAnimationStateData& data ) function will be called.

Nodes provide their functionality by overriding these virtual methods:

virtual void EnterState( SAnimationStateData& data, bool dueToRollback );
virtual EHasEnteredState HasEnteredState( SAnimationStateData& data );
virtual void EnteredState( SAnimationStateData& data ) {}
virtual bool CanLeaveState( SAnimationStateData& data );
virtual void LeaveState( SAnimationStateData& data );
virtual void LeftState( SAnimationStateData& data, bool wasEntered ) {}

If a transition from the current state to another is needed then the nodes of the current state will be queried by calling CanLeaveState function to check is it ok to begin the transition to the next state. Each node has right to veto the transition. Once all nodes return true the transition will begin.

At the beginning of a transition to another state the LeaveState method is first called for each node of the current state, and then the EnterState method is called for each node of the next state. Some nodes would clear or initialize their data in these methods. Since these methods are called before the actual start of the animation, most of the nodes wouldn't do anything here except the AnimationLayerX nodes which are supposed to push their animation in the animation queue.

Next, the system will constantly query the nodes of the next state by calling HasEnteredState function on each node. The function should return:

  • eHES_Instant - if this node doesn't affect the entering.
  • eHES_Waiting - if the node affects the entering and the entering is still in progress.
  • eHES_Entered - if the node wants to notify the system that entering has finished.

The AnimationLayerX nodes for instance are monitoring the animation queue and return eHES_Waiting until their animation start playing in which case would return eHES_Entered. This is used for synchronizing other nodes with the animation. Once a node has returned eHES_Entered the transition between animation graph states is over (note that at this moment the transition between animations has just begun and is not over yet). The nodes of the current state get notified with a call to their LeftState method and the nodes of the next state with EnteredState method.

Most of the nodes return eHES_Instant in HasEnteredState since they provide their functionality only while the associated animation is playing. If all of the nodes of the next state return eHES_Instant it means the graph is entering a null state. In this case the transition is done and the next state becomes the current state instantly. Since it's a null state it doesn't play an animation so LeftState and EnteredState methods are not called in this case. However LeftState for nodes of the previous state will be called later when some other non-null state is entered.