Skip to main content

动画状态机

概述

动画树编辑器( Anim Tree Editor)中添加状态机(State Machine)并暴露接口,可实现逻辑层的状态切换,从而由数据来驱动状态管理。

状态机(State Machine)节点包含若干状态和状态间的迁移。每个状态包含一个动画树,每个迁移包含一组迁移规则。在外部逻辑的驱动下,状态机将根据迁移规则在不同状态间切换。状态机将激活状态的输出动画融合,并作为自身的输出姿势,传递给自身所处的动画树做进一步处理。

状态机节点

创建状态机节点

动画树(Anim Tree)空白处右击打开节点选择(Node Selection)面板,展开状态机(State Machine)分类并从中选取添加新状态机(Add New State Machine)

image-20230704110151612

创建完成:

image-20230704110222316

编辑状态机节点

选中状态机节点,点击节点的标题区可修改状态机名。

ModifyName

双击节点可进入该状态机的状态图中,在状态图中可创建状态和组织结构。

StateGraph

状态机节点属性

动画树(Anim Tree)中选择状态机(State Machine)节点,可在属性窗口(Property Window)中设置其属性。

image-20230705103614191

属性说明
状态机名(State Machine Name)编辑状态机名。
单帧最大状态迁移数量(Max Amount Of State Transition Per Frame)一帧中最多运行状态迁移的数量。
再次进入是否重置(Whether Reset When Re-Entry)再次进入是否需要重置,如果不重置则从离开时的状态开始更新。
初始化时是否跳过第一次迁移(Whether Skip First Transition When Initializing)若勾选,则将直接满权重进入满足条件的状态;否则,将从入口状态过渡至满足条件的状态。

状态节点

创建状态节点

在状态图空白处右击打开节点选择面板,点击添加状态(Add State)

image-20230704111650722

创建完成:

image-20230704161325094

编辑状态节点

选中状态节点,点击该节点的标题区后可修改状态名。

image-20230704143802964

双击状态节点可进入该状态的动画树编辑界面。

AnimTreeOfState

根据需要,为该状态配置动画树。

image-20230704162746438

状态节点属性

在状态图中选中状态节点,可在属性窗口(Property Window)中设置其属性。

image-20230704112226503

属性说明
状态名(State Name)编辑状态名。
进入状态事件(Start State Event)进入状态事件回调函数名。
离开状态事件(End State Event)离开状态事件回调函数名。
完全混入状态事件(Fully Blend Into State Event)完全混入状态事件回调函数名。
每次进入状态都重置(Reset On Every Entry)是否每次进入状态都重置关联动画树的子节点。

创建迁移

从一个状态节点的外沿处按住鼠标左键不松开,向外拖拽,可拉出一根代表迁移的箭头,在合适的位置释放鼠标后,会弹出一个节点选择面板,选择想要创建的目标节点。此时,就一次性创建了一条迁移和一个目标状态。

CreateTransition

可以从刚才的目标状态再创建一条迁移至原先的状态。

CreateTransition02

一个状态 -> 另一个状态的迁移可以有多个。

CreateTransition03

迁移属性

点击两个状态之间的迁移,可在属性窗口(Property Window)中设置其属性。

image-20230705102400651

属性说明
过渡模式(Transition Mode)两个状态动画播放连接时的过渡模式。
  • 交叉淡入淡出(Cross Fade):原动画权重逐渐减少,新动画权重逐渐增加。
  • 惯性过渡(Inertia Transition):在过渡开始时保留原动画的惯性,过渡中原动画惯性逐渐消失。
持续时间(Duration)应用于淡入淡出的持续时间。
混合模式(Blend Mode)应用于淡入淡出的混合类型。
优先级(Priority)动画编辑器过渡优先级。
动作结束时自动迁移(Auto Transit When Action Ends)前一个状态的动作结束后是否自动迁移至目标状态。
回调字符串(Callback String)配置回调函数,根据返回值决定是否迁移。
是否使用条件(Use Conditions)勾选后可选择参数(除字符串型参数和向量型参数)用于条件表达式,并将最终结果用于控制状态迁移。

删除迁移

右击两个状态间迁移,选择删除迁移(Delete Transition)可删除该迁移。

image-20230705102600322

状态机构建常规操作

实际操作中可根据角色的动作需求,在状态图中进行总体设计,然后逐个状态,逐个迁移进行实现。

image-20230704134901488

双击Ground状态节点进入所关联的动画树(Anim Tree),在动画树中可添加各类动画树节点以实现预期的地面动作需求。例如,这里使用一个混合空间播放器(Blend Space Player)节点。

image-20230704172131592

完成这个状态的设置后,可以借助导航栏(Navigation Bar)跳出这一层级的编辑,重新回到之前的状态图。

image-20230704172442243

接下来可以对其他状态和迁移进行编辑,例如可以选中JumpStart状态 -> Jump状态的迁移,在属性窗口(Property Window)中编辑其属性完成迁移规则的配置。

image-20230704173532979

管道节点

管道可以视为一种特殊的状态,与上述状态不同的是,它不包含动画树。除了通过迁移控制状态切换外,状态机还提供了一种被称为管道的节点,用于一对多,多对一或多对多的状态切换需求。管道类似于一个移除了动画树的状态。它内部具有一个“是否允许进入迁移”的规则,通过对这条规则的配置,当状态试图通过管道迁移时,可以决定是否能够通过其做进一步的迁移。

创建管道节点

在状态图空白处右击打开节点选择面板,点击添加管道(Add Conduit)

image-20230704142535454

创建完成:

image-20230704142813411

选中管道节点,点击该节点的标题区可修改管道名。

image-20230704143731930

管道节点属性

在状态图中选中管道节点,可在属性窗口(Property Window)中设置其属性。

image-20230704143957445

属性说明
管道名(Conduit Name)编辑管道名。
是否能进入(Can Entry)是否能进入该节点。

管道节点用例

下图中的Killed节点即为管道(Conduit)节点,当角色处于Walk或Run的运动状态时,若遭受攻击,健康值低于死亡临界值,逻辑控制状态向Killed管道节点迁移,并由具体的迁移规则决定最终切换至何种状态。

image-20230704155821525

迁移规则

状态迁移包含如下三种迁移规则:

  • 状态动画播放完自动切换到下一状态

  • 通过使用回调函数制定迁移规则以完成状态迁移

  • 通过设置是否使用条件(Use Conditions)完成状态迁移。

    使用回调函数和使用条件相比,推荐后者。

动画播放完自动迁移

设计如下图所示的状态图,完成起跳到落地的状态迁移。

image-20230705114559820

Jump状态关联的动画树(Anim Tree)

image-20230705130517245

JumpEnd状态关联的动画树(Anim Tree)

image-20230705130613337

选中Jump状态 -> JumpEnd状态的迁移, 在其属性窗口(Property Window)中勾选动作结束时自动迁移(Auto Transit When Action Ends)

image-20230705131149639

按照上述操作,设置JumpEnd状态 -> Jump状态的迁移。

image-20230705131935987

点击应用(Apply)按钮即可查看效果。Jump状态动画播放结束后自动过渡到目标JumpEnd状态动画,JumpEnd状态动画播放结束后自动过渡到Jump状态动画,依此循环。

AutoTransitWhenActionEnds

回调字符串

可通过设置回调字符串(Callback String)来完成状态迁移。设计如下图所示的状态图,然后分别设置状态间的迁移,例如,选中Ground状态 -> JumpStart状态的迁移,在其属性窗口(Property Window)回调字符串(Callback String)中输入jump_start。

image-20230705173506200

状态迁移的回调

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

参数1:AnimCallee实体

参数2:目标角色实体

参数3:动作索引

参数4:状态机索引

参数5:过渡前状态索引

参数6:过渡后状态索引

参数7:回调事件名称字符串

使用条件

设计如下图所示的状态图,完成Idle和Run状态间的迁移。

image-20230705133251550

Idle状态关联的动画树(Anim Tree)

image-20230705164957799

Run状态关联的动画树(Anim Tree)

image-20230705165024234

动画参数(Animation Parameter)中点击 image-20230705133459967 按钮添加一个参数,此处选择浮点型(Float) 参数。

image-20230705140132860

添加完成后,修改参数名称(Parameter Name)为Speed。

image-20230705144727899

选中Idle状态 -> Run状态的迁移,在其属性窗口(Property Window)中,勾选使用条件(Use Conditions),在第一个下拉框中选择刚才所创建的参数,并设置具体条件,当Speed参数值大于0.1时,则从Idle状态过渡至Run状态。

image-20230705144939649

选中Run状态 -> Idle状态的迁移,同样勾选使用条件(Use Conditions),选择Speed参数用于条件表达式,当Speed参数值小于0.1时,则从Run状态迁移至Idle状态。

image-20230705145336074

动画树编辑器(Anim Tree Editor)中,在菜单栏下方的下拉框中选择预览对象(Preview Object),点击应用(Apply)按钮查看效果。

image-20230705164402401

修改Speed参数值可切换状态。

UseConditions

有关使用状态机并通过使用条件制定迁移规则的具体应用可参考角色快速入门

示例

以下为应用状态机(State Machine)完成控制角色行动的一个示例,迁移规则运用了动作结束时自动迁移(Auto Transit When Action Ends)回调字符串(Callback String)

创建组合体

参考Actor组装,创建一个组合体(Actor),其中包含一系列动画资产。

image-20230707101655913

创建动画序列

组件编辑器(Component Editor)中,双击任一骨架资产打开动画编辑器(Animation Editor)

image-20230707101717079

动画编辑器(Animation Editor)中的资产浏览器(Asset Browser)中,选中所要转换的骨架资产,右击打开快捷菜单,选择将骨架资产转为动画序列资产(Convert Skeleton To AnimSeq),在弹出的窗口中输入动画序列的名称,点击确定(OK)按钮完成创建。

image-20230707101855763

按照上述操作,将walk、run和stand相关的骨骼资产均转换为动画序列资产,后续用作混合空间(Blend Space)中的采样点。

image-20230707103626768

资产浏览器(Asset Browser)中,双击一个走路或跑步的动画序列,为其添加同步标记(Sync Marker)同步组(Sync Group),可使不同动画混合时脚步同步。为其他walk和run动画序列同样添加同步标记和同步组。可通过动画同步文档了解同步标记和同步组的相关内容。

image-20230707103733175

编辑完成后,依次点击菜单栏文件(File)-> 保存所有(Save All)进行保存。

image-20230707105705963

创建混合空间

动画编辑器(Animation Editor)中,依次点击菜单栏文件(File)-> 创建(Create),在弹出窗口的下拉框中选择混合空间(BlendSpace),设置名称和路径,点击是(Yes)完成创建。

image-20230707131133186

可参考混合空间文档,创建如下图所示的混合空间资产。

image-20230707132419873

编辑完成后,保存。

创建动画树

动画编辑器(Animation Editor)中,依次点击菜单栏文件(File)-> 创建(Create),在弹出窗口的下拉框中选择动画树(AnimTree),设置名称和路径,点击是(Yes)完成创建。

image-20230707132954488

在打开的动画树编辑器(Anim Tree Editor)中,右击空白处打开节点选择面板,展开混合空间列表(Blend Space List),选择混合空间播放器 "bs_run"(Blend Space Player "bs_run")节点。

image-20230707133558282

选中所创建的混合空间播放器(Blend Space Player)节点,在属性窗口(Property Window)中勾选是否循环(Loop),并配置脚本中的回调字符串。(可通过混合空间播放器节点了解该节点。)

image-20230710172933922

动画树(Anim Tree)空白处右击打开节点选择面板,展开缓存姿势节点列表(Cached Poses Node List),选择新建缓存姿势(Create Cache Pose)

image-20230707134420875

选中所创建的缓存姿势(Cache Pose)节点,点击其标题区修改其名称为Ground。

image-20230707134602695

混合空间播放器(Blend Space Player)节点链接到Ground缓存姿势(Cache Pose)节点。

image-20230707134724729

创建状态机

创建一个状态机(State Machine)节点,修改其名称为Locomotion。

image-20230707150942328

双击该状态机(State Machine)节点进入到状态图的编辑界面中,设计如下图所示的状态图。

image-20230707151822879

Ground状态所关联的动画树(Anim Tree)

image-20230707152215038

JumpStart状态所关联的动画树(Anim Tree)

image-20230707152545761

Jump状态所关联的动画树(Anim Tree)

image-20230707152613697

JumpFall状态所关联的动画树(Anim Tree),其中混合(Blend)节点的混合权重(Blend Weight)设置为0.5,这样可以让落地到地面的姿势过渡更加自然。

image-20230707153018801

设置状态迁移

选中Ground状态 -> JumpStart状态的迁移,在属性窗口(Property Window)中,设置状态迁移回调字符串(Callback String)为jump_start。

image-20230707162637603

选中JumpStart状态,在属性窗口(Property Window)中,设置完全混入状态事件(Fully Blend Into State Event)回调函数名为fully_blend_state。

image-20230707163253291

选中JumpStart状态 -> Jump状态的迁移,在属性窗口(Property Window)中,勾选动作结束时自动迁移(Auto Transit When Action Ends)持续时间(Duration)为0.1。

image-20230711110616477

选中Jump状态,在属性窗口(Property Window)中,分别设置进入状态事件(Start State Event)离开状态事件(End State Event)完全混入状态事件(Fully Blend Into State Event)的回调函数名为start_state、end_state和fully_blend_state。

image-20230707164426428

选中Jump状态 -> JumpFall状态的迁移,在属性窗口(Property Window)中,设置状态迁移回调字符串(Callback String)为jump_end、持续时间(Duration)为0.1。

image-20230711110446115

分别设置JumpFall状态 -> Ground状态的两个迁移,在属性窗口(Property Window)中,一个勾选启用动作结束时自动迁移(Auto Transit When Action Ends),另一个使用回调字符串(Callback String)jump_start。

image-20230707165549109

image-20230707165624195

状态图编辑完成后,返回到上一级动画树(Anim Tree)中。

image-20230707165933019

状态机(State Machine)节点链接到最终动画姿势(Final Animation Pose)节点。

image-20230707170049434

点击保存(Save)按钮完成动画树(Anim Tree)整体的创建。

image-20230707170324003

配置组合体

组合体编辑器(Actor Editor)中,点击资产细项(Asset Details),设置该组合体的默认动作(Default Action)为动画树test_tree,然后点击保存(Save)

image-20230707171052363

创建角色

创建一个新的默认关卡,依次点击工具栏创建游戏对象(Create Game Object)-> 角色(Character),创建一个角色。

image-20230707171523445

大纲(Hierarchy)面板中选中所创建的角色,在观察器(Inspector)面板中选中CharacterMesh0(Inherited),将所创建的组合体(Actor)拖入到骨架网格体配置文件(Skeletal Mesh Config File)的插槽中。

image-20230707171733525

拖入完成:

image-20230707171909125

观察器(Inspector)面板中,选中CollisionCylinder(Inherited)和CharacterMesh0(Inherited)分别调整该角色胶囊体和网格体的位置,使胶囊体尽可能在包裹角色的前提下,让角色蒙皮的脚部贴地,胶囊体也需在地面上方。

image-20230707172114224

大纲(Hierarchy)面板中,选中角色对象,然后在观察器(Inspector)面板中,选中CharacterMesh0(Inherited),将默认动作覆盖(Default Action Override)设置为所创建的动画树test_tree。

image-20230711132218841

选中CollisionCylinder(Inherited),然后点击添加组件(Add Component)按钮,选择并添加弹簧臂组件(Spring Arm Component)

image-20230707175131652

image-20230707175540240

勾选弹簧臂组件的是否使用Pawn控制旋转(Use Pawn Control Rotation)属性。

image-20230707175736606

添加一个摄像机组件(Camera Component)为弹簧臂组件的子集,并调整摄像机至合适位置。

image-20230710093033550

选中CharMoveComp(Inherited)组件,设置地面摩擦(Ground Friction)为3、最大步行速度(Max Walk Speed)为9、制动减速行走(Braking Deceleration Walking)为9、最大自定义移动速度(Max Custom Movement Speed)为9。

image-20230710094441496

观察器(Inspector)面板中选中LCharacter(Instance),将自动持有玩家(Auto Possess Player)设置为玩家0(Player 0)

image-20230710142130112

绑定脚本

添加两个脚本组件(Script Component)。选中该角色,然后在观察器(Inspector)面板中点击添加组件(Add Component)按钮,选择脚本组件(Script Component)

image-20230707174119942

在脚本组件的属性(Property)面板中配置脚本文件,从资源预览窗口(Resource Preview)中将所编写的脚本拖入到角色脚本组件的脚本文件(Script File)插槽中。

image-20230710140849898

image-20230710141240650

拖拽完成:

image-20230710141418583

image-20230710141450610

脚本

以下为参考脚本:

robot_character.lua

--script template

local IE_Pressed = 0 --按键按下
local IE_Released = 1 --按键抬起
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

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

--向前移动回调
function on_moveforward(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

--获得控制器Y轴角度
local controller = owner.Controller

if nx_is_valid(controller) then
local yaw = controller.AngleY
--获得向前方向
local x, y, z = nx_function("ext_angle_get_forward_vector", 0.0, yaw, 0.0)

--增加移动量

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

return 1
end

--向右移动回调
function on_moveright(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

--获得控制器Y轴角度
local controller = owner.Controller

if nx_is_valid(controller) then
local yaw = controller.AngleY
--获得向右方向
local x, y, z = nx_function("ext_angle_get_right_vector", 0.0, yaw, 0.0)

--增加移动量
owner:AddMovementInput(x, y, z, axis_value)
end
return 1
end

--左右旋转(绕Y轴旋转)回调
function on_turn(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

owner:AddControllerYawInput(axis_value)

return 1
end

--上下旋转(绕X轴旋转)回调
function on_lookup(owner, axis_value)
if not nx_is_valid(owner) then
return 0
end

owner:AddControllerPitchInput(-axis_value)

return 1
end

--跳跃键按下回调
function on_jump_pressed(owner)
if nx_find_custom(owner, "JumpAgainLock") and owner.JumpAgainLock then
return 1
end

--角色跳跃
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

--用于初始化
function on_begin_play(component)
input_comp_init_bind(component)
nx_callback(component, "on_tick", "tick")
end

robot_character_animation.lua

--脚本模板

--------------------------
--局部效用函数--
--------------------------


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)
-- 执行
nx_log("state machine state enter jump")
end

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

function state_machine_state_callback_FullyBlendState(pawn, role, action_index, machine_index, state_index)
-- 执行
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

--用于初始化
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

最终结果

在编辑器中点击运行后,可通过WASD空格键(SpaceBar)控制角色行动。

Preview