1. 项目概述与核心思路在上一篇文章里我们实现了UE4中主角攻击敌人敌人掉血的基础逻辑。当时我们可能用了一个简单的碰撞盒Box Collision或者胶囊体Capsule Collision来检测攻击是否命中。这个方法在原型阶段很高效但一旦角色动作复杂起来或者需要更精确、更符合视觉表现的判定时问题就来了。比如你的武器明明挥空了但因为碰撞盒还停留在上一帧的位置敌人却掉了血或者武器模型已经砍中了敌人但因为碰撞盒还没“扫”到那个区域攻击被判定为无效。这种“打空气掉血”和“砍中了没反应”的体验对任何动作游戏来说都是致命的。所以这次我们的核心任务就是“优化攻击判定”。这绝不仅仅是把碰撞盒调大调小那么简单而是要建立一个更精准、更实时、更符合玩家直觉的命中检测系统。优化的目标很明确让攻击判定的范围、时机与角色动画和武器模型的视觉表现高度同步消灭“玄学命中”提升打击感和游戏体验的可靠性。从网络热词和搜索内容来看大家关心的方向很集中UE4蓝图是主要实现手段攻击判定是核心需求而射线检测则是一个被广泛讨论和使用的关键技术。我们这次优化就会围绕这些点展开将基础的碰撞体检测升级为更先进的、基于动画和武器轨迹的判定方案。2. 攻击判定方案深度解析与选型在动手写蓝图之前我们必须先想清楚有哪些方法可以实现更优的攻击判定每种方法的优缺点是什么我们的项目适合哪一种2.1 传统碰撞体检测的局限性我们最初使用的可以归类为“静态或简单动态碰撞体检测”。实现方式在角色或武器骨骼上附加一个碰撞组件如Box Component设置好大小和位置。在攻击动画播放的某个时刻通过动画通知触发将这个碰撞组件的碰撞预设Collision Preset设置为检测敌人然后在一段短暂时间后关闭检测。优点实现简单计算开销极小对于回合制或判定要求极简的游戏够用。缺点精度差一个方盒子或胶囊体很难匹配刀、剑等不规则武器的攻击范围更无法还原“挥砍”的轨迹。同步困难判定完全依赖于我们手动设置的“开启时机”和“持续时间”。如果动画速度变化、或者出现卡顿判定窗口很容易和动画脱节。无法处理连续攻击对于快速连击如果只用单个碰撞体很难区分是哪一次攻击命中了目标。2.2 射线检测Raycast方案详解正如搜索内容中提到的射线检测是解决上述问题的利器。它的核心思想不是用一个“体积”去覆盖而是用一条或多条“线”去模拟武器在极短时间内扫过的路径。基本原理从空间中的一个点起点朝一个方向发射一条无形的线段射线检测这条线段路径上是否与任何物体相交。在UE4中这通常通过LineTraceBy...系列的蓝图节点实现。如何用于攻击判定我们可以在武器的关键部位如剑尖、剑刃中部设置多个插槽Socket。在攻击动作的每一帧或关键帧我们从武器上一帧的位置Last Frame Socket Location到当前帧的位置Current Frame Socket Location发射射线。如果这条射线击中了敌人就认为武器在这两帧之间“扫过”了敌人判定为命中。优点高精度可以非常精确地模拟利器的切割轨迹。实时性好每帧检测判定与动画帧率同步手感扎实。灵活性高通过增加射线的数量从剑柄到剑尖设置多个发射点可以模拟不同武器部位的伤害如剑刃伤害高剑柄伤害低。缺点性能开销每帧进行多次射线检测比单次碰撞检测开销大。对于大量单位同时进行的战斗需要优化。实现复杂度需要管理多个插槽、记录上一帧位置、处理多段射线的命中去重等。2.3 物理资产与碰撞预设的动态启用这是介于前两者之间并常与射线检测配合使用的高级方案。基本原理为武器骨骼创建一个复杂的物理资产Physics Asset为其不同部分如刀刃、刀背、护手设置不同形状的碰撞体。在默认状态下这些碰撞体的碰撞响应Collision Response设置为“No Collision”或仅查询Query Only不会阻挡物体。当攻击发生时通过动画通知或蓝图逻辑动态地将刀刃部分碰撞体的碰撞预设改为可阻挡并检测敌人如“OverlapAllDynamic”。优点视觉匹配度极高碰撞形状完全按照武器3D模型定制判定区域最直观。利用物理引擎判定由物理引擎每帧计算同步性很好。缺点配置繁琐需要制作和调整物理资产对美术资源有要求。调试稍麻烦碰撞形状复杂调试时需要可视化物理调试。实操心得对于大多数中小型动作游戏项目我推荐“以每帧射线检测为主以动画通知动态启用武器碰撞体为辅”的混合方案。射线检测负责高精度、实时的轨迹判定这是打击感的灵魂。而武器上的碰撞体可以作为一个“保底”或“特效触发区域”例如当射线检测命中后再启用武器碰撞体去触发命中停顿Hit Pause效果、播放刀光特效的碰撞事件等。这样在保证核心判定精准的同时也能丰富命中反馈。3. 基于射线检测的优化方案实战接下来我们抛弃那个笨拙的大盒子用蓝图一步步构建一个基于多段射线检测的精准攻击判定系统。3.1 武器骨骼与插槽准备一切始于资源准备。如果你的武器是附着在角色手上的静态网格Static Mesh你需要先将其转换为骨骼网格体Skeletal Mesh并绑定到一个简单的骨骼上通常就一根根骨骼名为“Weapon”。如果武器本来就是骨骼网格体比如自带晃动效果的刀那就更好了。创建插槽在武器的骨骼网格体编辑器中找到你认为在攻击中代表“有效攻击部位”的骨骼。通常我们会在**剑尖Tip和剑刃中部Edge**创建插槽。右键骨骼 - 添加插槽Add Socket。给插槽起一个清晰的名字如M_Socket_TipM_Socket_Edge。调整插槽位置与方向确保插槽的位置和方向正确。射线将从这里发射。你可以临时附加一个立方体网格到插槽上在动画中观察其运动轨迹是否与武器部位吻合。3.2 构建攻击判定蓝图逻辑我们会在角色的蓝图或一个专门的“武器系统”组件蓝图中实现核心逻辑。3.2.1 变量定义与初始化首先定义必要的变量来存储状态和数据AttackSockets(类型名称数组)存储我们创建好的插槽名称如[M_Socket_Tip, M_Socket_Edge]。LastSocketLocations(类型向量数组)用于存储每个插槽在上一帧的世界坐标位置。数组长度应与AttackSockets一致。bIsAttacking(类型布尔)标记角色是否正处于攻击状态。AttackTraceLength(类型浮点数)当两帧之间插槽移动距离异常大比如第一帧初始化时作为射线长度的安全值可设为10-20厘米。Damage(类型浮点数)本次攻击的伤害值。在事件开始运行Event BeginPlay时初始化LastSocketLocations数组为每个插槽填入一个初始值例如角色当前位置。3.2.2 每帧射线检测流程核心逻辑放在Event Tick中但需要受bIsAttacking控制。1. 判断 bIsAttacking 是否为 True。如果否则仅更新 LastSocketLocations 后返回为下一轮攻击做准备。 2. 如果是 True开始循环遍历 AttackSockets 数组中的每一个插槽。 a. 获取当前插槽在当前帧的世界位置CurrentLocation。 b. 获取该插槽在上一帧存储的位置LastLocation。 c. 计算两点间的距离DeltaDistance。 d. 判断 DeltaDistance 是否大于一个极小值如0.1。如果太小说明武器几乎没动跳过检测以避免不必要的计算。 e. 计算射线的方向(CurrentLocation - LastLocation).GetSafeNormal()。 f. 计算射线的长度取 DeltaDistance 和 AttackTraceLength 的较小值确保射线不会过长。 g. 执行射线检测LineTraceByChannel - 起点LastLocation - 终点LastLocation Direction * TraceLength - 碰撞通道Trace Channel设置为自定义的“Weapon”通道并在项目设置中让敌人角色网格体响应此通道。 - 忽略自身Ignore Self勾选避免打到自己。 h. 如果射线命中Hit Result 为有效 - 检查命中的演员Hit Actor是否是敌人可通过标签Tag或接口判断。 - 如果是敌人并且这个敌人不在本次攻击已命中的列表里需要另一个数组 AlreadyHitActors 来记录防止一次攻击对同一敌人造成多次伤害则 i. 调用敌人的受伤函数如应用伤害 ApplyDamage 或调用自定义事件。 ii. 将敌人加入 AlreadyHitActors 数组。 iii. 可选触发命中反馈如播放受击动画、屏幕抖动、生成命中粒子等。 i. 无论是否命中在循环的最后将 CurrentLocation 赋值给对应索引的 LastSocketLocations为下一帧做准备。 3. 完成所有插槽的遍历。注意事项AlreadyHitActors数组必须在每次攻击开始时清空在攻击结束时也可以清空。这个“攻击开始”和“攻击结束”的信号最佳来源是动画通知Animation Notify。3.3 动画通知驱动攻击状态我们不再用计时器或按键时长来控制攻击判定的开关而是让动画本身来驱动。创建动画通知在攻击动画的蒙太奇Attack Montage或动画序列中找到武器开始挥动的第一帧添加一个自定义动画通知命名为Notify_AttackStart。在武器挥动结束的帧添加另一个通知Notify_AttackEnd。在蓝图里处理通知在角色动画蓝图或接收动画通知的蓝图中为这两个通知创建事件。On Notify_AttackStart设置bIsAttacking true清空AlreadyHitActors数组。On Notify_AttackEnd设置bIsAttacking false可以顺便清空AlreadyHitActors数组。这样做的好处是无论动画播放速度快慢、是否被中断判定窗口都严格与动画片段绑定从根本上解决了判定与视觉不同步的问题。4. 性能优化与高级技巧每帧进行多次射线检测尤其是在有多敌人、多玩家的情况下可能会成为性能瓶颈。以下是一些优化策略和增强功能的技巧。4.1 性能优化策略降低检测频率不一定非要在Event Tick里做。可以创建一个自定义事件在攻击状态下利用Set Timer by Function设置一个极短间隔如0.02秒即每秒50次的循环计时器来执行检测逻辑。攻击结束时清除计时器。这能在几乎不影响手感的前提下减少计算次数。距离裁剪Distance Culling在检测前先计算敌人与玩家的距离。如果敌人距离超过武器可能攻击到的最大范围可保守估计为武器长度手臂长度一点容差则直接跳过对该敌人的检测逻辑。这需要维护一个附近敌人的列表可以通过Sphere Overlap定期获取。简化射线数量在保证手感的前提下使用最少的插槽。对于长剑也许剑尖一个插槽就够了。对于大刀可能需要在刀刃上设置2-3个。使用对象池管理命中特效如果每次命中都生成新的粒子特效然后销毁会产生垃圾回收开销。可以预生成一个特效对象池需要时激活并移动到位置用完失活而非销毁。4.2 判定增强与功能扩展不同攻击类型的判定配置你可以创建一个数据结构Data Table 或 结构体来定义不同的攻击。例如LightAttack: 使用插槽[M_Socket_Tip]伤害 10.0。HeavyAttack: 使用插槽[M_Socket_Tip, M_Socket_Edge, M_Socket_Hilt]伤害 25.0。SpinAttack: 使用插槽[M_Socket_Edge]但AttackTraceLength调大以覆盖旋转时的巨大位移。 在触发攻击时根据当前攻击类型加载对应的配置。伤害区域与衰减结合多个插槽的检测可以实现简单的伤害区域。例如被剑尖插槽的射线命中造成100%伤害被剑刃中部插槽命中造成80%伤害。这只需要为每个插槽配置一个伤害系数即可。防御与碰撞检测为敌人的盾牌或格挡武器也设置碰撞或射线检测。当我的攻击射线命中对方的“盾牌”插槽时触发“被格挡”事件播放格挡火花、音效并可能反弹或削减伤害。调试可视化在开发阶段可以将每条检测射线在游戏中画出来使用Draw Debug Line节点并持续显示bIsAttacking状态。这是调试攻击判定范围、时机问题最直观的方法没有之一。5. 常见问题排查与实战心得即使逻辑正确在实际操作中还是会遇到各种“诡异”的问题。这里记录几个我踩过的坑和解决方案。5.1 问题速查表问题现象可能原因排查与解决方案攻击完全无法命中1. 射线检测通道未设置正确。2. 插槽位置获取错误本地空间 vs 世界空间。3.bIsAttacking状态从未变为 True。1. 检查项目设置中的碰撞通道确保“Weapon”通道与敌人网格体的碰撞响应设置为“Overlap”或“Block”。在射线检测节点中确认Trace Channel选择正确。2. 使用Get Socket Location节点时确保Context是正确的骨骼网格体组件并且输出的是世界位置。在Tick中打印一两个插槽的位置坐标看是否在随武器移动。3. 检查动画通知是否成功触发蓝图事件。在Notify_AttackStart事件里打印一条日志或改变一个可见的调试变量。一次攻击造成多次伤害AlreadyHitActors数组未在攻击开始时清空或清空时机不对。确保在Notify_AttackStart事件中清空该数组。如果使用连击系统要注意是在每次独立攻击开始时清空而不是在整个连击 combo 开始时清空一次。攻击判定“延迟”或“提前”动画通知放置的帧不准确。LastSocketLocations初始化错误。仔细在动画编辑器中预览将开始和结束通知精确放置在武器开始移动和结束移动的帧。确保在角色初始化和每次攻击开始前LastSocketLocations被正确初始化为当前插槽的位置而不是一个旧值或零向量。性能开销过大每帧检测的射线数量过多或检测频率过高。实施本章4.1节的优化策略。首先尝试用计时器替代Tick。其次使用控制台命令stat unit查看Game线程耗时定位瓶颈。射线在复杂场景中误命中射线击中了环境中的其他物体如墙壁、地板。在射线检测节点中仔细配置“忽略的演员Actors to Ignore”列表或使用更精确的碰撞通道如自定义的“Weapon”通道只与“Enemy”和“Destructible”通道交互。也可以使用LineTraceByProfile并配置复杂的碰撞预设Collision Preset。5.2 实战心得与技巧从简到繁不要一开始就做多插槽、多攻击类型。先用一个插槽实现最基本的、能工作的射线检测。确保动画通知、状态切换、单次伤害这些基础流程完全跑通后再增加复杂度。调试是你的最佳伙伴UE4的蓝图调试和绘制调试信息Draw Debug功能极其强大。遇到问题时不要干想把关键变量位置、状态、命中结果打印到屏幕上把射线画出来。视觉化的信息能帮你快速定位是逻辑错误、数据错误还是时机错误。手感是调出来的不是算出来的攻击判定的参数如AttackTraceLength安全长度、攻击状态的精确起止帧都需要在游戏中反复测试调整。叫上你的朋友或同事让他们盲测告诉你哪里感觉“打中了却没反应”或者“没打着却掉血了”然后根据反馈微调动画通知点和插槽位置。考虑网络同步如果你在做多人游戏所有这一切判定逻辑都必须放在服务器端Server进行。客户端只负责播放动画和特效。服务器进行射线检测确认命中后再同步伤害和受击表现给所有客户端。这能从根本上杜绝外挂和不同步问题。蓝图中的LineTraceBy...节点在服务器和客户端上都会执行务必用Has Authority或Is Server节点来包装你的核心判定逻辑。优化攻击判定是一个从“能用”到“好用”的关键步骤。它没有唯一的正确答案但核心思想是让代码逻辑去紧密贴合你的游戏设计和视觉表现。通过射线检测和动画通知的结合我们构建的系统不仅更精准也更易于调整和维护。当你的武器每一次挥砍都能带来预期的、扎实的反馈时你所投入的每一分钟调试都是值得的。