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.
Files
animation_state_machine
This file needs to be referenced inside the unit's .unit
file.
<unit type="str" slot="1">
... network syncs ...
<anim_state_machine name="anims/units/helicopter/helicopter" />
... the rest of the unit definition ...
</unit>
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"/>
<global name="a_variable_name" value="0"/>
<default state="std/empty"/>
<states file="path/to/the/states/file"/>
</state_machine>
- Segments are probably used to allow for multiple states to play at once; say, a medic can be crouching while playing the upper body "heal" animation. Since only one state can be assigned playing to each segment, you define a "base" segment for the entire medic and an "upper_body" segment for his arms.
- 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. - An
animation_states
file is defined.
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">
<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 theirname/formatted/like/this
directly. - The default empty state we specified in the
animation_state_machine
file.
State types
template_state
: already documented above.emptyloop
: a state that does not play an animation until it is overriden by another state.once
: a state that plays a single, pre-determined animation, once.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
: 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 theanimation_subset
files. If the state is of the typemix
ormixloop
, you also need to include aweight=
parameter. E.g.<anim name="
r870_shotgun_reload_loop"throw_concussion" weight="projectile_concussion"/>exit
: when the animation that the state played finishes, enter the named state. This will never trigger onloop
andmixloop
states. E.g.<exit name="fps/idle"/>
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"/>
keys
:Aa table ofkey
elements. More information below.param
: similar toanimation_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
: unknown. E.g.<default blend="1.5"/>
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"/>
That will result in the callback function being called as callback_function(param1, param2, param3, param4, ...)
. 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 onloop
andmixloop
.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"/>
Naming states
State names should aim to follow the next structure, each block divided from each other by a forward slash (e.g. std/helicopter_attack_1
). This is to keep consistency with the vanilla game's files*:
- 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 thestd
template state.std/death/animation_1
will play while applying possible key actions in thestd/death
template state. The latter will then inherently apply any key actions in thestd
template state.
* The game might even refuse to load states that don't follow this naming scheme and crash, however, this has not been tested.
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>
State names should aim to follow the next structure, each block divided from each other by a forward slash (e.g. std/helicopter_attack_1). This is to keep consistency with the vanilla game's files*:
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_1will play while applying possible key actions in thestdtemplate state.std/death/animation_1will play while applying possible key actions in thestd/deathtemplate state. The latter will then inherently apply any key actions in thestdtemplate state.
* The game might even refuse to load states that don't follow this naming scheme and crash, however, this has not been tested.
animation_def
This file needs to be referenced inside the unit's .object
file.
<dynamic_object>
... material_config and sequence_manager ...
<animation_def name="path/to/animation/definition" />
... the rest of the object definition ...
</dynamic_object>
Here's a breakdown. 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. Takeroot_point
, which not only is declared as the root bone, but also assigned bothall
androot
aliases.
For every animatable set you will need:
- Blend sets, which probably define how much, well, blending, occurs between two animations. When you don't want to blend, you can use "all", which is used across all ASMs.
- Animation sets, which define one or more
animation_subset
files.
You can also use modifiers, which are currently unknown.
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 Lua
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: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_speed(animation_idstring, speed)
: sets the speed of the animation.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:stop_segment(segment_idstring)
: stop all the animations in the segment.ASM:segment_state(segment_idstring)
: get the playing state on the specified segment.ASM:config():states()
: return a list of states in the ASM.State:name():s()
: return the state name.
ASM:set_enabled(enabled)
ASM:set_animation_time_all_segments(time)
ASM:play_raw(animation)
ASM:set_root_blending(bool)
ASM:set_callback_object(lua_object)
ASM:index_to_state_name(index)
ASM:force_modifier(modifier)
ASM:allow_modifier(modifier)
ASM:forbid_modifier(modifier)
ASM:get_modfiier(modifier)
ASM:set_modifier_blend(modifier, blend)
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)
.
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.