近战(Melee)组件使用说明
概述
近战碰撞组件(Melee Collision Component)在角色播放攻击动作时,提供对角色武器运动轨迹上的碰撞检测功能。
近战碰撞组件
添加近战碰撞组件
在大纲(Hierarchy)面板中选中一个对象,然后在观察器(Inspector)面板中,点击添加组件(Add Component),选择近战碰撞组件(Melee Collision Component),添加一个近战碰撞组件。
添加完成:
属性
在观察器(Inspector)面板中,选中LMeleeCollision,下方会显示该组件的相关属性。
调试
调试(Debug)用于开启或关闭碰撞检测调试信息,这些调试信息包括武器碰撞体的运动轨迹、碰撞点等。
属性 | 说明 |
---|---|
武器方向(Direction Of Weapons) | 显示武器方向,一般取武器模型坐标系的某个轴向量作为武器的方向。 武器的轴向量通过在属性武器(Weapons)-> 进阶(Advance)中,开启详细碰撞(Detailed Collision)后通过主轴(Main Axis)属性进行切换。 ![]() |
碰撞结果(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)用于游戏逻辑中的伤害计算,包括伤害及冲击力。
属性 | 说明 |
---|---|
生成伤害事件(Generate Damage Event) | 启用后,游戏逻辑可以获取具体的伤害信息。 |
伤害程度(Damage Degree) | 造成的伤害大小。 |
冲量(Momentum) | 造成的冲击力大小。 |
忽略游戏对象
添加或删除需要忽略的游戏对象。
属性 | 说明 |
---|---|
忽略游戏对象(Ignore Game Object) | 选择需要被忽略的游戏对象(当前关卡(Level)中的游戏对象),该游戏对象在游戏中不做碰撞检测。 |
按钮
按钮 | 说明 |
---|---|
添加一个新的忽略游戏对象。 | |
删除当前忽略游戏对象。 | |
删除所有忽略游戏对象。 | |
复制当前忽略游戏对象。 |
武器
添加武器,选择参与检测的碰撞体以及碰撞检测触发方式。
属性 | 说明 |
---|---|
碰撞体组件(Collider Component) | 需要参与近战武器碰撞检测的组件,选择该组件中的碰撞体充当武器进行碰撞检测。 |
骨骼名称(Bone Name) | 在碰撞体组件中选择充当武器的骨骼。 |
碰撞体索引(Collider Index) | 选择当前骨骼下参与近战武器碰撞检测的碰撞体。 |
插值个数(Interpolation Number) | 选择插值个数。 插值个数:在动画播放过程中可能存在帧与帧之间碰撞体位移过大的情况,需要选择合适的插值数填充碰撞体在帧与帧之间的位移。(武器碰撞体过小或攻击动作速度过快时,需要增大插值次数,否则有概率发生检测遗漏。) |
武器组(Weapon Group) | 触发事件时通知对应组别中的所有武器(Weapons)开启碰撞检测。 |
有效(Valid) | 武器是否生效,勾选则生效,反之则不生效。 |
触发器模式(Trigger Mode) | 选择触发器模式。
|
详细碰撞(Detailed Collision) | 启用射线碰撞,记录与武器射线发生碰撞的碰撞点。 |
主轴(Main Axis) | 调整射线方向。 |
脚本
添加脚本文件。
属性 | 说明 |
---|---|
脚本文件(Script File) | 脚本文件名称,选择要绑定的脚本文件。 |
指定的骨架网格体(Specified Skeletal Mesh) | 选择当前对象中的任一LSkeletalMeshComponent组件后,当前的Melee组件会根据所选LSkeletalMeshComponent的动画播放做碰撞检测。 |
快速入门
以下为使用Melee的一个示例。
创建组合体
参考Acotr组装,创建一个组合体,包含SK_Avatar_tpose,Avatar_Attack和Avatar_Walk动画资产。
在动画中添加碰撞检测事件
在组合体编辑器(Actor Editor)中单击其中一个动画资产打开动画编辑器(Animation Editor)。
在动画编辑器(Animation Editor)的动画浏览器(Asset Browser) 中,选中Avatar_Attack,右击打开快捷菜单,选择将骨架资产转为动画序列资产(Convert Skeleton To AnimSeq),在弹出的窗口中输入名称后点击确定(OK)按钮完成转换。该动画序列资产将用于添加碰撞检测事件。
双击转换后的动画序列资产,在通知(Notify)轨道中右键,依次点击添加通知(Add Notify) -> 碰撞检测通知(Collision Detection Notify),添加一个碰撞检测通知。
添加完成:
根据视口(Viewport)中模型的动作位置,为该通知选择合适的帧作为通知的开始和通知的结束。可参考调整状态通知调整该通知。
右击所创建的碰撞检测通知(Collision Detection Notify),在弹出的通知(Notify)窗口中,设置触发权重(Trigger Weight),混合动画时判定是否触发通知,仅在当前动画混合度超过设置值时才能够触发通知;当触发权重(Trigger Weight)设置为0时,通知触发期间即便角色没有播放攻击动作也将进行近战武器碰撞检测。
选中碰撞检测通知(Collision Detection Notify),在详细面板(Details Panel)中,点击按钮添加一个武器组,并设置需要通知的武器组(Weapon Group)。若不设置具体的武器组,则对近战碰撞组件(Melee Collision Component)中所有的武器(Weapons)发出通知。
设置完成后,依次点击菜单栏文件(File) -> 保存所有(Save All)。
配置动画树
创建一个动画树,然后创建一个状态机(State Machine)节点,并创建所要用到动画参数(Animation Parameter)以及设置状态迁移条件,其状态图如下图所示:
idle状态所关联的动画树:
walk状态所关联的动画树:
在插槽管理器(Slot Manager)中,创建一个名为MeleeSlot的插槽。
在动画树中添加插槽"MeleeSlot"(Slot "MeleeSlot")节点,并按下图所示将各个节点链接在一起。
有关如何创建插槽和添加插槽节点的内容可参考动画树 -> 插槽节点。
编辑完成后,点击保存(Save)按钮。
配置组合体
在组合体编辑器中,点击资产详情(Asset Details),然后将默认动作(Default Action)设置为所创建的动画树,设置完成后点击保存(Save)按钮。
创建布娃娃
参考布娃娃编辑器文档,为SK_Melee_Demo.actor创建布娃娃资源,为hand_r骨骼添加刚体。
创建角色
创建一个关卡,依次点击创建游戏对象(Create Game Object) -> 角色(Character),创建一个角色对象。
在观察器(Inspector)面板中,选中CharacterMesh0(Inherited),从项目(Project)面板中将所要用到的组合体(.actor)拖拽到骨架网格体配置文件(Skeletal Mesh Config File)中。
拖拽完成:
在观察器(Inspector)面板中,选中ColliderCylinder(Inherited)和CharacterMesh0(Inherited)分别调整该角色胶囊体和网格体的位置,使胶囊体尽可能在包裹角色的前提下,让角色蒙皮的脚部贴地,胶囊体也需在地面上方。
为该角色添加弹簧臂组件(Spring Arm Component)和摄像机组件(Camera Component),并调整摄像机组件的位置和角度至合适值。
在观察器(Inspector)面板中选中LCharacter(Instance),将自动持有玩家(Auto Possess Player)设置为玩家0(Player 0)。
添加近战碰撞组件
在大纲(Hierarchy)面板中选中所创建的角色对象,然后为对象添加一个近战碰撞组件(Melee Collision Component)(可参考添加近战碰撞组件)。
在观察器(Inspector)面板的下方设置近战碰撞组件(Melee Collision Component)的属性。
在伤害事件(Damage Event)中配置伤害,根据需求决定是否开启生成伤害事件(Generate Damage Event),如果开启伤害事件则配置合适的伤害程度(Damage Degree)及冲量(Momentum)。
在忽略对象(Ignore Game Object)中可添加需要忽略的游戏对象。
点击武器(Weapons)标题栏中的按钮,添加一个武器条目,设置碰撞体组件(Collider Component)为CharacterMesh0,骨骼名称(Bone Name)为hand_r,触发器模式(Trigger Mode)为碰撞检测通知(Collision Detection Notify),勾选详细碰撞(Detailed Collision),具体配置如下图所示:
配置好各项属性后,勾选调试(Debug)中的武器方向(Direction Of Weapons)、碰撞结果(Collision Result)和碰撞轨迹(Collision Trajectory),并设置显示时间(Exist Time)为0.5。
创建墙壁
从项目(Project)面板中将一个模型作为墙壁拖拽到关卡(Level)中,并调整其位置(Position)和缩放(Scale)至合适值。
绑定脚本
在大纲(Hierarchy)面板中选中所创建的角色对象,然后在观察器(Inspector)面板中点击添加组件(Add Component)按钮,选择脚本组件(Script Component)添加一个脚本组件。
在资源预览窗口空白处右击打开快捷菜单,选择脚本文件(Script File),创建一个脚本文件。
将所创建的脚本文件拖拽到脚本组件(LScript)的脚本文件(Script File)中。
拖拽完成:
脚本文件
以下为参考脚本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
调试
点击运行按钮。移动角色到墙壁前,鼠标左击使角色播放攻击动作时右手碰撞到墙壁。
- 碰撞轨迹 灰色 :近战武器碰撞检测期间的运动轨迹。
- 碰撞轨迹 蓝色:武器运动轨迹上粗检测存在碰撞。
- 射线检测 红色:射线与场景中物体发生碰撞的碰撞点。
- 扫描检测 绿色:武器(hand_r的碰撞体)与场景中物体发生碰撞,在起点与终点处绘制碰撞体形状并用绿色线条连接(碰撞体为球体时不显示)。
碰撞体为盒体:
碰撞体为球体:
武器(Weapons) -> 触发器模式(Trigger Mode) -> 详细碰撞(Detailed Collision)为开启状态,下图框中的黄线代表武器的射线轴向量朝向。
以下各项调试中,所选碰撞体都为盒体。
关闭碰撞轨迹
在调试(Debug)中取消勾选碰撞轨迹(Collision Trajectory)。
关闭碰撞结果
在调试(Debug)中取消勾选碰撞结果(Collision Result )。
关闭射线检测
在调试(Debug)中勾选忽略射线结果(Ignore Raycast Result)。
关闭运动轨迹
在调试(Debug)中勾选忽略重叠结果(Ignore Overlap Result)。
关闭扫描检测
在调试(Debug)中勾选忽略扫描结果(Ignore Sweep Result)。