Skip to main content

Animation State Machine

While baking animations inside .model files might be enough for simple, continuous animations*, when it comes to actually having complex movement occur (such as a randomized escort path, or a helicopter that simultaneously hovers and shoots missiles, like in Hotline Miami day 2), you will want to make use of the Animation State Machine (will be referred to as ASM).

For any unit to make use of the ASM, it will require four additional files with the following extensions:

  • animation_state_machine
  • animation_states
  • animation_def
  • animation_subset

* There are some exceptions to this, as in, for instance, car door opening/closing animations, which are actually a single animation where certain ranges of keyframes correspond to the animation. The keyframes that play can be specified in animation_group sequences; see Sequence Manager XML.

But what exactly is a "state"?

An animation state is a way to control what action an unit is doing, what happens during it, and what it does when it finishes. Take, for instance, a fps/melee_hit state; the animation is determined through weights, sounds are played accordingly, and, when it finishes, it exits to the fps/idle state, that is, the "weapon idle" FPS animation. Compare this to a baked animation, where you can only start it, pause it, and be forced to time your sound design around the finished animation instead of using animation markers.

Initial .unit setup

The animation_state_machine and animation_def files need to be directly referenced by the .unit file:

<unit type="str" slot="1">
  ... network syncs ...
  <anim_state_machine name="anims/units/helicopter/helicopter" />

  <dependencies>
    <depends_on animation_def="anims/units/helicopter/helicopter"/>
    <depends_on animation_state_machine="anims/units/helicopter/helicopter"/>
  </dependencies>
  
  ... the rest of the unit definition ...
</unit>

Initial .object setup

<dynamic_object>
  ... material_config and sequence_manager ...
  <animation_def name="path/to/animation/definition" />
  ... the rest of the object definition ...
</dynamic_object>

Additionally, make sure that animated collisions follow the animated template, otherwise they will not move.

Files

animation_state_machine

This file needs to be referenced inside the unit's .unit file. Let's break down a sample animation_state_machine file. Note that the purpose of some attributes is currently unknown.

<state_machine name="a_cool_name_for_the_asm" timebase="frames">
  <segment name="base"/>
  <segment name="secondary_actions"/>
  
  <global name="a_variable_name" value="0"/>
  
  <default state="std/empty"/>
	
  <states file="path/to/the/states/file"/>
</state_machine>
  • Segments are used to allow for multiple states to be active at once; say, a medic can be crouching while playing the upper body "heal" animation. Every state will need to be assigned to a segment, and only one state can play at once per segment. HOWEVER, you can play multiple animations together from separate states in a single segment by using blend sets; see the section on animation_def.
  • Globals allow for weights: a single state can have multiple animations (if it is of the "mix" type), however, it will only play the animation with the highest weight value. These can be controlled through scripting (see the corresponding section).
  • A default empty state, with no animations, is set when the unit is loaded. This is standard on any animation_state_machine file unless you want the unit to start doing stuff as soon as it loads. If a invalid state is specified, no other states will work.
  • An animation_states file is defined. There exists the possibility of defining the states directly in this file; however, it is recommended to still create individual files.

animation_states

This file needs to be referenced inside the animation_state_machine file. It contains all of the playable states for the ASM. A valid animation state file resembles the following:

<xml>
  <state name="std" type="template_state" segment="a_segment_for_the_state" mute="an_optional_segment_to_mute">
    <to name="a_generic_name_for_this_animation" redirect="name/of/the/corresponding/state"/>
  </state>

  <state name="std/empty" type="emptyloop" segment="a_segment_for_the_state">
  </state>

  ... ALL OTHER STATES ...
</xml>
  • A state of the type template_state: here, you define the names of the animation redirects to call from the sequence manager, that is, any name of your choice, and a reference to the state that will be played, for each state. You can't trigger states from the sequence manager if you use their name/formatted/like/this directly.
  • The default empty state we specified in the animation_state_machine file.

State types

  • template_state: a state that serves as a property template for other states under the hierarchy. For it to apply to children states, it needs to be defined before them.
  • emptyloop: a state that does not play an animation until it is overriden by another state.
    • A special blending effect occurs when entering a state of this type when using multiple segments; see the from_group action description below.
  • once: a state that plays a single, pre-determined animation, once. If given multiple animations, it will always pick the same one, regardless of weights. For the correct behaviour in that case, use mix.
  • loop: a state that plays a single, pre-determined animation, repeatedly, until another state is triggered.
  • mix: a state that plays the animation with the most weight from a list of possible animations, once.
  • mixloop: a state that plays the animation with the most weight from a list of possible animations, repeatedly, until another state is triggered.
  • timeblend: a state that "plays" the position of the bones defined in its animation at a time t, which is an implicit parameter of the state, settable from Lua. The bones won't move from that position until another state is played or this one is re-entered with another t value. When using this state, animations will need to define a time= parameter alongside them for reference purposes. This state also allows weights. E.g. <anim name="falcogini_wheel_turn_left" time="1" weight="falcogini"/>:
    • When t = 0, the state will place the bones at their starting position in the animation.
    • When t = 1 (the value specified in the XML), the state will place the bones at their ending position in the animation.
    • When 0 < t < 1, the state will place the bone at that position in the animation given the reference time parameter.
  • snapshot: unknown. Used once in core/units/locator/locator.animation_state_machine.

The following states are referenced in engine decompilations, but never used in the game:

  • sequence: untested, but should play its animations in a sequence as specified.
  • empty: unknown.
  • offset_once: unknown.
  • offset_loop: unknown.

State actions

  • to: define a redirect to a state. E.g. <to name="reload_exit" redirect="fps/reload/r870_shotgun/exit"/>
  • anim: play an animation with the specified name, which should have been defined in the animation_subset files. If the state is of the type mix or mixloop, you also need to include a weight= parameter. E.g. <anim name="throw_concussion" weight="projectile_concussion"/>
  • exit: when the animation that the state played finishes, enter the named state. This will never trigger on loop and mixloop states. E.g. <exit name="fps/idle"/>. If exiting a state in a particular segment to a state in another segment, the second state will break.
  • block: Block the specified state from triggering during this state. E.g. <block name="upper_body/recoil/crouch/auto_exit"/>
  • unblock
  • block_group
  • unblock_group
  • from_group: blend the specified "amount" between the animation that the specified state was playing when exited and the animation that will now be played by this state. E.g. <from_group name="fps/idle" blend="1"/>.
    • from_group only works for same-segment state switching; to blend finished animations on a segment with playing animations on another segment, exit on the finished segment to an emptyloop state on that same segment which uses from_group. See the video resource on blending for more info.
  • keys: a table of key elements. More information below.
  • param: similar to animation_state_machine globals, except these are defined and stored on a per-state basis. They can also be used to set weights, just like globals, and controlled through Lua. E.g. <param name="generic_stance" value="0"/>
  • default: set default properties for states under the hierarchy. E.g. <default blend="1.5" blend_out="1.5"/>.
    • blend: amount of blend-in; see the video resource on blending for more info.
    • blend_out: unknown... pretty sure that it doesn't do what it's supposed to. If you want to actually blend out, use from_group.
  • modifier: Enable the specified modifier. E.g. <modifier name="ik" blend="1" blend_out="1"/>.
  • remove_modifier

Key types

General format:

<key at="key_type" callback="the_method_of_that_extension_class_to_call" class_name="the_name_of_the_extension_class" param1="first_param" param2="second_param" param3="moar_params" param4="you_get_the_point"/>

The former will result in the callback function being called as callback_function(param1, param2, param3, param4, ...) when the condition specified in at is met. Parameters are optional. Now for the key types:

  • enter: when the state is entered.
  • exit: when the state is exited.
  • loop: when the state loops. This will only trigger on loop and mixloop.
  • trigger: when the animation reaches a certain marker. E.g. <key at="trigger" trigger="wp_g3_grab_end" callback="play_sound" class_name="base" param1="wp_g3_grab_end"/>
  • full_blend: when a blend-in finishes. Does not work on emptyloop states.
  • Any number: when the animation is at that keyframe.

Naming states

State names should aim to follow the next structure, each "group" divided from each other by a forward slash (e.g. std/helicopter_attack_1):

  • An identifier for the collection of states that follow the template state of the same identifier. E.g. std, fps, etc. The initial template state should include all the redirect declarations.
  • For any states that require other template states inside the domain of the initial template state, repeat the former naming process.

Some examples:

  • std/animation_1 will play while applying possible key actions in the std template state.
  • std/death/animation_1 will play while applying possible key actions in the std/death template state. The latter will then inherently apply any key actions in the std template state.

Example

A base-game example of a state, with comments indicating what its doing. This particular one is the state for reloading the Reinfeld 880:

<state name="fps/reload/r870_shotgun/loop" type="loop" segment="base" speed="1.0"> <!-- Note the possibility of changing the speed of animations here -->
  <!-- Set redirects for stopping the reload shell insert loop. These would be called from Lua in this case. -->
  <to name="reload_exit" redirect="fps/reload/r870_shotgun/exit"/>
  <to name="reload_not_empty_exit" redirect="fps/reload/r870_shotgun/not_empty_exit"/>

  <!-- Play the animation -->
  <anim name="r870_shotgun_reload_loop"/>

  <!-- Do stuff at certain markers/events -->
  <keys>
      <key at="enter" callback="anim_clbk_spawn_shotgun_shell" class_name="base"/>
      <key at="loop" callback="anim_clbk_spawn_shotgun_shell" class_name="base"/>
      <key at="enter" callback="enter_shotgun_reload_loop" class_name="base" param1="fps/reload/r870_shotgun/loop"/>
      <key at="trigger" trigger="wp_reinbeck_reload_cock" callback="play_sound" class_name="base" param1="wp_reinbeck_reload_cock"/>
      <key at="trigger" trigger="wp_reinbeck_shell_insert" callback="play_sound" class_name="base" param1="wp_reinbeck_shell_insert"/>
      <key at="trigger" trigger="wp_foley_generic_lever_pull" callback="play_sound" class_name="base" param1="wp_foley_generic_lever_pull"/>
      <key at="trigger" trigger="wp_foley_generic_back_in_hand" callback="play_sound" class_name="base" param1="wp_foley_generic_back_in_hand"/>
      <key at="trigger" trigger="anim_act_01" callback="anim_clbk_unspawn_shotgun_shell" class_name="base"/>
      <key at="exit" callback="anim_clbk_unspawn_shotgun_shell" class_name="base"/>
  </keys>
</state>

animation_def

This file needs to be referenced inside the unit's .object file.

The following is a simplified version of anims/units/enemies/cop/cop_def.animation_def, which I reckon showcases this file well.

<xml>
  <!-- Animatable sets -->
  <animatable_set name="cop">
      <!-- Root -->
      <bone name="root_point" root="true" alignment="true" alias="all root"/>
      
      <!-- Spine -->
      <bone name="Hips" alias="all legs"/>
      <bone name="Spine" alias="all upper upper_l upper_r lod"/>
      <bone name="Spine1" alias="all upper upper_l upper_r lod"/>
      <bone name="Spine2" alias="all upper upper_l upper_r lod"/>
      
      <!-- Left leg -->
      <bone name="LeftUpLeg" alias="all legs lod"/>
      <bone name="LeftLeg" alias="all legs lod"/>
      <bone name="LeftFoot" alias="all legs lod"/>
      
      <!-- Right leg -->
      <bone name="RightUpLeg" alias="all legs lod"/>
      <bone name="RightLeg" alias="all legs lod"/>
      <bone name="RightFoot" alias="all legs lod"/>
      
  </animatable_set>
  
  <!-- Blend sets -->
  <blend_set name="all" animatable_set="cop">
      <blend alias="all" weight="1.0"/>
  </blend_set>
  
  <blend_set name="upper_body" animatable_set="cop">
      <blend alias="all" weight="1"/>
      <blend name="Spine2" weight="0.85"/>
      <blend name="Spine1" weight="0.4"/>
      <blend name="Spine" weight="0.25"/>
      <blend alias="legs" weight="0"/>
      <blend alias="root" weight="0"/>
  </blend_set>
  
  <!-- IK Modifiers -->
  <modifier name="look_upper_body" type="ik" animatable_set="cop" iterations="1" blend_in="0.5" blend_out="0.3">
      ... modifier params ...
  </modifier>

  <!-- Animation sets -->
  <animation_set name="cop" animatable_set="cop">
      <subset file="path/to/the/subset/file"/>
      <subset file="any/other/subset/file"/>
      <subset file="you/can/have/as/many/as/you/want"/>
  </animation_set>
</xml>
  • Animatable sets are where you directly reference the bones in your model. You can also give them aliases, separated by spaces, in the alias key. Take root_point, which not only is declared as the root bone, but also assigned both all and root aliases.

For every animatable set you will need:

  • Blend sets, which define how two animations playing at once blend together. This does not refer to switching between animations, but rather, how much weight the animations have over the bones; every animation needs to be specified a blend set to use. See the video resource on blending for more info.
    • When you don't want to blend (that is, play your animation with full bone influence), you can use an "all" set which applies a weight of 1 to all the bones. You use these in your animation subset.
  • Animation sets, which need to define one or more animation_subset files.

Make sure that when you name your animatable set and your animation set, they don't match a segment's name! Otherwise, you'll crash when changing states.

Modifiers

For procedural animation, such as those seen with VR arm movements, or peer upper body rotation when they move their camera.

Types: group, stand, ik, shoulder, displacement, offset, link, mirror.

The IK type requires either a rotation or a position node. These can be of type script, lock or animation (this last one might be of interest for animated IK, however, I haven't managed to make it work);

  • When using script, they are controllable from Lua.
  • When using animation, the rotation/position node also needs to include a target attribute, otherwise the game will crash when enabling the modifier. rotation can also use an axis (x, y or z) attribute when set to this type.
<!-- The modifier in charge of keeping up with a peer's camera direction and updating their ; it's controlled through HuskPlayerMovement -->
<modifier name="action_upper_body" type="ik" animatable_set="cop" iterations="1" blend_in="0.5" blend_out="0.3">
    <rotation type="script"/>
    <target name="aim"/> <!-- This bone needs to exist in the model, otherwise crash. -->
    <!--          ^^ Also of note is that this bone needs to be parented to whatever bones it will move as a result of its transformations -->

    <!-- IK bone chain -->
    <bone name="Spine2" rotate="1"/>
    <bone name="Spine1" rotate="0.35"/>
    <bone name="Spine" rotate="0.35"/>
</modifier>

The mirror type requires a config attribute which can only be biped. For each of its entries, it needs a first and a second attribute, with an optional transform attribute which can either be inverse or root.

animation_subset

This file needs to be referenced inside the animation_def file. It's arguably the easiest to get a grip on.

<xml>
  <anim	name="referencable_anim_name"		file="path/to/the/animation/file"	blend_set="all"/>
</xml>
  • For each animation file, you define an animation name and a blend set to use. This animation name is what animation states reference to play the animation file.

The folks that worked with these files actually left us some extra info on making animations:

If the animation finishes inside the viewable area and does not finish outside of the viewable area, you must animate the root point. Or else the animation will not sync at drop in. For eample the police car arrives and stop in the viewable area, here the root point shoul be animated. The heicopter arrives and leaves the the viewable area, here all_no_root is allowed.

Make sure to follow that guideline in order to avoid sync issues!

Controlling animations through Sequence Managers

Use animation_redirect. Example:

<sequence name=" 'heli_suburbia_hover' " triggable="true">
    <animation_redirect name=" 'suburbia_hover' "/>
</sequence>

Remember that this is the name you specify in to state actions that redirects you to a state.

Video resources

Animation Blending

Common crash-causers

When working with these files, it is especially frustrating to try to track down the source of access violations. Below is a list of issues to check:

  • Paths are misspelled on any of the files
  • An XML structure was not properly closed
  • animation_def or animation_state_machine are missing from the unit's dependencies list
  • animation_def was not defined in the object file
  • A bone specified in animation_def is not actually a bone, or is the root bone and does not exist
  • An animation was specified in an emptyloop state
  • An animation specified in a state does not exist

If none of these solved your issue, read this page carefully again, from top to bottom.

Appendix A: Lua methods

Animation State Machine

The ASM of an unit can be referenced through the unit's anim_state_machine() method.

  • ASM:set_global(name, value): sets the global to the specified number value.
  • ASM:set_global_soft(name, value)
  • ASM:get_global(name): get the value of the global.
  • ASM:set_parameter(state, parameter, value): Set the parameter inside the specified state to a number value.
  • ASM:set_parameter_soft(state, parameter, value)
  • ASM:set_speed(animation_idstring, speed): sets the speed of the animation.
  • ASM:set_speed_soft(animation_idstring, speed)
  • ASM:get_speed(state): get the speed of the state.
  • ASM:is_playing(animation_idstring): return whether the animation is playing or not.
  • ASM:segment_real_time(segment_idstring): return the current time of the segment.
  • ASM:segment_relative_time(segment_idstring)
  • ASM:segment_muted(segment_idstring)
  • ASM:stop_segment(segment_idstring)
  • ASM:segment_state(segment_idstring): get the playing state on the specified segment.
  • ASM:segment_state_object(segment_idstring)
  • ASM:config():states(): return a list of states in the ASM.
    • State:name():s(): return the state name.
  • ASM:state_in_group(state)
  • ASM:set_enabled(enabled)
  • ASM:set_animation_time_all_segments(time)
  • ASM:play_raw(animation)
  • ASM:play(animation)
  • ASM:root_blending()
  • ASM:set_root_blending(bool)
  • ASM:instant_blending()
  • ASM:set_instant_blending(bool)
  • ASM:set_callback_object(lua_object)
  • ASM:index_to_state_name(index)
  • ASM:state_name_to_index(state)
  • ASM:enabled_modifiers()
  • ASM:force_modifier(modifier)
  • ASM:allow_modifier(modifier)
  • ASM:forbid_modifier(modifier)
  • ASM:get_modifier(modifier)
  • ASM:set_modifier_blend(modifier, blend)
  • ASM:reset()

To play a ASM state, use the unit's play_redirect(redirect_name, time_offset) method. This returns an Idstring identifier for the animation. Alternatively, you can also use the unit's play_state(state_idstring, time_offset).

Modifiers

IK

IK modifiers can be accessed through ASM:get_modifier(idstring_modifier_name). If the rotation/position type is script, they are controllable through:

Directly set the target bone's position/rotation:

  • IKModifierInstance:set_target_position(vector3)
  • IKModifierInstance:set_target_rotation(rotation)

Set the rotation of the target bone by making one of its axis have the direction of the provided normalized vector, taking the bone's position as the point of reference:

  • IKModifierInstance:set_target_x(vector3)
  • IKModifierInstance:set_target_y(vector3)
  • IKModifierInstance:set_target_z(vector3)

Set the rotation of the target bone by making one of its axis point towards the provided point in world space:

  • IKModifierInstance:set_target_x_look_at(vector3)
  • IKModifierInstance:set_target_y_look_at(vector3)
  • IKModifierInstance:set_target_z_look_at(vector3)

Unknown:

  • IKModifierInstance:set_object(?)
  • IKModifierInstance:blend()