Skip to main content

Motion Warping

Overview

Motion Warping allows the animation's Root Motion to be procedurally adjusted to fit different start positions and rotations to reuse the Root Motion animation assets in different scenes.

Automatically corrects the movement and rotation values of the animation based on the position and orientation of the target object. For example, allow the player to jump over barriers with a jumping motion at different positions.

Animation Sequences

By creating Motion Warping State Notifies in the animation sequence to specify the Motion Warping area, and adjusting the Root Bone displacement as needed, you can create a variety of animation effects even with limited animation assets.

image-20230602164445677

Creating State Notifies

Open the needed animation sequence asset in the Animation Editor. Right-click the Notify track and select Add Notify -> Motion Warping.

After adding a Motion Warping State Notify, you can drag the Notify tab left or right on the track to adjust its start and end time to specify the warping area.

Properties

After selecting a Motion Warping State Notify on the track, you can edit its related properties in the Details Panel.

image-20230602164537719

PropertyDescription
TypeSpecify the type of the applied algorithm.
Target NameSpecify the tag name of the target in the scene.
Warp TranslationSpecify whether to enable the adjustment of the displacement.
Warp RotationSpecify whether to eanble the adjustment of the rotation.
Rotation Warping TypeSpecify the method used to adjust the rotation.
  • Align To Target: Keep the orientation consistent with the target during the process.
  • Look At Target: Always face the target during the process.

Animation Trees

Animations can be controlled and managed by adding nodes in the Animation Tree, and animations that will use Motion Warping can be placed into slots in the Animation Tree.

Example

The following is an example of the character using the Motion Warping feature to jump over a target barrier.

Assembling Actors

Refer to Assembling Actors to create an Actor containing skinned mesh and skeleton assets.

image-20230529093523691

Setting Animation Sequences

Convert the jumping skeleton asset to an animation sequence asset. Select the skeleton asset in the Asset Browser window of the Animation Editor, then right-click and select Convert Skeleton To AnimSeq.

image-20230529093615311

Name the animation sequence asset in the pop-up window, and click the OK button to finish the conversion.

Enable Root Motion. Check Enable Root Motion in the Asset Details panel of the animation sequence asset.

Add a Motion Warping State Notify to the track in the editing panel of the asset. Right-click the track and then click Add Notify -> Motion Warping.

Enter the name of the State Notify in the pop-up New Notify window, and click the OK button to finish adding.

Adjust the position and duration of the Motion Warping State Notify according to the animations to be warped. For basic operations on State Notify, please refer to Animation Notify.

image-20230526155223209

Select the Motion Warping State Notify and modify its properties in the Details Panel. Enter the name VaultPoint in Target Name to mark the warping.

image-20230602164742654

Continue adding State Notifies as described above, and specify Target Names for these warpings.

image-20230602164853535

Check End Trigger in the Asset Details panel to trigger a Notify when the animation finishes playing.

Configuring Animation Trees

Character animations can be managed by adding nodes in Animation Trees.

In the Animation Editor, click File (Menu Bar) -> Create, then click the drop-down box in the pop-up window, select AnimTree, name it and select the path to create it.

Right-click in the created Animation Tree to open the Node Selection panel, and then click State Machine -> Add New State Machine to add a State Machine node.

Double-click the State Machine node to enter the state graph editing interface, where you can create states and organize structures. Create the transition between the two states idle and run by referring to Character Quick Start -> Adding State Machine Nodes.

Create a slot FullBody in the Slot Manager window of the Anim Tree Editor. First, right-click Slot Name in Slot Manager, and then click Add Default Group And Slot in the pop-up Shortcut Menu.

Right-click DefaultSlot to open the Shortcut Menu, select Add Slot to add a new one, enter the slot name in the pop-up window and press the Enter key to finish adding.

Drag from the State Machine node pin to open the Node Selection panel, expand the Montage category and select Slot"FullBody".

Connect the Slot "Fullbody" node to the Final Animation Pose node.

Last, click File -> Save in the Anim Tree Editor.

Configuring Actors

Double-click the created Actor file in the Resource Preview window to open it, then click Asset Details in the Actor Editor, set Default Action to the Animation Tree you just created, and set Root Motion Mode to From Everything.

image-20230529094319407

After modification, click Save in the Actor Editor.

image-20230529153000436

Creating Characters

Create a Level in the Component Editor. Add a Character in the Level and set its properties. The Skeletal Mesh Config File for the character is the Actor you just created. Refer to Character Quick Start -> Character Settings for details.

image-20230529163840236

Input Settings

Refer to Character Quick Start -> Editing Input Settings for details.

Creating Targets

Drag three models from the Resource Preview window into the Level and adjust their sizes. Use the three static meshes as targets to be avoided by the character warping (The orientation of the three static meshes needs to be consistent with the character).

In the Hierarchy panel, right-click the static mesh object to open the Shortcut Menu, and click Edit -> Rename to rename it.

After renaming:

Select the static mesh you just created in the Hierarchy panel and enter the name in the String Tag property under the Inspector panel. The name here should be consistent with the Target Name of the Motion Warping State Notify in the animation sequence.

image-20230529144646525

Set String Tags as FlyEndPoint and LandingPoint for the remaining two static meshes as described above.

Binding Scripts to Characters

Bind a script to the character. Select LCharacter in the Hierarchy panel, select LCharacter(Instance) in the Inspector panel, then click the Add Component button and select the Script Component.

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

image-20230529153111175

After dragging:

image-20230529154146504

Script One

Reference script:

motion_warping_character.lua

--Script template

require("public_attr")

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

--------------------------
--Local utility function--
--------------------------

---------------------
--Default callbacks--
---------------------

--Use this for initialization
function on_begin_play(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:AddAxisBinding("AdjustCameraDis", true, false, true, "InputAxisEvent_AdjustCameraDis")

input_comp:AddCombinationBinding("Jump", IE_Pressed, true, false, true, "InputActionEvent_Jump_Pressed")
input_comp:AddCombinationBinding("Jump", IE_Released, true, false, true, "InputActionEvent_Jump_Released")
input_comp:AddCombinationBinding("Dash", IE_Pressed, true, false, true, "InputActionEvent_Dash_Pressed")
input_comp:AddCombinationBinding("EnableLookAt", IE_Pressed, true, false, true, "InputActionEvent_EnableLookAt_Pressed")
input_comp:AddCombinationBinding("LeftClick", IE_Pressed, true, false, true, "InputActionEvent_LeftClick_Pressed")

input_comp:AddCombinationKeyBinding("R", false, false, false, false, 0, false, false, true, "r_key_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, "InputAxisEvent_AdjustCameraDis", "on_adjust_camera_dis")

nx_callback(owner, "InputActionEvent_Jump_Pressed", "on_jump_pressed")
nx_callback(owner, "InputActionEvent_Jump_Released", "on_jump_released")
nx_callback(owner, "InputActionEvent_LeftClick_Pressed", "on_leftclick_pressed")

nx_callback(owner, "InputActionEvent_Dash_Pressed", "on_dash_pressed")
nx_callback(owner, "InputActionEvent_EnableLookAt_Pressed", "on_enable_look_at_pressed")

nx_callback(owner, "on_velocity_changed", "on_velocity_changed")
nx_callback(owner, "r_key_pressed", "toggle_ragdoll")

end

-- Query MotionWarping TargetPoint
local mw_target_name1 = "VaultPoint"
local mw_target_name2 = "FlyEndPoint"
local mw_target_name3 = "LandingPoint"

local scene = owner.Scene
local actor = character_get_actor(owner)

local vault_point_objs = scene:QueryObjectListByTag(mw_target_name1, "fx_component.LGameObject")
if #vault_point_objs > 0 then
local vault_point_actor = vault_point_objs[1]
actor:AddOrUpdateMotionWarpingTarget(mw_target_name1, vault_point_actor.PositionX, vault_point_actor.PositionY, vault_point_actor.PositionZ, vault_point_actor.AngleX, vault_point_actor.AngleY, vault_point_actor.AngleZ)
end

local fly_end_point_objs = scene:QueryObjectListByTag(mw_target_name2, "fx_component.LGameObject")
if #fly_end_point_objs > 0 then
local fly_end_point_actor = fly_end_point_objs[1]
actor:AddOrUpdateMotionWarpingTarget(mw_target_name2, fly_end_point_actor.PositionX, fly_end_point_actor.PositionY, fly_end_point_actor.PositionZ, fly_end_point_actor.AngleX, fly_end_point_actor.AngleY, fly_end_point_actor.AngleZ)
end

local landing_point_objs = scene:QueryObjectListByTag(mw_target_name3, "fx_component.LGameObject")
if #landing_point_objs > 0 then
local landing_point_actor = landing_point_objs[1]
actor:AddOrUpdateMotionWarpingTarget(mw_target_name3, landing_point_actor.PositionX, landing_point_actor.PositionY, landing_point_actor.PositionZ, landing_point_actor.AngleX, landing_point_actor.AngleY, landing_point_actor.AngleZ)
end
end

nx_callback(component, "on_tick", "tick")
end

--Use this for release
function on_end_play(component)
end

--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

--Get spring_arm
function character_get_spring_arm(character)
if nx_is_valid(character) then
local spring_arm_component = character:FindComponentByClassName("LSpringArmComponent")

return spring_arm_component
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

--Mouse wheel callback
function on_adjust_camera_dis(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

local spring_arm_component = character_get_spring_arm(owner)

if nx_is_valid(spring_arm_component) then
local delta = axis_value * CAMERA_DISTANCE_CHANGE_SPEED

local cur_value = spring_arm_component.TargetArmLength
local new_value = cur_value + delta

if delta > 0 then
if new_value > CAMERA_DISTANCE_MAX then
new_value = CAMERA_DISTANCE_MAX
end

spring_arm_component.TargetArmLength = new_value
else
if new_value < CAMERA_DISTANCE_MIN then
new_value = CAMERA_DISTANCE_MIN
end
spring_arm_component.TargetArmLength = new_value
end
end

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

--Dash
function on_dash_pressed(owner)
local actor = character_get_actor(owner)
if not nx_is_valid(actor) then
return
end

local movement_comp = owner.CharacterMovement
--local mode = movement_comp.MovementMode
local new_mode = nx_enum("MOVEMENT_MODE::Flying")
movement_comp:SetMovementModeWithCustom(new_mode, 0)
actor:BlendAction("Super_Jumping_Over_sq", false, false, true, 1.0, true, "FullBody", true)
end

--lookat
function on_enable_look_at_pressed(owner)
local actor = character_get_actor(owner)
if not nx_is_valid(actor) then
return
end

local action_name = actor:GetBlendActionName(0)
local action_type = actor:GetActionType(action_name)
local action_index = actor:GetActionIndex(action_name)
if action_type == AT_ANIMTREE then
if actor:GetAnimTreeValue(action_index, "LookAtIndex") == 0 then
actor:SetAnimTreeValue(action_index, "LookAtIndex", 1)
else
actor:SetAnimTreeValue(action_index, "LookAtIndex", 0)
end
end
end

--Release jump key callback
function on_jump_released(owner)
--Character stops jumping
owner:StopJumping()

return 1
end

function on_leftclick_pressed(owner)
local actor = character_get_actor(owner)
if not nx_is_valid(actor) then
return
end

--actor:BlendAction("PrimaryMelee_D_Slow_sq", false, false, true, 1.0, true, "myslot", true)
--actor:BlendAction("PrimaryMelee_C_Slow2_MSA_Anim", false, false, true, 1.0, true, "myslot", true)
actor:BlendAction("PrimaryMelee_C_Slow_MSA_sq", false, false, true, 1.0, true, "myslot", true)
--actor:SetBlendActionPause("PrimaryMelee_C_Slow_MSA_sq", true)

--[[
local cur_percent = 0
while cur_percent < 100 do
actor:SetCurrentPercent("PrimaryMelee_C_Slow_MSA_sq", cur_percent)
nx_pause(0.016)
cur_percent = cur_percent + 0.016
end]]

--actor:SetBlendActionPause("PrimaryMelee_C_Slow_MSA_sq", true)

end

--Velocity changes callback
function on_velocity_changed(owner, component_id, new_x, new_y, new_z, old_x, old_y, old_z)
local actor = character_get_actor(owner)

if nx_is_valid(actor) then
if actor:GetBlendActionCount() > 0 then
local action_name = actor:GetBlendActionName(0)
local action_type = actor:GetActionType(action_name)
local action_index = actor:GetActionIndex(action_name)

if action_type == AT_ANIMTREE then
local speed = math.sqrt(new_x * new_x + new_y * new_y + new_z * new_z)
actor:SetAnimTreeValue(action_index, "Speed", speed)

nx_set_custom(actor,"CurSpeed",speed);
end
end
end

return 1
end

function on_rhand_ik_open(self, bOpen)
nx_set_custom(self, "rhand_ik", bOpen)
--[[local actor = self.Actor
if nx_is_valid(actor) then
if actor:GetBlendActionCount() > 0 then
local action_name = actor:GetBlendActionName(0)
local action_type = actor:GetActionType(action_name)
local action_index = actor:GetActionIndex(action_name)

if action_type == AT_ANIMTREE then
actor:SetAnimTreeValue(action_index, "bRhand", bOpen)
end
end
end--]]
end

function set_tree_state(actor, state_name, enable)
if not nx_is_valid(actor) then
return
end

-- Set state machine variables
if actor:GetBlendActionCount() > 0 then
local action_name = actor:GetBlendActionName(0)
local action_type = actor:GetActionType(action_name)
local action_index = actor:GetActionIndex(action_name)
if action_type == AT_ANIMTREE then
actor:SetAnimTreeValue(action_index, state_name, enable)
end
end
end

-- Toggle ragdoll
function toggle_ragdoll(owner, key_name)
local actor = character_get_actor(owner)
if not nx_is_valid(actor) then
return
end

nx_set_value("default_actor", actor)

local mesh_component = owner.MeshComponent
if not nx_is_valid(mesh_component) then
return
end

-- Record mesh_component
actor.mesh_component = mesh_component

local enable_ragdoll = not mesh_component.SimulatePhysics

if (enable_ragdoll) then
mesh_component.SimulatePhysics = enable_ragdoll
set_tree_state(actor, "IsDead", true)
else
local capsule = owner.CapsuleComponent
if nx_is_valid(capsule) then
-- Record capsule
actor.capsule = capsule
end

-- Save snapshot
actor:SavePoseSnapshot("finalpose")

mesh_component.SimulatePhysics = enable_ragdoll

-- Notify blend snapshot
set_tree_state(actor, "IsStopped", true)

-- Play get up action
actor:BlendAction("GetUp_Back_montage", false, false, true, 1.0)

set_tree_state(actor, "IsDead", false)

-- Pause two frames
nx_pause(0)
nx_pause(0)

-- Notify stop blend snapshot
set_tree_state(actor, "IsStopped", false)
end
end

function tick(component, delta_time)
update_capsule_location()
end

-- Update capsule location in the ragdoll state
function update_capsule_location()
local actor = nx_value("default_actor")
if not nx_is_valid(actor) then
return
end

if not nx_find_custom(actor, "mesh_component") then
return
end

local mesh_component = actor.mesh_component
if not nx_is_valid(mesh_component) then
return
end

if mesh_component.SimulatePhysics then
if not nx_find_custom(actor, "capsule") then
return
end

local capsule = actor.capsule
if not nx_is_valid(capsule) then
return
end

local x, y, z = actor:GetNodeWorldPosition("pelvis")
capsule:SetPosition(x, capsule.PositionY, z)
end
end

public_attr.lua

--Explorer: global variables

MODEL_PATH = "mdl\\"
MODEL_PATH_DX9 = ""
ACTOR_PATH = "ini\\actor\\"
ACTOR_PATH_DX9 = "ini\\actor\\"
LIGHT_PATH = "ini\\light\\"
LIGHT_PATH_DX9 = "ini\\light\\"
EFFECT_PATH = "ini\\effect\\"
PARTICLE_PATH = "ini\\particle\\"
SOUND_PATH = "snd\\"
REVERB_PATH = "reverb\\"
TRIGGER_PATH = "ini\\trigger\\"
PROBE_PATH = "ini\\light_probe\\"
VOLUME_FOG_PATH = "ini\\volume_fog\\"
MATERIAL_PATH = ""

--Resource type definitions
TYPE_MODEL = "model"
TYPE_ACTOR = "actor"
TYPE_LIGHT = "light"
TYPE_EFFECT = "effect"
TYPE_PARTICLE = "particle"
TYPE_SOUND = "sound"
TYPE_REVERB = "reverb"
TYPE_TRIGGER = "trigger"
TYPE_PROBE = "light_probe"
TYPE_VOLUME_FOG = "volume_fog"
TYPE_DECAL = "decal"
TYPE_GROUP = "group"
TYPE_RIPPLE = "ripple"
TYPE_SNOW = "snow"
TYPE_RAIN = "rainlayer"
TYPE_UI3D = "ui3d"

--Animation resource types
AT_UNKNOWN = 0
AT_SKELETON = 1
AT_SKELETON_AS_ANIMSEQUENCE = 2
AT_ANIMSEQUENCE = 3
AT_MONTAGE = 4
AT_BLENDSPACE = 5
AT_BLENDSPACE1D = 6
AT_AIMOFFSETBLENDSPACE = 7
AT_ANIMTREE = 8

TYPE_LIST = {
"model",
"actor",
"light",
"effect",
"particle",
"decal",
"sound",
"reverb",
"trigger",
"light_probe",
"volume_fog",
"ripple",
"snow",
"rainlayer",
}

FORM_TREE_BROWSER = "common_form\\form_tree_browser"
SEARCH_PATH = "ini\\common_form\\form_tree_browser\\"
SEARCH_CONFIG = SEARCH_PATH .. "form_put_visual.ini"
SEARCH_CONFIG_TEMP = "cache\\common_form\\form_tree_browser\\form_put_visual_temp.ini"
GROUP_CONFIG = "ter\\visual_group.ini"

--Get visual_put file
function get_search_file()
local ini = nx_create("IniDocument")

ini.FileName = nx_resource_path() .. SEARCH_CONFIG

if not ini:LoadFromFile() then
nx_destroy(ini)
return 0
end

local file = ini:ReadString("SEARCH_CONFIG", "search_file", "form_put_visual.ini")

nx_destroy(ini)

return nx_resource_path() .. SEARCH_PATH .. file, file
end

Bind another Motion Warping Animation Script for the character as described above.

image-20230529155633335

Script Two

Reference script:

motion_warping_character_animation.lua

--Script template

--------------------------
--Local utility function--
--------------------------

require("public_attr")
require("scene_utils")

local function get_terrain(pawn)
if nx_is_valid(pawn) then
local lscene = pawn.Scene

if nx_is_valid(lscene) then
return scene_get_engine_terrain(lscene)
end
end

return nx_null()
end

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

return nx_null()
end

local function is_moving(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)
if speed > 0.0 and movement:IsMovingOnGround() then
return true
end
end
end

return false
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 get_current_jump_count(pawn)
if nx_is_valid(pawn) then
return pawn.JumpCurrentCount
end

return 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_accelerating(pawn)
if nx_is_valid(pawn) then
local movement = get_movement_component(pawn)

if nx_is_valid(movement) then
if math.abs(movement.CurrentAccelerationX) > 0.0
or math.abs(movement.CurrentAccelerationY) > 0.0
or math.abs(movement.CurrentAccelerationZ) > 0.0 then
return true
end
end
end

return false
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

local function get_foot_ik_weight(role)
local foot_ik_weight = -1.0

local foot_ik_id = role:GetCurveIDByNameInSkeleton("foot_ik")

if foot_ik_id >= 0 then
foot_ik_weight = role:GetCurveValue(foot_ik_id)
end

return foot_ik_weight
end

function transition_callback_IdleToJogStart(pawn, role, action_index, machine_index, prev_state, next_state)
if is_accelerating(pawn) and not is_jumping(pawn) and is_moving(pawn) then
return true
else
return false
end
end

function transition_callback_JogStartToJogStop(pawn, role, action_index, machine_index, prev_state, next_state)
if get_speed(pawn) < 1.0 then
return true
else
return false
end
end

function transition_callback_IdleToRun(pawn, role, action_index, machine_index, prev_state, next_state)
if get_speed(pawn) > 50.0 then
return true
else
return false
end
end

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

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

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

function transition_callback_IdleOrJogsToJumps(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_JumpsToJumpLand(pawn, role, action_index, machine_index, prev_state, next_state)
if not is_jumping(pawn) then
return true
else
return false
end
end

function transition_callback_JumpsToIdleOrJogs(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_JumpToSecondJump(pawn, role, action_index, machine_index, prev_state, next_state)
if is_jumping(pawn) and get_current_jump_count(pawn) > 1 then
return true
else
return false
end
end

function transition_callback_SecondJumpToThirdJump(pawn, role, action_index, machine_index, prev_state, next_state)
if is_jumping(pawn) and get_current_jump_count(pawn) > 2 then
return true
else
return false
end
end

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

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

transition_event_callback =
{
["IdleToJogStart"] = transition_callback_IdleToJogStart,
["JogStartToJogStop"] = transition_callback_JogStartToJogStop,
["IdleToRun"] = transition_callback_IdleToRun,
["RunToJogStop"] = transition_callback_RunToJogStop,
["JogStopToIdle"] = transition_callback_JogStopToIdle,
["JogStopToJogStart"] = transition_callback_JogStopToJogStart,
["IdleOrJogsToJumps"] = transition_callback_IdleOrJogsToJumps,
["JumpsToJumpLand"] = transition_callback_JumpsToJumpLand,
["JumpsToIdleOrJogs"] = transition_callback_JumpsToIdleOrJogs,
["JumpToSecondJump"] = transition_callback_JumpToSecondJump,
["SecondJumpToThirdJump"] = transition_callback_SecondJumpToThirdJump,

["IdleToMultiDiectionMove"] = transition_callback_IdleToMultiDiectionMove,
["MultiDiectionMoveToIdle"] = transition_callback_MultiDiectionMoveToIdle,

}

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)
if nx_is_valid(role) then
nx_log("state_machine_state_callback_EnterState event, role: " .. nx_string(role) .. " action_name: " .. role:GetActionName(action_index) .. ", machine index: " .. role:GetAnimTreeStateMachineName(action_index, machine_index) .. ", state name: " .. role:GetAnimTreeStateMachineStateName(action_index, machine_index, state_index))
end
end

function state_machine_state_callback_LeaveState(pawn, role, action_index, machine_index, state_index)
if nx_is_valid(role) then
nx_log("state_machine_state_callback_LeaveState event, role: " .. nx_string(role) .. " action_name: " .. role:GetActionName(action_index) .. ", machine index: " .. role:GetAnimTreeStateMachineName(action_index, machine_index) .. ", state name: " .. role:GetAnimTreeStateMachineStateName(action_index, machine_index, state_index))
end
end

function state_machine_state_callback_FullyBlendState(pawn, role, action_index, machine_index, state_index)
if nx_is_valid(role) then
nx_log("state_machine_state_callback_FullyBlendState event, role: " .. nx_string(role) .. " action_name: " .. role:GetActionName(action_index) .. ", machine index: " .. role:GetAnimTreeStateMachineName(action_index, machine_index) .. ", state name: " .. role:GetAnimTreeStateMachineStateName(action_index, machine_index, state_index))
end
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 false
end

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

return
end

function has_fullbody_action(pawn)
return false
end

function animtree_callback_ShouldUseLayerBlend(pawn, role)
if not has_fullbody_action(pawn) and is_moving(pawn) then
return true
end

return false
end

function animtree_callback_CalcLeanAngle(pawn, role)
return 0.0
end

function animtree_callback_CalcSlopeAngle(pawn, role)
return 0.0
end

function animtree_callback_ShouldUseRHand(pawn, role)
if nx_is_valid(pawn) then
return nx_custom(pawn, "rhand_ik")
end

return false
end

function animtree_callback_GetPlayerWeaponState(pawn, role)

return 0
end

function animtree_callback_GetPlayerFightState(pawn, role)

return 0
end

function animtree_callback_IsPlayerMoving(pawn, role)
if is_moving(pawn) then
return true
end

return false
end

--Animation tree right foot IK node callback
function animtree_callback_RRootIK(pawn, role)
local terrain = get_terrain(pawn)

if not nx_is_valid(terrain) then
return false
end

local foot_ik_weight = get_foot_ik_weight(role)

if foot_ik_weight < 0.0 then
--Foot IK weight less than 0.0, close Foot IK
return false
end

--Right foot node position in model space
local obj_pos_x, obj_pos_y, obj_pos_z = role:GetNodeObjectPosition("foot_r")
local foot_rel_height = obj_pos_y

--Right foot node position in world space
local position_x = 0.0
local position_y = 0.0
local position_z = 0.0
position_x, position_y, position_z = role:GetNodeWorldPosition("foot_r")

--Ground height in world space
local groundheight = terrain:GetGroundHeight(position_x,position_z)

--Right foot node adjust to the ground in world space
local strnewpos = nx_string(position_x) .. "," .. nx_string(groundheight+foot_rel_height) .. "," .. nx_string(position_z)

if (position_y-foot_rel_height) < groundheight or ((position_y-foot_rel_height)-groundheight) > 0.05 then
--Right foot node lower than the ground or 0.05 meter above the ground, set right foot IK parameters
local action_index = get_anim_tree_index(role)

if action_index >= 0 then
role:SetAnimTreeValue(action_index, "footrpos", strnewpos)
role:SetAnimTreeValue(action_index, "foot_ik_weight", foot_ik_weight)
return true
end

return false
else
return false
end
end

function animtree_callback_LRootIK(pawn, role)
local terrain = get_terrain(pawn)

if not nx_is_valid(terrain) then
return false
end

local foot_ik_weight = get_foot_ik_weight(role)

if foot_ik_weight < 0.0 then
return false
end

local obj_pos_x, obj_pos_y, obj_pos_z = role:GetNodeObjectPosition("foot_l")
local foot_rel_height = obj_pos_y

local position_x = 0.0
local position_y = 0.0
local position_z = 0.0
position_x, position_y, position_z = role:GetNodeWorldPosition("foot_l")

local groundheight = terrain:GetGroundHeight(position_x,position_z)
local strnewpos = nx_string(position_x) .. "," .. nx_string(groundheight+foot_rel_height) .. "," .. nx_string(position_z)

if (position_y-foot_rel_height) < groundheight or ((position_y-foot_rel_height)-groundheight) > 0.05 then
local action_index = get_anim_tree_index(role)

if action_index >= 0 then
role:SetAnimTreeValue(action_index, "footlpos", strnewpos)
role:SetAnimTreeValue(action_index, "foot_ik_weight", foot_ik_weight)
return true
end

return false
else
return false

end
end

animtree_event_callback =
{
["ShouldUseLayerBlend"] = animtree_callback_ShouldUseLayerBlend,
["CalcLeanAngle"] = animtree_callback_CalcLeanAngle,
["CalcSlopeAngle"] = animtree_callback_CalcSlopeAngle,
["ShouldUseRHand"] = animtree_callback_ShouldUseRHand,
["RRootIK"] = animtree_callback_RRootIK,
["LRootIK"] = animtree_callback_LRootIK,
["GetPlayerWeaponState"] = animtree_callback_GetPlayerWeaponState,
["GetPlayerFightState"] = animtree_callback_GetPlayerFightState,
["IsPlayerMovingHorizontal"] = animtree_callback_IsPlayerMoving,
}

function on_animtree_event(mesh_comp, action_index, callback_name)
local callback = animtree_event_callback[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)
end

return false
end

local function get_current_time_scale(role)
local curve_id = role:GetCurveIDByNameInSkeleton("slowmo_time_scale")
--local curve_id = nx_custom(role, "slowmo_time_scale")
if curve_id < 0 then
--nx_log("invalid test_curve_id: " .. curve_id)
return
end

local curve_value = role:GetCurveValue(curve_id)
return curve_value
end

local function begin_bullut_time(pawn)
if nx_is_valid(pawn) then
local time_scale = 0.05

local world = nx_value("world")
world.GlobalTimeSpeed = time_scale
pawn.camera_rotated = true
end
end

local function end_bullut_time(pawn)
if nx_is_valid(pawn) then
local world = nx_value("world")
world.GlobalTimeSpeed = 1
pawn.camera_rotated = false
end
end

function on_action_event(mesh_comp, action_name, event_name, frame, info)

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

if event_name == "JumpOver" then

pawn.JumpAgainLock = true

elseif event_name == "JumpLand" then

pawn.JumpAgainLock = false

elseif event_name == "AttackHit" then
local terrain = nx_value("terrain")
local npc_story = nx_value("npc_story")

if nx_is_valid(terrain) then
local visual_table = terrain:GetRangeVisualList(role.PositionX, role.PositionZ, 2.0)
for k,v in pairs(visual_table) do
if nx_string(npc_story) == nx_string(v) then
npc_story.be_hurted = true
end
end
end
elseif event_name == "Melee_Start" then

begin_bullut_time(pawn)

elseif event_name == "Melee_End" then

end_bullut_time(pawn)

else
--nx_log("event_name = " .. event_name)
end

if event_name == "TRIGGER_ACTION_END" then
local movement_comp = pawn.CharacterMovement
--if movement_comp.MovementMode == nx_enum("MOVEMENT_MODE::Flying") then
local movement_mode = nx_enum("MOVEMENT_MODE::Walking")
movement_comp:SetMovementModeWithCustom(movement_mode, 0)
--end
end
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_action_event", "on_action_event")
nx_callback(mesh_comp, "on_state_machine_state_event", "on_state_machine_state_event")
end

---------------------
--Default callbacks--
---------------------

--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
mesh_comp.ActorCalleeType = "CComponentActorCalleeScript"
nx_bind_script(mesh_comp, nx_current(), "anim_callee_init")
end
end
end

--Use this for release
function on_end_play(component)
end

scene_utils.lua

--scene utils script

function scene_get_engine_terrain(lscene)
local level = lscene.PersistentLevel

if nx_is_valid(level) then
local lterrain = level:GetGameObjectByType("fx_component_terrain.LTerrain")

if nx_is_valid(lterrain) then
return lterrain.EngineTerrain
end
end

return nx_null()
end

PIE

Click the Play In Editor button to view the effect.

image-20230529160126958