2026.8.16纯蓝图FPS学习笔记
UE5 纯蓝图 FPS 实战笔记:命中判定、AI 追击与带冲量的布娃娃死亡
一个用 UE 5.8 纯蓝图(零 C++)完成的 FPS 练习项目。本文梳理三个核心系统的实现思路:武器命中判定、Whisper AI 追击,以及带冲量的布娃娃死亡效果。文中的蓝图节点名均可在编辑器里直接搜索复现。
一、先看整体架构
项目逻辑全部放在 Content/Code/ 下,类关系非常简单:
1 | BP_FPSPlayer (Character) ──BI_Fire接口──▶ BP_WeaponBase (Actor) ──继承──▶ BP_VIRTUS |
三个系统围绕”一颗子弹”串联起来:扣下扳机 → 接口调用开火 → 射线检测 → 物理材质分流 → 命中敌人 → 传递骨骼名与子弹方向 → 伤害计算 → 受击蒙太奇 / 布娃娃死亡。理解了这条链路,整个项目的设计思路就清楚了。
二、命中判定:Hitscan + 物理材质分流
1. 射线检测基础
开火逻辑在武器基类 BP_WeaponBase 里,核心就是一条 LineTraceSingle:
- 使用自定义 Trace 通道
TraceTypeQuery1,和默认的 Visibility/Camera 通道隔离开,避免被其他系统的射线干扰; - 打开
bTraceComplex,对复杂碰撞体做精确检测,保证打在骨骼网格的轮廓上而不是胶囊体上; - 用
BreakHitResult拆出所有需要的信息:ImpactPoint、ImpactNormal、HitBoneName、Distance、PhysMat、HitActor。
其中子弹方向直接由数学算出来:Normal(TraceEnd - TraceStart),归一化后传给敌人——这一步很关键,后面布娃娃死亡要复用这个方向,后面会讲。
2. 用物理材质做表面判定,而不是一堆 Cast
命中后要决定”打到了什么、该播什么特效”。常见做法是 Cast To 各种 Actor 类,但这里换了一个更优雅的思路:用 PhysicalMaterial 的表面类型(Physical Surface)做分流。
项目里定义了四种物理材质:
| 物理材质 | 表面类型 | 用途 |
|---|---|---|
PM_Dirt / PM_Metal / PM_Wood |
SurfaceType1~3 | 地形与场景物体 |
PM_Enemy |
SurfaceType4 | 敌人身体专用标记 |
射线命中后拿到 PhysMat,一个 SwitchEnum 按表面类型分流:
- 水泥/泥土/金属/木头 → 对应的弹着 VFX(
FX_SquibConcrete/Dirt/Metal/Wood)+ 撞击音效 Cue + 弹孔贴花(BP_BulletDecal,按表面切换贴花材质); PM_Enemy→ 播放血液 Niagara(Impact_Blood),进入敌人伤害流程。
为什么这个设计值得记一笔:物理材质是碰撞查询的免费副产品——BreakHitResult 里的 PhysMat 不需要任何额外查询或 Cast,零开销就拿到了”这是什么表面”。美术在网格上设置物理材质后,逻辑零改动即可生效;以后加新表面类型也只是加一个枚举分支和一套表现资产。相比”把场景里每种物体都 Cast 一遍”,这套方案更干净、更好扩展。
3. 命中敌人后的传递链
命中 BP_Whisper 时,武器做三件事:
Cast To BP_Whisper;- 调用敌人的两个自定义事件:
Impact Bone(Name)——命中骨骼名;Bullet Direction(Vector)——上面算出的子弹方向;
ApplyDamage(BaseDamage)结算伤害。
一次 Trace 的所有信息就这样”喂”给了敌人,伤害计算、受击表现、死亡表现共用同一份命中数据,不会有二次查询。
4. 射速与后坐力
- 射速:
SetTimerDelegate计时器循环触发开火(间隔约 0.1s,即 600rpm 档),IsFiring标志控制启停; - 后坐力:
AddControllerPitchInput/AddControllerYawInput配合RandomFloatInRange在随机范围内抖动摇杆,而不是固定偏移——连续点射时枪口跳动更自然; - 开火表现由玩家/武器双视角蒙太奇 + 动画通知
MCX-VIRTUS_Fire同步,逻辑时机跟着动画走。
三、Whisper AI:没有行为树的纯蓝图追击
1. 异步 MoveTo + 委托循环
Whisper 继承 Character,但没有使用 AIController 类,也没有行为树,整个 AI 就是一张事件图。
核心节点是 AIBlueprintHelperLibrary 的 CreateMoveToProxyObject——一个异步 AIMoveTo 代理节点,用法和时序节点类似:
- 输入:目标 Actor(玩家)、
AcceptanceRadius(约 50); - 输出:OnSuccess / OnFail 两个委托,绑到自定义事件
MovementResult; - 在回调里重新发起下一次 MoveTo,就形成了”追到/追丢了都继续追”的循环。
配合 LessEqual 距离判定(玩家进入约 5000 范围内才发起追击)和追击时的 MaxWalkSpeed 调整,一个”巡逻发呆 → 发现玩家 → 加速追击”的简单 AI 就有了。对于单敌人练习场景,这个方案比搭一套行为树轻得多,而且事件流一目了然、容易调试。
2. 状态枚举:谁打你,谁告诉你演什么
BP_Whisper 内定义了状态枚举:E_Chase / E_Death / E_HitMontage / E_Whisper_Damage。
有意思的是,这个枚举不是 AI 自己维护的——武器命中时直接设置敌人的状态。射击系统本来就掌握着最准确的命中信息(部位、伤害量、致死与否),由它来”导演”敌人的反应,AI 侧只负责响应,职责划分很清晰。
四、受击反馈:一个 Map 驱动的蒙太奇系统
1. 骨骼名 → 蒙太奇映射
受击表现的核心是一个蓝图变量:
1 | HitAnimMontages : Map<Name, AnimMontage> |
- 躯干骨骼(
spine_01/spine_02/spine_03)→Anim_Whisper_Taking_Bodyshot_Montage; - 其他部位(头、颈、四肢)→
Anim_Whisper_Taking_Headshot_Montage。
收到 Impact Bone 后 Map Find 查表,PlayAnimMontage 直接播放,还支持 StartSectionName 从指定段起播。用数据映射替代 if-else 硬编码,以后加部位、加表现只需要改这个 Map。
2. 伤害倍率
同一份骨骼名还参与伤害计算:EqualEqual(Name) 比较命中骨骼,身体与头部分走不同倍率(示例值约 2x / 3x),Multiply 后从 Health 里扣除。也就是说:骨骼名同时驱动了伤害数值和受击动画,两个系统共享一个输入。
3. 动画蓝图的分层结构
ABP_Whisper 的设计也值得一提:
- Locomotion 状态机:只有
Idle/Walk一个状态,由Walk Speed参数驱动 1D 混合空间BS1D_Whisper(Idle1 ↔ Walk1,三次插值)——移动逻辑保持极简; - DefaultSlot 蒙太奇槽位 +
LayeredBoneBlend:受击/死亡蒙太奇通过槽位叠加在基础移动动画之上,互不打断。
“基础运动永远在播,一次性表现走蒙太奇槽位”是角色动画的经典分层思路,这里体现得很标准。
五、带冲量的布娃娃死亡
这是整个项目最”爽”的部分:敌人中弹后不是播放死亡动画,而是沿子弹方向被物理打飞。
死亡流程在 BP_Whisper 内,关键四步:
- 血量归零判定(
Health - Damage ≤ 0); DisableMovement——立刻停掉 AI 移动。这一步不能省:追击逻辑还在跑的话,CharacterMovement 会和物理模拟抢控制权,布娃娃会被”拽”着走;SetAllBodiesSimulatePhysics(true)——全身骨骼切到物理模拟,进入布娃娃;AddImpulse施加冲量,三个参数是精髓:BoneName= 命中骨骼(来自武器传的Impact Bone)——冲量只作用在被打中的部位;Impulse= 子弹方向 × 冲量系数(约 5000,由Conv_DoubleToVector标量广播 + 向量乘法实现)——方向与枪线一致;bVelChange = true——用”速度变化”模式而非”力”模式。力模式受质量影响,布娃娃各部位质量不同,同样的力打在不同部位表现差异巨大;速度变化模式直接指定速度增量,保证爆头和打腿的”手感”一致。
最后把状态切到 E_Death,收尾。
效果:爆头向后仰、打腿前扑、侧面命中横飞,方向永远和枪线一致——玩家的每一次射击都能”看到”自己的命中结果,这是手感正反馈的重要来源。
配套资产 Phy_Whisper_PhysicsAsset 用了 ConeConstraint(圆锥约束)连接骨骼,限制关节旋转自由度,让布娃娃的动作像”人体”而不是”面条”。
六、让系统跑起来的其他细节
1. BI_Fire 蓝图接口
玩家不持有武器具体类引用,统一通过 BI_Fire 接口的 StartWeaponFire / StopFire 调用武器。换武器 = 换一个实现该接口的 Actor,玩家逻辑零改动。这是蓝图里最值得养成的习惯之一:用接口而非具体类通信。
2. 武器继承体系
BP_WeaponBase承载所有通用逻辑:命中判定、弹药、弹壳、贴花、表面分流;BP_VIRTUS子类只做数据配置:BaseDamage、FireRate、MagSize、TotalAmmo,以及自己的蒙太奇/音效引用;- 武器本身是模块化部件拼装:Weapon / Magazine(弹匣)/ Sight(机瞄)/ Scope(瞄具)/ Stock(枪托)/ Barrel(枪管)分部件挂载。
新枪 = 新子类 + 换部件网格,通用逻辑一行不改。
3. 弹药与换弹
- 三变量模型:
MagSize(弹匣容量)/CurrentAmmo(当前弹匣)/TotalAmmo(备弹),开火DecrementInt,打空有专属干火音效Dead_Trigger_Hand-Guns; - 换弹蒙太奇上放置自定义动画通知
AN_Reload:动画播到”填弹”那一帧时回调武器蓝图执行ReloadAmmo——逻辑时机由动画驱动,而不是用延迟或计时器猜; PlayMontageCallbackProxy的五个回调(OnBlendOut / OnCompleted / OnInterrupted / OnNotifyBegin / OnNotifyEnd)管理换弹状态机,换弹途中开火被禁用。
4. 弹壳系统 BP_Casing
小物件也有讲究:
ProjectileMovementComponent:初速 + 重力 +bShouldBounce落地弹跳;RotatingMovementComponent+ 随机转速:每颗弹壳的落地姿态都不同;OnComponentHit(开启bNotifyRigidBodyCollision)落地播放弹壳音效,DoOnce防重复触发;InitialLifeSpan到时自动销毁,不留垃圾。
5. Enhanced Input 与瞄准手感
IMC_FPSInput映射上下文 + 6 个 Input Action(Move / Look / Fire / Aim / Reload / Run),IA_Fire 绑定 Triggered/Completed 支持按住连发;- 瞄准手感三件套:
FInterpTo平滑 FOV 缩放(默认 75)、瞄准时灵敏度联动(UpdateScopeSensitivity)、走/跑速度平滑切换(400 ↔ 600)——全部插值过渡,没有生硬的瞬变; E_PlayerState/E_WeaponState枚举驱动玩家与武器两套动画状态机。
七、总结
回头看这个项目,几条值得记住的设计原则:
- 一次 Trace 喂饱所有系统——命中位置、骨骼、方向、物理材质一次性拿到,伤害、表现、死亡共用一份数据;
- 数据驱动替代硬编码——表面类型枚举、骨骼名→蒙太奇映射表,加内容不写逻辑;
- 动画通知驱动逻辑时机——开火、换弹填弹的时机都跟着动画走;
- 接口解耦 + 继承分层——玩家不认识具体武器,武器基类不认识具体枪型;
- 表现优先的小细节(布娃娃冲量、弹壳随机旋转、插值手感)才是 FPS 手感的来源。
已知的迭代方向:攻击动画资源(Anim_Whisper_Attack1/2/3)已经备好但尚未接入 AI 流程,下一步可以让 Whisper 在贴近玩家时发起近战攻击;死亡蒙太奇与布娃娃的衔接也可以打磨得更顺滑。





