Skip to main content

Animation State Machine

Overview

Adding a State Machine in the Anim Tree Editor and exposing the interface enables the state-switching at the logic layer, thereby achieving data-driven state management.

The State Machine node contains multiple states and transitions between states. Each state contains an Anim Tree, and each transition contains a set of transition rules. Driven by external logic, the State Machine switches between different states based on the transition rules. The State Machine blends the output animation of the Activated state and passes it as its own output pose to the Anim Tree it is in for further processing.

State Machine Nodes

Creating State Machine Nodes

Right-click in the blank area of the Anim Tree to open the Node Selection panel. Expand the State Machine category and select Add New State Machine.

image-20230704110151612

After creation:

image-20230704110222316

Editing State Machine Nodes

Select the State Machine node and click its title area to modify the name.

ModifyName

Double-click the node to enter the state graph of the State Machine. Then you can create states and organize structures here.

StateGraph

State Machine Node Properties

Select the State Machine node in the Anim Tree to set its properties in the Property Window.

image-20230705103614191

PropertyDescription
State Machine NameEdit the name of the State Machine.
Max Amount Of State Transition Per FrameThe maximum number of state transitions that can be executed in a single frame.
Whether Reset When Re-EntryWhether to reset when re-entering. If not, it will be updated from the state when leaving.
Whether Skip First Transition When InitializingIf checked, it will directly enter the state that meets the conditions with full weight; otherwise, it will transit from the entry state to the state that meets the conditions.

State Nodes

Creating State Nodes

Right-click in the blank space of the state graph to open the Node Selection panel, and select Add State.

image-20230704111650722

After creation:

image-20230704161325094

Editing State Nodes

Select the State node and click its title area to modify the name.

image-20230704143802964

Double-click the Idle State node to enter its Anim Tree editing interface.

AnimTreeOfState

Configure the Anim Tree for this state as needed.

image-20230704162746438

State Node Properties

Select the State node in the state graph to set its properties in the Property Window.

image-20230704112226503

PropertyDescription
State NameEdit the name of the State.
Start State EventThe callback function name of the Start State Event.
End State EventThe callback function name of the End State Event.
Fully Blend Into State EventThe callback function name of the Fully Blend Into State Event.
Reset On Every EntryWhether to reset the child nodes of the associated Anim Tree every time entering the state.

Creating Transitions

By holding down the left mouse button at the outer edge of a State node and dragging it outwards, an arrow for transition will be pulled out. After releasing the mouse at the appropriate position, a Node Selection panel will pop up, allowing the selection of the desired target node to be created. At this point, a transition and a target state are created simultaneously.

CreateTransition

You can create another transition from the target state back to the original state.

CreateTransition02

There can be multiple transitions from one state to another state.

CreateTransition03

Transition Properties

Click the transition between two states to set its properties in the Property Window.

image-20230705102400651

PropertyDescription
Transition ModeThe transition mode for the two states animation playback connected.
  • Cross Fade: The original animation weight gradually decreases while the new animation weight gradually increases.
  • Inertia Transition: The inertia of the original animation is preserved at the beginning of the transition, and gradually disappears during the process.
DurationThe duration applied to the fade-in and fade-out effects.
Blend ModeThe blend mode applied to the fade-in and fade-out effects.
PriorityThe transition priority in the Anim Editor.
Auto Transit When Action EndsWhether to automatically transit to the target state after the action in the previous state ends.
Callback StringConfigure the callback function, and decide whether to transit according to the return value.
Use ConditionsAfter checking, parameters (except string parameters and vector parameters) can be selected for conditional expressions, and the final results can be used to control State Transitions.

Deleting Transitions

Right-click the transition between two states and select Delete Transition to delete it.

image-20230705102600322

State Machine Creation Steps

In actual operation, the overall design can be carried out in the state graph according to the action requirements of the characters, and then implemented step by step for each state and each transition.

image-20230704134901488

Double-click the Ground state node to enter the associated Anim Tree where various Anim Tree nodes can be added to achieve the expected ground action requirements. For example, a Blend Space Player node is used here.

image-20230704172131592

After completing the setup for this state, you can use the Navigation Bar to jump out of the editing of this hierarchy and return to the previous state graph.

image-20230704172442243

Next, you can edit other states and transitions. For example, select the transition from the JumpStart state to the Jump state and edit its properties in the Property Window to configure the transition rules.

image-20230704173532979

Conduit Nodes

The Conduit can be seen as a special type of state. Unlike the above States, it does not contain an Anim Tree. In addition to switching states through transition control, the State Machine also provides a node called Conduit, which is used for one-to-many, many-to-one, or many-to-many State Transition requirements. The Conduit is similar to a State without an Anim Tree. It has an internal rule of "whether to allow entry into transition". By configuring this rule, it can determine whether further transitions can be made through the Conduit when a state attempts to transition through it.

Creating Conduit Nodes

Right-click in the blank space of the state graph to open the Node Selection panel and select Add Conduit.

image-20230704142535454

After creation:

image-20230704142813411

Select the Conduit node and click its title area to modify the name.

image-20230704143731930

Conduit Node Properties

Select the Conduit node in the state graph to set its properties in the Property Window.

image-20230704143957445

PropertyDescription
Conduit NameEdit the name of the Conduit.
Can EntryWhether to enter the node.

Conduit Node Examples

The Killed node in the figure below is the Conduit node. When the character is in the motion state of Walk or Run, if the character is attacked and the health point is lower than the death threshold, the logic will control the state to transit to the Killed Conduit node, and the state finally switches to is determined by the specific transition rules.

image-20230704155821525

Transition Rules

Three types of transition rules for State Transition:

  • Automatically switching to the next state after the state animation is played

  • Using callback functions to set transition rules for State Transition

  • Setting Use Conditions to transit.

    Compared to using callback functions, the latter one Use Conditions is recommended.

Automatically Transiting After the Animation is Played

Design a state graph as shown below to complete the State Transition from Jump to JumpEnd.

image-20230705114559820

The Anim Tree associated with the Jump state:

image-20230705130517245

The Anim Tree associated with the JumpEnd state:

image-20230705130613337

Select the transition from the Jump state to the JumpEnd state, and check Auto Transit When Action Ends in its Property Window.

image-20230705131149639

Set the transition from the JumpEnd state to the Jump state as described above.

image-20230705131935987

Click the Apply button to view the effect. After the Jump state animation is played, it automatically transits to the target JumpEnd state animation, after which it automatically transits back to the Jump state animation, and so on.

AutoTransitWhenActionEnds

Callback Strings

State Transition can be done by setting Callback Strings. Design a state graph as shown below and then set the transitions between states respectively. For example, select the transition from the Ground state to the JumpStart state, and enter jump_start in the Callback String of the Property Window.

image-20230705173506200

Callbacks of State Transitions

on_transition_event(callee, role, action_index, machine_index, prev_state, next_state, callback_name)

Parameter 1: AnimCallee entity

Parameter 2: Target character entity

Parameter 3: Action index

Parameter 4: State Machine index

Parameter 5: Pre-transition state index

Parameter 6: Post-transition state index

Parameter 7: Callback event name string

Use Conditions

Design a state graph as shown below to complete the transition between Idle and Run states.

image-20230705133251550

The Anim Tree associated with the Idle state:

image-20230705164957799

The Anim Tree associated with the Run state:

image-20230705165024234

Click the image-20230705133459967 button in Animation Parameter to add a parameter. Here select the Float parameter.

image-20230705140132860

Modify the Parameter Name to Speed after adding.

image-20230705144727899

Select the transition from the Idle state to the Run state to show its Property Window. Check Use Conditions, select the parameter created earlier in the first drop-down box, and set specific conditions like this: when the Speed parameter is greater than 0.1, it will transit from the Idle state to the Run state.

image-20230705144939649

Select the transition from the Run state to the Idle state. Check Use Conditions, and select the Speed parameter for the conditional expression. When its value is less than 0.1, it will transit from the Run state to the Idle state.

image-20230705145336074

Select Preview Object from the drop-down box below the Menu Bar, and click the Apply button to view the effect in the Anim Tree Editor.

image-20230705164402401

Modify the Speed parameter to switch the state.

UseConditions

Please refer to Character Quick Start for specific applications of using State Machines and setting transition rules by using conditions.

Example

The following is an example of applying the State Machine to control the character's actions. The transition rule uses Auto Transit When Action Ends and Callback String.

Creating Actors

Refer to Assembling Actors to create an Actor which contains a series of animation assets.

image-20230707101655913

Creating Animation Sequences

In the Component Editor, double-click any Skeleton asset to open the Animation Editor.

image-20230707101717079

Select the Skeleton asset to be converted in the Asset Browser of the Animation Editor. Right-click to open the Shortcut Menu, and select Convert Skeleton To AnimSeq. Enter the name of the Animation Sequence in the pop-up window, and click the OK button to complete the creation.

image-20230707101855763

Convert all walk, run, and stand related Bone assets into Animation Sequence assets as described above which will be used as sampling points in the Blend Space later.

image-20230707103626768

Double-click a walk or run Animation Sequence in the Asset Browser to add Sync Marker and Sync Group to it which will synchronize the footsteps when blending different animations. Similarly, add Sync Markers and Sync Groups to other walk and run Animation Sequences. Please refer to the Animation Syncing documentation for details on Sync Markers and Sync Groups.

image-20230707103733175

Click File (Menu Bar) -> Save All to save after editing.

image-20230707105705963

Creating Blend Spaces

Click File (Menu Bar) -> Create in the Animation Editor. Select BlendSpace in the drop-down box of the pop-up window, and set the name and path here. Click Yes to complete the creation.

image-20230707131133186

Please refer to the Blend Spaces documentation to create a Blend Space asset as shown below.

image-20230707132419873

Save it after editing.

Creating Anim Trees

Click File (Menu Bar) -> Create in the Animation Editor. Select AnimTree in the drop-down box of the pop-up window, and set the name and path here. Click Yes to complete the creation.

image-20230707132954488

Right-click the blank space to open the Node Selection panel in the opened Anim Tree Editor. Expand Blend Space List, and select the Blend Space Player "bs_run" node.

image-20230707133558282

Select the created Blend Space Player node. Check Loop in Property Window, and configure the callback string in the script. (Please refer to Blend Space Player for details on this node.)

image-20230710172933922

Right-click in the blank area of the Anim Tree to open the Node Selection panel. Expand Cached Poses Node List, and select Create Cache Pose.

image-20230707134420875

Select the created Cache Pose node. Click its title area and change its name to Ground.

image-20230707134602695

Link the Blend Space Player node to the Ground Cache Pose node.

image-20230707134724729

Creating State Machines

Create a State Machine node and change its name to Locomotion.

image-20230707150942328

Double-click the State Machine node to enter the editing interface of the state graph, and design one as shown below.

image-20230707151822879

The Anim Tree associated with the Ground state:

image-20230707152215038

The Anim Tree associated with the JumpStart state:

image-20230707152545761

The Anim Tree associated with the Jump state:

image-20230707152613697

The Anim Tree associated with the JumpFall state, where the Blend Weight of the Blend node is set to 0.5, allowing for a smoother pose transition from landing to the ground.

image-20230707153018801

Setting State Transitions

Select the transition from the Ground state to the JumpStart state, and set its Callback String to jump_start in the Property Window.

image-20230707162637603

Select the JumpStart state, and set the Fully Blend Into State Event callback function name to fully_blend_state in the Property Window.

image-20230707163253291

Select the transition from the JumpStart state to the Jump state. Check Auto Transit When Action Ends, and set Duration to 0.1 in Property Window.

image-20230711110616477

Select the Jump state, and set the callback function names of Start State Event, End State Event and Fully Blend Into State Event to start_state, end_state and fully_blend_state respectively in the Property Window.

image-20230707164426428

Select the transition from the Jump state to the JumpFall state. Set state transition Callback String to jump_end, and Duration to 0.1 in the Property Window.

image-20230711110446115

Set the two transitions from the JumpFall state to the Ground state respectively. In the Property Window, one checks to enable Auto Transit When Action Ends, and the other uses Callback String jump_start.

image-20230707165549109

image-20230707165624195

Return to the previous Anim Tree after editing the state graph.

image-20230707165933019

Link the State Machine node to the Final Animation Pose node.

image-20230707170049434

Click the Save button to complete the creation of the whole Anim Tree.

image-20230707170324003

Configuring Actors

Click Asset Details in the Actor Editor. Set the Default Action of the Actor to the Anim Tree test_tree, and then click Save.

image-20230707171052363

Creating Characters

Create a new default Level, and click Create Game Object (Toolbar) -> Character to create a character.

image-20230707171523445

Select the created character in the Hierarchy panel and CharacterMesh0(Inherited) in the Inspector panel respectively. Drag the created Actor into the Skeletal Mesh Config File slot.

image-20230707171733525

After dragging:

image-20230707171909125

Select CollisionCylinder(Inherited) and CharacterMesh0(Inherited) in the Inspector panel, then adjust the position of the character's capsule and mesh respectively, so that the feet of the character skin are on the ground and the capsule is above the ground in the premise that the capsule wraps the character as much as possible.

image-20230707172114224

Select the character object in the Hierarchy panel and CharacterMesh0(Inherited) in the Inspector panel respectively. Set the Default Action Override to the created Anim Tree test_tree.

image-20230711132218841

Select CollisionCylinder(Inherited), then click the Add Component button to add a Spring Arm Component.

image-20230707175131652

image-20230707175540240

Check the Use Pawn Control Rotation property of the LSpringArm Component.

image-20230707175736606

Add a Camera Component as a subset of the Spring Arm Component, and adjust the camera to the appropriate position.

image-20230710093033550

Select the CharMoveComp(Inherited) component, and set Ground Friction to 3, Max Walk Speed, Braking Deceleration Walking, and Max Custom Movement Speed to 9 respectively.

image-20230710094441496

Select LCharacter(Instance) in the Inspector panel, and set Auto Possess Player to Player 0.

image-20230710142130112

Binding Scripts

Add two Script Components. Select the character, then click the Add Component button in the Inspector panel and select Script Component.

image-20230707174119942

Configure the script file in the Property panel of the LScript Component. Drag the written Scripts from the Resource Preview window into the Script File slot of the character Script Component.

image-20230710140849898

image-20230710141240650

After dragging:

image-20230710141418583

image-20230710141450610

Scripts

The following is the reference scripts:

robot_character.lua

--script template

local IE_Pressed = 0 --Press the key
local IE_Released = 1 --Release the key
local IE_Repeat = 2
local IE_DoubleClick = 3
local IE_Axis = 4
local IE_MAX = 5

local CAMERA_DISTANCE_CHANGE_SPEED = 0.5
local CAMERA_DISTANCE_MIN = -1
local CAMERA_DISTANCE_MAX = 50

--Get actor
function character_get_actor(character)
if nx_is_valid(character) then
local mesh_component = character.MeshComponent

if nx_is_valid(mesh_component) then
local vis_base = mesh_component.VisBase

if nx_is_valid(vis_base) and nx_is_kind(vis_base, "Actor") then
return vis_base
end
end
end

return nx_null()
end

--Move forward callback
function on_moveforward(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

--Get controller Y-axis angle
local controller = owner.Controller

if nx_is_valid(controller) then
local yaw = controller.AngleY
--Get forward direction
local x, y, z = nx_function("ext_angle_get_forward_vector", 0.0, yaw, 0.0)

--Add movement

owner:AddMovementInput(x, y, z, axis_value)
end

return 1
end

--Move right callback
function on_moveright(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

--Get controller Y-axis angle
local controller = owner.Controller

if nx_is_valid(controller) then
local yaw = controller.AngleY
--Get right direction
local x, y, z = nx_function("ext_angle_get_right_vector", 0.0, yaw, 0.0)

--Add movement
owner:AddMovementInput(x, y, z, axis_value)
end
return 1
end

--Rotate left and right (around Y-axis) callback
function on_turn(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

owner:AddControllerYawInput(axis_value)

return 1
end

--Rotate up and down (around X-axis) callback
function on_lookup(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

owner:AddControllerPitchInput(-axis_value)

return 1
end

--Press jump key callback
function on_jump_pressed(owner)
if nx_find_custom(owner, "JumpAgainLock") and owner.JumpAgainLock then
return 1
end

--Character jumping
owner:Jump()

return 1
end


function input_comp_init_bind(component)
local owner = component.GameObjectOwner

if nx_is_valid(owner) then
local input_comp = owner.InputComponent

if nx_is_valid(input_comp) then
nx_bind_script(owner, nx_current())

input_comp:AddAxisBinding("MoveForward", true, false, true, "InputAxisEvent_MoveForward")
input_comp:AddAxisBinding("MoveRight", true, false, true, "InputAxisEvent_MoveRight")
input_comp:AddAxisBinding("Turn", true, false, true, "InputAxisEvent_Turn")
input_comp:AddAxisBinding("LookUp", true, false, true, "InputAxisEvent_LookUp")

input_comp:AddCombinationBinding("Jump", IE_Pressed, true, false, true, "InputActionEvent_Jump_Pressed")

nx_callback(owner, "InputAxisEvent_MoveForward", "on_moveforward")
nx_callback(owner, "InputAxisEvent_MoveRight", "on_moveright")
nx_callback(owner, "InputAxisEvent_Turn", "on_turn")
nx_callback(owner, "InputAxisEvent_LookUp", "on_lookup")

nx_callback(owner, "InputActionEvent_Jump_Pressed", "on_jump_pressed")
end
end
end

--Use this for initialization
function on_begin_play(component)
input_comp_init_bind(component)
nx_callback(component, "on_tick", "tick")
end

robot_character_animation.lua

--script template

--------------------------
--local utility function--
--------------------------


local radian = 57.2958
local foot_rel_height = 0.14


local function get_movement_component(pawn)
if nx_is_valid(pawn) then
return pawn:GetMovementComponentID()
end

return nx_null()
end

local function get_speed(pawn)
if nx_is_valid(pawn) then
local movement = get_movement_component(pawn)

if nx_is_valid(movement) then
local speed = nx_function("ext_vector_length", pawn.VelocityX, pawn.VelocityY, pawn.VelocityZ)
return speed
end
end

return 0.0
end

local function is_jumping(pawn)
if nx_is_valid(pawn) then
local movement = get_movement_component(pawn)

if nx_is_valid(movement) then
if movement:IsFalling() then
return true
end
end
end

return false
end

local function is_flying(pawn)
if nx_is_valid(pawn) then
local movement = get_movement_component(pawn)

if nx_is_valid(movement) then
if movement:IsFlying() then
return true
end
end
end

return false
end

function transition_callback_jump_start(pawn, role, action_index, machine_index, prev_state, next_state)
if is_jumping(pawn) then
return true
else
return false
end
end

function transition_callback_jump_end(pawn, role, action_index, machine_index, prev_state, next_state)
if not is_jumping(pawn) then
return true
else
return false
end
end

transition_event_callback =
{
["jump_start"] = transition_callback_jump_start,
["jump_end"] = transition_callback_jump_end,
}

function on_transition_event(mesh_comp, action_index, machine_index, prev_state, next_state, callback_name)

local pawn = mesh_comp.GameObjectOwner
local role = mesh_comp.VisBase

if not nx_is_valid(pawn) or not nx_is_valid(role) then
return false
end

local callback = transition_event_callback[callback_name]
if callback then
return callback(pawn, role, action_index, machine_index, prev_state, next_state)
end

return false
end

function state_machine_state_callback_EnterState(pawn, role, action_index, machine_index, state_index)
-- do something
nx_log("state machine state enter jump")
end

function state_machine_state_callback_LeaveState(pawn, role, action_index, machine_index, state_index)
-- do something
nx_log("state machine state leave jump")
end

function state_machine_state_callback_FullyBlendState(pawn, role, action_index, machine_index, state_index)
-- do something
nx_log("state machine state fully blend jump")
end

state_machine_state_event_callback =
{
["start_state"] = state_machine_state_callback_EnterState,
["end_state"] = state_machine_state_callback_LeaveState,
["fully_blend_state"] = state_machine_state_callback_FullyBlendState,
}

function on_state_machine_state_event(mesh_comp, action_index, machine_index, state_index, callback_name)

local pawn = mesh_comp.GameObjectOwner
local role = mesh_comp.VisBase

if not nx_is_valid(pawn) or not nx_is_valid(role) then
return
end

local callback = state_machine_state_event_callback[callback_name]
if callback then
callback(pawn, role, action_index, machine_index, state_index)
end
end

function animtree_callback_bs_x_direction(pawn, role)
return pawn.direction
end

function animtree_callback_bs_y_speed(pawn, role)
return get_speed(pawn)
end

local function get_anim_tree_index(role)
if role:GetBlendActionCount() > 0 then
local action_name = role:GetBlendActionName(0)
local action_type = role:GetActionType(action_name)
if action_type == AT_ANIMTREE then
return role:GetActionIndex(action_name)
end
end

return -1
end


animtree_event_callback =
{
["bs_x_direction"] = animtree_callback_bs_x_direction,
["bs_y_speed"] = animtree_callback_bs_y_speed,
}

function on_animtree_event(mesh_comp, action_index, callback_name)

local pawn = mesh_comp.GameObjectOwner
local role = mesh_comp.VisBase

if not nx_is_valid(pawn) or not nx_is_valid(role) then
return
end

local callback = animtree_event_callback[callback_name]
if callback then
return callback(pawn, role)
end

return false
end

function anim_callee_init(mesh_comp)
nx_callback(mesh_comp, "on_transition_event", "on_transition_event")
nx_callback(mesh_comp, "on_animtree_event", "on_animtree_event")
nx_callback(mesh_comp, "on_state_machine_state_event", "on_state_machine_state_event")
nx_callback(mesh_comp, "on_state_action_event_begin", "on_state_action_event_begin")
nx_callback(mesh_comp, "on_state_action_event_update", "on_state_action_event_update")
nx_callback(mesh_comp, "on_state_action_event_end", "on_state_action_event_end")
end

--use this for initialization
function on_begin_play(component)
local owner = component.GameObjectOwner

if nx_is_valid(owner) then
local mesh_comp = owner.MeshComponent

if nx_is_valid(mesh_comp) then

owner.meshcomp_init_relative_posy = owner.MeshComponent.RelativePositionY
mesh_comp.ActorCalleeType = "CComponentActorCalleeScript"
nx_bind_script(mesh_comp, nx_current(), "anim_callee_init")
end
end
nx_callback(component, "on_tick", "tick")
end

function tick(component, delta_time)
local owner = component.GameObjectOwner

local move_comp = get_movement_component(owner)
local speedx = move_comp.VelocityX
local speedz = move_comp.VelocityZ
local rate = speedx / speedz
local tan = math.atan(rate)
local velocity_angle = radian * tan

if speedx == 0 and speedz == 0 then
velocity_angle = 0
elseif speedx > 0 and speedz == 0 then
velocity_angle = 90
elseif speedx == 0 and speedz < 0 then
velocity_angle = 180
elseif speedx < 0 and speedz == 0 then
velocity_angle = 270
elseif speedx > 0 and speedz < 0 then
velocity_angle = 180 - math.abs(velocity_angle)
elseif speedx <= 0 and speedz < 0 then
velocity_angle = velocity_angle + 180
elseif speedx < 0 and speedz > 0 then
velocity_angle = 360 - math.abs(velocity_angle)
end

local cap_comp = owner.CapsuleComponent
local foward_angle = cap_comp.AngleY * radian
local relative_angle = velocity_angle - foward_angle

if relative_angle > 180 then
relative_angle = relative_angle - 360
elseif relative_angle < -180 then
relative_angle = relative_angle + 360
end
owner.direction = relative_angle

end

PIE

After clicking PIE in the Editor, you can control the action of the character through WASD and SpaceBar.

Preview