Skip to main content

近战(Melee)组件使用说明

概述

近战碰撞组件(Melee Collision Component)在角色播放攻击动作时,提供对角色武器运动轨迹上的碰撞检测功能。

近战碰撞组件

添加近战碰撞组件

大纲(Hierarchy)面板中选中一个对象,然后在观察器(Inspector)面板中,点击添加组件(Add Component),选择近战碰撞组件(Melee Collision Component),添加一个近战碰撞组件。

image-20230728173924469

image-20230728174017144

添加完成:

image-20230728174235157

属性

观察器(Inspector)面板中,选中LMeleeCollision,下方会显示该组件的相关属性。

image-20230803103336466

调试

调试(Debug)用于开启或关闭碰撞检测调试信息,这些调试信息包括武器碰撞体的运动轨迹、碰撞点等。

image-20230803093940189

属性说明
武器方向(Direction Of Weapons)显示武器方向,一般取武器模型坐标系的某个轴向量作为武器的方向。 武器的轴向量通过在属性武器(Weapons)-> 进阶(Advance)中,开启详细碰撞(Detailed Collision)后通过主轴(Main Axis)属性进行切换。
image-20230803104033410
碰撞结果(Collision Result)启用后,碰撞检测过程中产生碰撞时的位置,表现形式为蓝色几何体。
碰撞轨迹(Collision Trajectory)仅可在启用碰撞结果(Collision Result)后启用。启用后,在场景中会显示近战武器碰撞检测过程中武器的运动轨迹,表现形式为灰色几何体。
显示时间(Exist Time)设置碰撞结果(Collision Result)及碰撞轨迹(Collision Trajectory)几何体的显示时间。
忽略射线结果(Ignore Raycast Result)启用后,射线照射到场景中的物体,仅留下撞击点,在撞击点处绘制红色小球。
忽略重叠结果(Ignore Overlap Result)启用后,场景中不再显示碰撞体的运动轨迹。
忽略扫描结果(Ignore Sweep Result)启用后,与场景中物体发生碰撞时不再绘制绿色线条以及扫描检测的起始和终点位置。
忽略详细的碰撞数据(Ignore Detailed Collision Data)开启/关闭精确碰撞检测。若不启用,则精确检测时将计算碰撞检测的具体撞击点;若启用,则仅判断有无击中物体,不会记录发生碰撞的具体位置。

伤害事件

伤害事件(Damage Event)用于游戏逻辑中的伤害计算,包括伤害及冲击力。

image-20230803104214117

属性说明
生成伤害事件(Generate Damage Event)启用后,游戏逻辑可以获取具体的伤害信息。
伤害程度(Damage Degree)造成的伤害大小。
冲量(Momentum)造成的冲击力大小。

忽略游戏对象

添加或删除需要忽略的游戏对象。

image-20230731113421995

属性说明
忽略游戏对象(Ignore Game Object)选择需要被忽略的游戏对象(当前关卡(Level)中的游戏对象),该游戏对象在游戏中不做碰撞检测。

按钮

按钮说明
image-20230808112856338添加一个新的忽略游戏对象。
image-20230808112922018删除当前忽略游戏对象。
image-20230808112952173删除所有忽略游戏对象。
image-20230808113013310复制当前忽略游戏对象。

武器

添加武器,选择参与检测的碰撞体以及碰撞检测触发方式。

image-20230803104339756

属性说明
碰撞体组件(Collider Component)需要参与近战武器碰撞检测的组件,选择该组件中的碰撞体充当武器进行碰撞检测。
骨骼名称(Bone Name)在碰撞体组件中选择充当武器的骨骼。
碰撞体索引(Collider Index)选择当前骨骼下参与近战武器碰撞检测的碰撞体。
插值个数(Interpolation Number)选择插值个数。 插值个数:在动画播放过程中可能存在帧与帧之间碰撞体位移过大的情况,需要选择合适的插值数填充碰撞体在帧与帧之间的位移。(武器碰撞体过小或攻击动作速度过快时,需要增大插值次数,否则有概率发生检测遗漏。)
武器组(Weapon Group)触发事件时通知对应组别中的所有武器(Weapons)开启碰撞检测。
有效(Valid)武器是否生效,勾选则生效,反之则不生效。
触发器模式(Trigger Mode)选择触发器模式。
  • 无(None):不触发近战武器碰撞检测。
  • 移动(Movement):发生位移时开启武器的碰撞检测。
  • 碰撞检测通知(Collision Detection Notify):通过碰撞检测通知开启武器的碰撞检测。
详细碰撞(Detailed Collision)启用射线碰撞,记录与武器射线发生碰撞的碰撞点。
主轴(Main Axis)调整射线方向。

脚本

添加脚本文件。

image-20230802105656888

属性说明
脚本文件(Script File)脚本文件名称,选择要绑定的脚本文件。
指定的骨架网格体(Specified Skeletal Mesh)选择当前对象中的任一LSkeletalMeshComponent组件后,当前的Melee组件会根据所选LSkeletalMeshComponent的动画播放做碰撞检测。

快速入门

以下为使用Melee的一个示例。

创建组合体

参考Acotr组装,创建一个组合体,包含SK_Avatar_tposeAvatar_AttackAvatar_Walk动画资产。

image-20230803130832240

在动画中添加碰撞检测事件

组合体编辑器(Actor Editor)中单击其中一个动画资产打开动画编辑器(Animation Editor)

image-20230803131246746

动画编辑器(Animation Editor)动画浏览器(Asset Browser) 中,选中Avatar_Attack,右击打开快捷菜单,选择将骨架资产转为动画序列资产(Convert Skeleton To AnimSeq),在弹出的窗口中输入名称后点击确定(OK)按钮完成转换。该动画序列资产将用于添加碰撞检测事件。

image-20230803162035661

双击转换后的动画序列资产,在通知(Notify)轨道中右键,依次点击添加通知(Add Notify) -> 碰撞检测通知(Collision Detection Notify),添加一个碰撞检测通知。

image-20230803162406565

添加完成:

image-20230803162622335

根据视口(Viewport)中模型的动作位置,为该通知选择合适的帧作为通知的开始和通知的结束。可参考调整状态通知调整该通知。

AdjustNotify

右击所创建的碰撞检测通知(Collision Detection Notify),在弹出的通知(Notify)窗口中,设置触发权重(Trigger Weight),混合动画时判定是否触发通知,仅在当前动画混合度超过设置值时才能够触发通知;当触发权重(Trigger Weight)设置为0时,通知触发期间即便角色没有播放攻击动作也将进行近战武器碰撞检测。

image-20230803170803991

选中碰撞检测通知(Collision Detection Notify),在详细面板(Details Panel)中,点击image-20230803171035899按钮添加一个武器组,并设置需要通知的武器组(Weapon Group)。若不设置具体的武器组,则对近战碰撞组件(Melee Collision Component)中所有的武器(Weapons)发出通知。

image-20230803172027163

设置完成后,依次点击菜单栏文件(File) -> 保存所有(Save All)

image-20230803172437958

配置动画树

创建一个动画树,然后创建一个状态机(State Machine)节点,并创建所要用到动画参数(Animation Parameter)以及设置状态迁移条件,其状态图如下图所示:

image-20230803174942335

注:该步骤具体可参考角色快速入门 -> 添加状态机节点

idle状态所关联的动画树:

image-20230803175306538

walk状态所关联的动画树:

image-20230803175332423

插槽管理器(Slot Manager)中,创建一个名为MeleeSlot的插槽。

image-20230807152559370

在动画树中添加插槽"MeleeSlot"(Slot "MeleeSlot")节点,并按下图所示将各个节点链接在一起。

image-20230807152533177

有关如何创建插槽和添加插槽节点的内容可参考动画树 -> 插槽节点

编辑完成后,点击保存(Save)按钮。

image-20230804095121517

配置组合体

在组合体编辑器中,点击资产详情(Asset Details),然后将默认动作(Default Action)设置为所创建的动画树,设置完成后点击保存(Save)按钮。

image-20230804143438206

创建布娃娃

参考布娃娃编辑器文档,为SK_Melee_Demo.actor创建布娃娃资源,为hand_r骨骼添加刚体。

image-20230804142127414

创建角色

创建一个关卡,依次点击创建游戏对象(Create Game Object) -> 角色(Character),创建一个角色对象。

image-20230803110206293

观察器(Inspector)面板中,选中CharacterMesh0(Inherited),从项目(Project)面板中将所要用到的组合体(.actor)拖拽到骨架网格体配置文件(Skeletal Mesh Config File)中。

image-20230803111008766

拖拽完成:

image-20230803111541395

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

image-20230803112017415

为该角色添加弹簧臂组件(Spring Arm Component)摄像机组件(Camera Component),并调整摄像机组件的位置和角度至合适值。

image-20230804100523797

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

image-20230804164000348

添加近战碰撞组件

大纲(Hierarchy)面板中选中所创建的角色对象,然后为对象添加一个近战碰撞组件(Melee Collision Component)(可参考添加近战碰撞组件)。

image-20230803105156125

观察器(Inspector)面板的下方设置近战碰撞组件(Melee Collision Component)的属性。

伤害事件(Damage Event)中配置伤害,根据需求决定是否开启生成伤害事件(Generate Damage Event),如果开启伤害事件则配置合适的伤害程度(Damage Degree)冲量(Momentum)

image-20230807152949332

忽略对象(Ignore Game Object)中可添加需要忽略的游戏对象。

image-20230807140425681

点击武器(Weapons)标题栏中的image-20230804145028480按钮,添加一个武器条目,设置碰撞体组件(Collider Component)CharacterMesh0骨骼名称(Bone Name)hand_r触发器模式(Trigger Mode)碰撞检测通知(Collision Detection Notify),勾选详细碰撞(Detailed Collision),具体配置如下图所示:

image-20230807140615512

配置好各项属性后,勾选调试(Debug)中的武器方向(Direction Of Weapons)碰撞结果(Collision Result)碰撞轨迹(Collision Trajectory),并设置显示时间(Exist Time)0.5

image-20230807153105706

创建墙壁

项目(Project)面板中将一个模型作为墙壁拖拽到关卡(Level)中,并调整其位置(Position)缩放(Scale)至合适值。

image-20230804160006504

绑定脚本

大纲(Hierarchy)面板中选中所创建的角色对象,然后在观察器(Inspector)面板中点击添加组件(Add Component)按钮,选择脚本组件(Script Component)添加一个脚本组件。

image-20230804162317962

image-20230808113354042

在资源预览窗口空白处右击打开快捷菜单,选择脚本文件(Script File),创建一个脚本文件。

image-20230804162638906

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

image-20230804163135315

拖拽完成:

image-20230807153319360

脚本文件

以下为参考脚本melee_character_demo.lua

参考以下脚本中的on_setup_input_component()函数,给角色绑定回调。

参考以下脚本中的on_leftclick_pressed()函数,在回调函数中混合Attack动画。

--script template

require("public_attr")

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

---------------------
--default callbacks--
---------------------

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

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

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

local input_comp = owner.InputComponent

if nx_is_valid(input_comp) then
on_setup_input_component(owner, input_comp)
end
end

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

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

--setup input component
function on_setup_input_component(pawn, input_comp)
if nx_find_custom(input_comp, "binded") and input_comp.binded then
return
end

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("LeftClick", IE_Pressed, true, false, true, "InputActionEvent_LeftClick_Pressed")


nx_callback(pawn, "InputAxisEvent_MoveForward", "on_moveforward")
nx_callback(pawn, "InputAxisEvent_MoveRight", "on_moveright")
nx_callback(pawn, "InputAxisEvent_Turn", "on_turn")
nx_callback(pawn, "InputAxisEvent_LookUp", "on_lookup")
nx_callback(pawn, "InputAxisEvent_AdjustCameraDis", "on_adjust_camera_dis")

nx_callback(pawn, "InputActionEvent_LeftClick_Pressed", "on_leftclick_pressed")

input_comp.binded = true

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_leftclick_pressed(owner)
local actor = character_get_actor(owner)
if not nx_is_valid(actor) then
return
end

actor:BlendAction("Attack", false, false, true, 1.0, false, "MeleeSlot", true)
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 set_tree_state(actor, state_name, enable)
if not nx_is_valid(actor) then
return
end

-- 设置状态机变量
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

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

-- 记录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
-- 记录capsule
actor.capsule = capsule
end

-- 保存快照
actor:SavePoseSnapshot("finalpose")

mesh_component.SimulatePhysics = enable_ragdoll

-- 通知混合快照
set_tree_state(actor, "IsStopped", true)

-- 播放起身动作
actor:BlendAction("GetUp_Back_montage", false, false, true, 1.0)

set_tree_state(actor, "IsDead", false)

-- 暂停两帧
nx_pause(0)
nx_pause(0)

-- 通知不再混合快照
set_tree_state(actor, "IsStopped", false)
end
end

function tick(component, delta_time)
update_capsule_location()
end

-- ragdoll状态下更新胶囊体的位置
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

--资源管理器:全局变量

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

--资源类型定义
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"

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

--获取visual_put文件
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

调试

点击运行按钮。移动角色到墙壁前,鼠标左击使角色播放攻击动作时右手碰撞到墙壁。

image-20230807105359069

  • 碰撞轨迹 灰色 :近战武器碰撞检测期间的运动轨迹。
  • 碰撞轨迹 蓝色:武器运动轨迹上粗检测存在碰撞。
  • 射线检测 红色:射线与场景中物体发生碰撞的碰撞点。
  • 扫描检测 绿色:武器(hand_r的碰撞体)与场景中物体发生碰撞,在起点与终点处绘制碰撞体形状并用绿色线条连接(碰撞体为球体时不显示)。

碰撞体为盒体:

image-20230804174855077

碰撞体为球体:

image-20230804171423711

武器(Weapons) -> 触发器模式(Trigger Mode) -> 详细碰撞(Detailed Collision)为开启状态,下图框中的黄线代表武器的射线轴向量朝向。

image-20230807105144392

以下各项调试中,所选碰撞体都为盒体。

关闭碰撞轨迹

调试(Debug)中取消勾选碰撞轨迹(Collision Trajectory)

EnableCollisionTrajectory

image-20230807095557312

关闭碰撞结果

调试(Debug)中取消勾选碰撞结果(Collision Result )

EnableCollisionResult

image-20230807095500245

关闭射线检测

调试(Debug)中勾选忽略射线结果(Ignore Raycast Result)

IgnoreRaycastResult

image-20230807095726001

关闭运动轨迹

调试(Debug)中勾选忽略重叠结果(Ignore Overlap Result)

IgnoreOverlapResult

image-20230807095857698

关闭扫描检测

调试(Debug)中勾选忽略扫描结果(Ignore Sweep Result)

IgnoreSweepResult

image-20230807100028930