Skip to main content

逐骨骼分层混合节点

最近更新时间:2023-10-07

概述

逐骨骼分层混合(Layered Blend Per Bone)能够在基础姿势上使用逐骨骼的掩码叠加多层姿势,常用于实现角色不同部位受不同动画驱动的这类需求。逐骨骼分层混合(Layered Blend Per Bone)允许将输入姿势定义为影响不同骨架区域的层,并对这些层进行加权混合,例如可用于角色在行走中播放不同的上半身动作。

节点使用

动画树(Anim Tree)空白处右键打开节点选择面板,展开混合(Blends)分类,从中选取逐骨骼分层混合(Layered Blend Per Bone)

image-20220909143257482

添加完成:

点击节点上添加引脚(Add Pin)可以为其添加新的引脚。

添加引脚完成:

然后可以继续为节点配置合适的输入/输出。

image-20230913174605572

属性

选中逐骨骼分层混合(Layered Blend Per Bone)节点后,可在属性窗口(Property Window)看到该节点的各项属性。

image-20230914110516190

属性说明
混合权重列表(Blend Weight List)设置每层的权重。
网格空间旋转混合(Mesh Space Rotation Blend)是否在网格空间混合旋转。
曲线混合选项(Curve Blend Option)设置曲线混合行为来控制动画层的混合方式。
  • 覆盖(Override):输入姿势序号高的曲线覆盖序号低的曲线。
  • 不覆盖(Do Not Override):输入姿势序号高的曲线不覆盖序号低的曲线。
  • 权重归一化(Weight Normalization):将各路姿势的权重归一化后对曲线加权平均。
  • 按权重混合(Blend By Weight):按各路姿势的权重加权平均。
  • 使用基础姿势的值(Use Base Pose Value):使用第一路输入姿势的曲线。
  • 使用最大值(Use Max Value):使用各路曲线的最大值。
  • 使用最小值(Use Min Value):使用各路曲线的最小值。
层设置(Layer Settings)层设置骨骼权重掩码,每层代表对应的混合姿势,有多少个混合姿势的输入就有多少层。每层可以配置多个分支,每个分支可以指定一根骨骼作为分支的根,一个深度值作为这个分支的过渡深度。当深度小于等于0时,该骨骼及其子骨骼不进行混合,应用基础姿势。当深度大于0时,该骨骼及其所有子骨骼与基础姿势进行混合,随着深度的增加逐渐增加混合姿势的混合权重。

示例

以下是使用逐骨骼分层混合(Layered Blend Per Bone)节点,让角色下半身移动时,上半身做攻击动作的简单示例。

本示例是在角色快速入门示例的基础上完成的。

添加动画

为原组合体(SK_Human.actor)添加攻击动画(default_Attack01.xskt)。

image-20230913154325146

添加的攻击动画:

Attack

编辑动画资产

组合体编辑器(Actor Editor)中双击添加的攻击动画(default_Attack01)打开动画编辑器(Animation Editor),进入到该动画资产的编辑界面。在资产细项(Asset Details)面板中,设置混入时间(Blend In Time)为0.2,混出时间(Blend Out Time)为0.3,使动作自然切换。

image-20231007140458460

依次点击菜单栏文件(File) -> 保存(Save)保存修改。

image-20231007134931558

配置动画树

角色快速入门所创建的动画树中继续添加一个缓存姿势节点,修改其名称为Locomotion,并将状态机节点连接到该缓存姿势节点上。

image-20230913152304490

插槽管理器(Slot Manager)中添加一个UpperBody插槽,然后添加一个逐骨骼分层混合(Layered Blend Per Bone)节点、一个UpperBody插槽节点和两个使用缓存姿势“Locomotion”(Use Cache Pose "Locomotion")节点,并按照下图将各个节点连接起来。

注:有关插槽节点的使用可参考动画插槽

image-20230914132106177

选中逐骨骼分层混合(Layered Blend Per Bone)节点,在属性窗口(Property Window)中点击 image-20230831130857956 按钮添加三层。

image-20230913152833105

按照下图,为这三层分别填写骨骼名称(Bone Name)深度(Depth),从而来指定该混合节点影响的骨骼。

image-20230913153012529

需要混合的攻击动画,也就是上半身为攻击动作,下半身不进行混合。Bip01骨骼,深度为4(大于0)控制该骨骼及其子骨骼与基础姿势进行混合。

Bip01骨骼(黄色高亮):

image-20230831165410525

下半身为行走动作,所以下半身不进行混合。找到控制下半身的骨骼Bip01 L Thigh(左大腿)和Bip01 R Thigh(右大腿),将这两个骨骼混合深度设置为-1使得下半身不进行混合,保持行走动画下半身的动作。

Bip01 L Thigh骨骼和Bip01 R Thigh骨骼(橙色框):

image-20230913153341923

点击保存(Save)按钮。

image-20230831174112098

输入设置

角色快速入门输入设置中继续添加一个组合映射。点击组合映射列表(Combination Mapping List)旁的 image-20230901155739068 按钮添加一个空的组合映射,然后设置其组合名称(Combination Name) 为Attack,键名(Key Name) 为Three。

image-20230901160321699

编写脚本

继续编辑原脚本(controller.lua)。

on_setup_input_component()函数中为人物绑定攻击回调,如下所示:

input_comp:AddCombinationBinding("Attack", IE_Pressed, true, false, true, "Attack_Pressed")

nx_callback(owner, "Attack_Pressed", "on_attack_pressed")

在回调函数on_attack_pressed()中混合攻击动画。

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

actor:BlendAction("default_Attack01", false, false, true, 1.0, true, "UpperBody", true)
end

controller.lua:

--动画资源类型
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

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


--用于初始化
function on_begin_play(component)
local owner = component.ActorOwner

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

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

--第一个参数对应输入设置中的Axis Name
--最后一个参数为自定义事件名
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("Attack", IE_Pressed, true, false, true, "Attack_Pressed")

--第二个参数为AddAxisBinding中的最后一个参数,最后一个参数表示脚本中的函数名
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, "Attack_Pressed", "on_attack_pressed")

nx_callback(owner, "on_velocity_changed", "on_velocity_changed")

end
end

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


--用于发布
function on_end_play(component)
end

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

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

--向前移动回调
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_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


--速度变化回调
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_attack_pressed(owner)
local actor = character_get_actor(owner)
if not nx_is_valid(actor) then
return
end

actor:BlendAction("default_Attack01", false, false, true, 1.0, true, "UpperBody", true)
end

绑定脚本

项目(Project)面板中将所创建的脚本拖拽到脚本组件(LScript)脚本文件(Script File)中。

image-20230901162320919

拖拽完成:

image-20230913165704327

运行

点击运行按钮,WASD移动时按3键,角色会一边移动一边攻击。

Preview