这次我们来看一个UE5.7战斗系统开发的实战项目。核心不是讲一堆虚幻引擎的复杂概念,而是直接上手,在UE5.7里搭建一套可玩、可扩展的战斗框架,重点解决连击、命中判定和伤害反馈这三个核心体验问题。对于想做动作游戏、ARPG或者想深入理解UE5动画和物理交互的开发者来说,这套流程可以直接复用。
本文会带你从零开始,在UE5.7中搭建一个基础的第三人称战斗场景。我们会重点实现:1)基于动画蒙太奇和状态机的连击系统;2)使用碰撞盒(Box Collision)和射线检测(Line Trace)进行精准的命中判定;3)集成伤害数值显示、受击动画、屏幕震动和音效的完整伤害反馈链。整个过程会避开华而不实的功能,聚焦于每个环节的蓝图实现、参数调试和性能观察,确保你在自己的项目里能快速跑通。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 引擎版本 | Unreal Engine 5.7 (部分逻辑兼容5.3+) |
| 核心功能 | 连击动画衔接、近战攻击命中判定、多维度伤害反馈 |
| 关键技术 | 动画蒙太奇、动画蓝图状态机、碰撞盒/射线检测、UMG UI、相机震动 |
| 硬件门槛 | 主要依赖CPU和GPU进行实时渲染与物理计算,建议GTX 1060 / RTX 2060及以上显卡,确保流畅运行编辑器 |
| 项目复杂度 | 中等,涉及动画系统、蓝图编程和UI集成,适合有一定UE基础或想系统学习战斗逻辑的开发者 |
| 输出目标 | 获得一个可运行、可测试、模块清晰的战斗原型,便于后续扩展为更复杂的技能系统 |
2. 适用场景与使用边界
这套战斗系统实战主要适用于以下几类开发者:
- 独立游戏开发者/小型团队:需要快速验证战斗手感,搭建原型。
- UE学习进阶者:已经熟悉基础操作,想深入动画系统、事件交互和游戏逻辑的整合。
- 动作/ARPG项目:作为近战战斗模块的基础,后续可扩展技能、格挡、弹反等机制。
它能解决的核心问题是:如何将动画、输入、物理碰撞和游戏反馈有机地串联起来,形成一个手感扎实的战斗循环。你将学会如何让角色“打得出连招”、“砍得中敌人”以及“打得有反应”。
需要注意的是,本实战侧重于系统实现框架与核心逻辑,而非顶级3A游戏的画面表现或极其复杂的动画融合技术。它不包含:
- 高级动画技巧:如运动匹配(Motion Matching)、复杂的骨骼IK调整。
- 网络同步:本文为单机实现,多人游戏需要额外处理客户端预测、服务器校验等。
- 完整的AI系统:敌人行为仅为简单的受击反应,复杂的AI决策树、行为树需要另行开发。
- 商业化素材:使用的动画、音效、模型仅为演示目的,实际项目需确保使用拥有合法授权的素材。
3. 环境准备与前置条件
在开始搭建之前,请确保你的开发环境已就绪。
1. 引擎与项目
- Unreal Engine 5.7:从Epic Games启动器安装或源码编译。5.7版本在动画通知、编辑器稳定性方面有较好支持。
- 新建项目:建议选择“第三人称游戏(Third Person)”模板,并启用“初学者内容包(Starter Content)”。项目名称如
CombatSystemDemo。
2. 内容准备
- 动画资源:你需要一套基础的攻击动画。可以在虚幻商城搜索免费资源(如“Infinity Blade: Warriors”的动画包),或使用项目模板自带的动画进行改编。至少准备:Idle(待机)、Attack1(攻击1)、Attack2(攻击2)、Attack3(攻击3)、GetHit(受击)。
- 音效与UI素材:准备刀剑挥砍音效、命中音效、UI提示音,以及显示伤害数字的字体和材质。初学者内容包中可找到一些基础资源。
- 敌人模型:一个简单的角色或静态网格体(如“第三人称”模板中的目标靶子)即可。
3. 知识预备
- 基本熟悉UE编辑器界面:内容浏览器、世界大纲、细节面板。
- 了解蓝图(Blueprint)的基本操作:创建变量、函数、事件图表。
- 对动画蓝图(Animation Blueprint)和状态机有初步概念。
4. 项目结构与核心蓝图创建
清晰的架构是后续高效开发的基础。我们先建立核心蓝图。
4.1 创建文件夹结构在内容浏览器中,创建如下文件夹以管理资产:
Content/ ├── Blueprints/ │ ├── Characters/ │ ├── Enemies/ │ └── UI/ ├── Animations/ │ ├── Player/ │ └── Enemy/ ├── AnimationBlueprints/ ├── UI/ ├── Sounds/ └── Materials/4.2 创建玩家角色蓝图
- 在
Blueprints/Characters/文件夹中右键,选择“蓝图类”。 - 父类选择
Character,命名为BP_CombatPlayer。 - 双击打开,在“组件”面板中,确保已有
SkeletalMesh、SpringArm、Camera等组件。 - 我们需要添加攻击碰撞盒。在组件面板点击“添加组件”,搜索并添加
Box Collision,重命名为AttackCollisionBox。 - 将
AttackCollisionBox附着到武器骨骼或手部骨骼上(例如hand_r)。在细节面板中调整其位置和大小,使其覆盖武器的攻击范围。关键一步:在碰撞预设(Collision Presets)中,将其设置为NoCollision或自定义一个如OverlapAll的预设,避免与角色自身碰撞。同时,在“碰撞”分类下,取消勾选“生成重叠事件(Generate Overlap Events)”的默认状态,我们将通过蓝图在攻击时动态启用它。
4.3 创建敌人蓝图
- 在
Blueprints/Enemies/中创建蓝图类,父类选择Character或Actor(简单演示可用Actor),命名为BP_SimpleEnemy。 - 为其添加一个
StaticMesh组件作为外观,并添加一个Box Collision组件作为受击盒,重命名为HitCollisionBox。
4.4 创建动画蒙太奇动画蒙太奇是驱动单次攻击动画的载体,并能嵌入通知(Notifies)来触发事件(如开启碰撞盒)。
- 在
Animations/Player/文件夹中,右键你的攻击动画序列(如Attack1),选择“创建” -> “创建动画蒙太奇”。 - 命名为
AM_AttackCombo。 - 双击打开蒙太奇编辑器。将动画序列拖入轨道。在“通知轨道(Notifies Track)”上,右键添加通知。我们需要两个关键通知:
EnableHitBox:在武器挥出即将接触敌人的时刻添加,用于开启攻击碰撞盒。DisableHitBox:在攻击动画收招或结束时添加,用于关闭攻击碰撞盒,避免一帧内多次触发。
- 我们需要先创建这两个自定义通知。在内容浏览器中右键,选择“动画” -> “动画通知”,分别创建
AnimNotify_EnableHitBox和AnimNotify_DisableHitBox。然后回到蒙太奇编辑器,将它们从资源列表拖到通知轨道上。
5. 连击系统实现:输入、状态与动画衔接
连击系统的核心是管理玩家的输入队列、当前连击段数以及控制动画的播放逻辑。
5.1 设置玩家输入与变量打开BP_CombatPlayer的事件图表。
- 定义变量:
ComboCount(整数):当前连击数,初始为0。bCanAttack(布尔):是否可以触发下一次攻击,初始为True。用于防止动画播放期间重复输入。AttackMontage(动画蒙太奇对象引用):存储我们创建的AM_AttackCombo。
- 绑定输入:在“项目设置 -> 输入”中,添加一个“动作映射(Action Mappings)”,命名为
Attack,绑定到鼠标左键或键盘按键(如J)。 - 处理输入事件:在
BP_CombatPlayer的事件图表中,右键搜索“输入动作 Attack”,添加该事件。 - 编写逻辑:
- 检查
bCanAttack是否为 True。 - 如果为真,设置
bCanAttack为 False(防止连点)。 - 根据
ComboCount的值,通过Play Montage节点播放AttackMontage。同时,需要设置蒙太奇的播放速率和起始位置,通常连击的后续攻击会更快。 - 在播放蒙太奇后,增加
ComboCount。例如,如果ComboCount大于等于3(假设是三连击),则重置为0。 - 关键:需要监听蒙太奇播放完成或中断的事件。使用
OnCompleted和OnInterrupted输出引脚,在这些事件中重置bCanAttack为 True,并考虑是否重置ComboCount。通常,如果连击成功衔接,ComboCount不重置;如果攻击动作被打断或长时间未输入,则需要重置。这可以通过一个定时器来实现“连击窗口期”。
- 检查
5.2 在动画蓝图中管理连击状态玩家角色的动画蓝图(通常为ABP_Player)是控制播放哪一段攻击动画的最佳位置。
- 打开角色的动画蓝图,进入动画图表(AnimGraph)。
- 创建一个状态机,命名为
AttackStateMachine。 - 双击进入状态机,创建多个状态:
Idle、Attack1、Attack2、Attack3。 - 转换规则:
- 从任何状态到
Attack1:当ComboCount等于1时。 - 从
Attack1到Attack2:需要一个条件,例如检测到在Attack1动画的特定帧(通过时间或通知)有新的攻击输入,并且ComboCount变为2。 - 以此类推。
- 每个攻击状态结束时,如果没有满足进入下一段的条件,则转换回
Idle,并重置ComboCount。
- 从任何状态到
- 将
AttackStateMachine的输出连接到最终动画姿势的输出节点。同时,需要将待机、移动等状态也通过一个主状态机或混合节点与之混合。
更实用的简化方案:对于快速原型,可以不在动画蓝图做复杂状态判断,而是在角色蓝图中,根据ComboCount直接播放AM_AttackCombo蒙太奇的不同片段(通过Play Montage节点的Start Section参数指定节名称)。在蒙太奇中为每一段攻击设置不同的节(Section),如Attack1,Attack2,Attack3。
6. 命中判定实现:碰撞盒与射线检测
命中判定决定了“这一刀有没有砍中”。我们采用“碰撞盒检测”作为主要方法,并简要介绍“射线检测”作为补充或替代方案。
6.1 碰撞盒检测流程
- 创建自定义动画通知:之前创建的
AnimNotify_EnableHitBox需要有其逻辑。双击打开它的蓝图。 - 在事件
Received_Notify中,我们需要获取到拥有此骨骼网格体的角色(Owner Actor),并转换为BP_CombatPlayer。 - 转换成功后,调用玩家角色上的一个自定义函数,例如
EnableAttackCollision()。 - 在
BP_CombatPlayer中创建函数EnableAttackCollision:- 此函数中,设置
AttackCollisionBox组件的“生成重叠事件”为 True。 - 同时,绑定该碰撞盒的
OnComponentBeginOverlap事件。
- 此函数中,设置
- 处理重叠事件:当
AttackCollisionBox开始与某物体重叠时:- 检查重叠的另一个组件(Other Comp)的所有者(Other Actor)是否是敌人(例如,检查其类是否是
BP_SimpleEnemy或实现了某个“可受击”接口)。 - 如果是敌人,并且当前敌人不在一个“已命中列表”中(防止单次攻击对同一敌人造成多次伤害),则执行命中逻辑。
- 命中逻辑包括:调用敌人身上的
ApplyDamage函数(或直接广播伤害事件)、播放命中特效、音效等。 - 将命中的敌人加入“已命中列表”。
- 检查重叠的另一个组件(Other Comp)的所有者(Other Actor)是否是敌人(例如,检查其类是否是
- 关闭碰撞盒:在
AnimNotify_DisableHitBox的通知中,调用玩家角色的DisableAttackCollision函数。此函数将“生成重叠事件”设为 False,并清空“已命中列表”,为下一次攻击做准备。
6.2 射线检测作为备选对于需要更精准、如剑气、刺击等判定,可以使用射线检测。
- 在攻击动画的关键帧,通过动画通知触发一个执行射线检测的函数。
- 函数中,从武器尖端或手部骨骼位置,向攻击方向发射一条射线(
LineTraceByChannel)。 - 检测通道(Trace Channel)可以设置为自定义的
Weapon通道,并为敌人的受击盒设置对该通道的响应。 - 如果射线命中敌人,则执行与碰撞盒检测相似的命中逻辑。 射线检测的优点是无须持续存在的碰撞体,性能开销可控,且判定精准。缺点是无法很好地处理持续范围的挥砍。
7. 伤害反馈系统:让战斗“有感觉”
命中之后,需要让玩家和敌人都感受到“打中了”。这是一个多感官的反馈系统。
7.1 敌人受击反馈
- 伤害处理:在
BP_SimpleEnemy中,创建一个浮点变量Health,初始值为100.0。创建一个函数ApplyDamage(float DamageAmount)。 - 在该函数中,减少
Health。如果Health <= 0,播放死亡动画或销毁Actor。 - 受击动画:在敌人动画蓝图中,添加一个
HitReaction状态。当接收到伤害时(可以通过事件分发器或直接调用),强制切换到受击动画蒙太奇播放一次,然后返回原状态。 - 受击特效与音效:在
ApplyDamage函数中,在敌人位置生成一个粒子特效(如火花)并播放受击音效。
7.2 玩家屏幕反馈
- 相机震动:命中敌人时,在玩家控制器或相机管理器上调用
PlayCameraShake节点。你需要先创建一个相机震动资产(Camera Shake)。在内容浏览器中右键选择“创建高级资源” -> “相机” -> “相机震动基类”,选择LegacyCameraShake或CameraShakeBase的子类,并调整震动的强度、频率和持续时间。 - 命中停顿(Hit Pause):为了增强打击感,可以在命中瞬间让游戏时间短暂变慢。使用
Set Global Time Dilation节点,将全局时间膨胀设置为一个小于1的值(如0.1),持续0.1秒,然后恢复为1.0。注意,这会影响到所有基于时间的逻辑。
7.3 UI伤害数字
- 创建伤害数字控件:在
UI/文件夹创建“用户控件(Widget Blueprint)”,命名为WBP_DamageNumber。 - 设计控件:添加一个文本块(Text Block),绑定一个浮点变量
DamageValue。可以添加动画,让文字从命中点向上飘移并渐隐。 - 生成伤害数字:在命中判定逻辑中(玩家蓝图的
OnComponentBeginOverlap事件里),创建这个控件到屏幕。- 使用
Create Widget节点创建WBP_DamageNumber。 - 将伤害值传递给它。
- 使用
Add to Viewport添加到视口。 - 为了将世界空间中的命中位置转换到屏幕空间,需要使用
Project World Location to Screen节点,将得到的屏幕位置设置给控件的初始位置。
- 使用
8. 集成测试与效果验证
现在,我们将所有模块串联起来,在编辑器中测试。
8.1 场景搭建
- 将
BP_CombatPlayer拖入场景。 - 放置几个
BP_SimpleEnemy在玩家周围。 - 确保玩家的
AttackCollisionBox在视口中可见(在细节面板勾选“在游戏中隐藏”为否,测试后可关闭),以便观察其激活范围。
8.2 功能测试流程
- 启动游戏:点击运行。控制角色移动至敌人附近。
- 测试基础攻击:按下攻击键(如鼠标左键)。观察:
- 角色是否播放第一段攻击动画。
- 在动画的特定帧,
AttackCollisionBox是否可视化地出现(如果设置为可见)。 - 当碰撞盒与敌人重叠时,敌人是否播放受击动画、血量是否减少、伤害数字是否弹出。
- 屏幕是否有轻微震动。
- 测试连击:在第一次攻击动画的收招阶段(连击窗口内),再次按下攻击键。
- 观察角色是否流畅地播放第二段、第三段攻击动画。
- 每次攻击的命中判定是否独立生效。
- 测试判定失效:在攻击动画的碰撞盒未激活阶段攻击,或攻击空气,确认不会触发命中反馈。
- 测试反馈组合:命中时,确认伤害数字、屏幕震动、受击音效和特效是否同时触发,营造出足够的打击感。
8.3 性能观察在测试时,打开“统计(Stat)”窗口(快捷键~后输入stat unit或stat fps)。
- 帧率(FPS):在连续攻击和多个敌人同时受击时,帧率应保持稳定。如果骤降,检查粒子特效是否过于复杂,或每帧内执行的逻辑(如大量重叠事件处理)是否过重。
- Draw Calls:伤害数字UI如果大量生成且未做池化管理,可能会增加绘制调用。对于原型阶段,数量不多时可接受。
9. 常见问题与排查方法
在实现过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 按下攻击键无任何反应 | 1. 输入未正确绑定。 2. bCanAttack初始为False或未重置。3. 动画蒙太奇引用为空。 | 1. 检查项目设置中的输入绑定。 2. 在角色蓝图中打印 bCanAttack的值。3. 检查 AttackMontage变量是否已赋值。 | 1. 重新绑定输入。 2. 确保在动画播放完成/中断事件中重置 bCanAttack。3. 在角色蓝图的细节面板或事件开始运行时为变量赋值。 |
| 攻击动画播放但无法命中敌人 | 1. 碰撞盒未正确附着或大小/位置不对。 2. 碰撞盒的碰撞预设未正确设置,或与敌人的碰撞通道无交互。 3. 动画通知未触发,或触发后未启用碰撞事件。 | 1. 在编辑器中运行游戏,并显示碰撞盒(\键),观察其位置。2. 检查 AttackCollisionBox和敌人HitCollisionBox的碰撞预设和响应。3. 在 EnableHitBox通知和角色函数中添加打印字符串,确认逻辑被执行。 | 1. 调整碰撞盒的附着骨骼和变换。 2. 确保攻击盒与受击盒的碰撞通道能产生重叠事件(如都设置为 OverlapAllDynamic)。3. 调试动画通知的执行流程。 |
| 单次攻击对同一敌人造成多次伤害 | “已命中列表”未生效或清空时机不对。碰撞盒在单次激活期间与敌人持续重叠,触发多次BeginOverlap。 | 在重叠事件中打印调试信息,查看每次命中时敌人是否已在列表中。检查DisableHitBox通知是否被调用并清空列表。 | 确保在BeginOverlap事件中先检查列表。确保在攻击结束时(DisableHitBox)或每次攻击开始时清空旧列表。 |
| 伤害数字显示位置错误 | 世界坐标到屏幕坐标的转换错误。控件锚点设置问题。 | 打印Project World Location to Screen节点输出的屏幕坐标值。检查伤害数字控件的对齐和锚点。 | 确保传入的世界坐标是命中点的坐标。将控件的对齐方式设置为(0.5, 0.5),锚点设置为中心。 |
| 连击无法衔接,总是从第一段开始 | ComboCount在每次攻击后都被错误重置。连击窗口期太短,输入未被检测到。 | 打印每次攻击前后的ComboCount值。检查重置ComboCount的逻辑(如在状态机回Idle时)。 | 确保只有在连击超时或受击时才重置ComboCount。可以延长基于定时器的连击窗口期。 |
| 屏幕震动效果不明显或没有 | 1. 相机震动资产未创建或强度太低。 2. PlayCameraShake调用的对象不对(应在玩家控制器上调用)。3. 震动在短时间内被多次调用覆盖。 | 1. 检查相机震动资产的参数。 2. 确认调用 PlayCameraShake节点的Target是玩家控制器。3. 添加调试信息确认函数被调用。 | 1. 调整震动资产的Oscillation Duration和Rotational/Oscillation Amplitude。2. 使用 Get Player Controller节点获取控制器。 |
10. 优化与扩展方向
当基础系统运行稳定后,可以考虑以下优化和扩展,使其更健壮、更专业。
10.1 系统优化
- 对象池管理:频繁生成的伤害数字、命中特效,应使用对象池进行复用,避免频繁的创建和垃圾回收开销。
- 事件分发器解耦:将“命中事件”、“受伤事件”使用事件分发器(Event Dispatcher)进行广播。这样,UI、音效、相机震动等系统只需监听这些事件,而不需要直接引用玩家或敌人蓝图,降低耦合度。
- 数据驱动:将攻击力、连击节奏、碰撞盒参数、伤害反馈强度等配置到数据表(Data Table)或结构体(Struct)中,便于策划调整和平衡。
10.2 功能扩展
- 技能系统:在连击基础上,引入魔力值(Mana)或技能冷却,实现重击、跳跃攻击、范围技能等。每种技能可以配置独立的动画蒙太奇、碰撞体和伤害计算。
- 防御与格挡:为敌人添加攻击行为,为玩家添加格挡、闪避机制。这需要新的输入绑定、动画状态和判定逻辑(如格挡成功时减伤、触发弹反)。
- 锁定系统:实现目标锁定功能,使攻击和镜头自动朝向锁定的敌人。
- 更复杂的AI:为敌人赋予巡逻、追击、攻击决策等行为树逻辑。
- 存档与属性成长:集成游戏实例(Game Instance)或存档系统,管理玩家的永久属性(如力量、敏捷),影响基础伤害。
10.3 网络同步基础思路(如需)若要改为多人游戏,核心改动点:
- 角色移动与动画:使用UE内置的
Character Movement Component进行网络同步。 - 攻击与命中判定:必须在服务器端进行权威判定。客户端发起攻击请求,服务器执行碰撞检测、计算伤害,然后广播结果给所有客户端。
- RPC调用:使用
ServerRPC 让服务器执行关键逻辑(如Server_Attack),使用MulticastRPC 让所有客户端播放非权威性的视觉效果(如受击动画、特效)。 - 预测与调和:对于高频操作(如移动),可能需要客户端预测和服务器调和,但这会大幅增加复杂度。对于原型,可以先采用完全的服务器权威模式。
这套基于UE5.7的战斗系统实战框架,已经涵盖了从输入到渲染反馈的完整链条。它的价值在于模块清晰、逻辑直接,你可以快速看到每一拳、每一刀背后的运行机制。建议你先严格按照步骤实现基础版本,确保连击、命中、反馈三个核心环节都能跑通。之后,再选择最感兴趣的一两个扩展方向进行深化,比如先做一个酷炫的技能特效,或者为敌人添加简单的巡逻AI。动手修改和调试的过程,才是理解UE5游戏逻辑最有效的途径。