按下 WASD 任一方向键 + LeftShift 触发空中闪现,角色沿相机方向瞬移 5000 单位距离,带溶解特效、径向模糊屏幕效果和四向闪避蒙太奇。蓝图版在 BP_Nyx 的 InputEventGraph,C++ 版在 ChangliCharacter::DoAirDash。
— 系统一句话总结
一、执行流程:蓝图 vs C++ 对照
两个版本走相同的逻辑链,C++ 把蓝图的 Timeline 驱动换成了 Tick 帧内驱动。
[输入] WASD + LeftShift(空中态)
│
├─ [守卫] 闪现中不重复触发(AirDashRemaining > 0)
├─ [守卫] 打断当前普攻(IsAttacking → InterruptAttack / ResetAttackM)
│
├─ [方向] W→前 / S→后 / A→左 / D→右
│ GetDashDirection() → Controller.GetControlRotation().Vector()(含 Pitch,不压平 Z)
│ GetDashRightVector() → Controller.GetControlRotation() + Yaw+90°
│
├─ [安全落点] CalculateSafeDashLocation(Dir, DashLerpDistance=5000)
│ 胶囊体 Sweep:碰到 StaticMesh/Landscape 墙 → 回退到最近阻挡点
│ 植被放行:IsVegetationHit 判断关键词,树/草不挡
│
├─ [特效 1] SpawnSystemAttached(DashDissolveFX) → 角色根组件
├─ [特效 2] ApplyAirDashScreenEffect() → EF_PP_RadialBlur_Dodge 挂相机 WeightedBlendables
├─ [特效 3] FlashCamLag_Disable() → SpringArm CameraLagSpeed = 9999
├─ [特效 4] WriteRadialBlurMPC(0.8f) → MPC_RadialBlur BlurIntensity 峰值
├─ [溶解] StartDissolveOut() → 角色溶解消失
│
├─ [位移] VLerp(DashLerpStart, DashLerpTarget, α) 0.3s 插值
│ α = curTime / DashLerpDuration(0.3)
│ SetActorLocation(Sweep=true)
│
└─ [动画] PlayMontage(PickDashMontageByInput(), 1.0f)
Nyx_Dodge_Montage / Dodge_Back / Dodge_Left / Dodge_Right
二、穿模修复:V1 → V5 五次迭代
闪现穿模是本功能最棘手的问题,先后经历 5 个版本修复。根因集中在 CalculateSafeDashLocation 的碰撞通道选择、Sweep 结果排序和 Z 轴处理上。
V1
蓝图独占版
全蓝图实现,无 C++ 安全计算 → 直接闪现,经常穿墙穿树
初版 · 穿模严重
V2
ECC_Visibility 通道
C++ LineTraceMultiByChannel(ECC_Visibility) → 漏检 StaticMeshActor 墙
ECC_Visibility 很多物体不响应
V3
ECC_WorldStatic + 植被放行
通道改为 WorldStatic,加 IsVegetationHit 关键词匹配("Vegetation"/"Grass"/"Tree")
墙能挡住了,但 Z 轴处理有问题
V4
Landscape 识别修复
类名判断 "Landscape" → "Landscape"(Contains 不匹配,导致地面不识别)
修类名匹配条件
V5
bypass Sweep + Z 钳位
蓝图端 bSweep=false 穿树;C++ 只改 Z 轴到地面位置,XY 保持方向一致
最终定案
关键修复:C++ CalculateSafeDashLocation 函数体(V5 定案)
// TransformCharacter.cpp · CalculateSafeDashLocation
FVector ATransformCharacter::CalculateSafeDashLocation(const FVector& Dir, float MaxDist)
{
FVector Start = GetActorLocation();
FVector End = Start + Dir.GetSafeNormal() * MaxDist;
// 路径 Sweep(胶囊体)
FHitResult Hit;
FCollisionQueryParams Params(SCENE_QUERY_STAT(DashSafe), false, this);
Params.AddIgnoredActor(this);
GetWorld()->SweepSingleByChannel(Hit, Start, End, FQuat::Identity,
ECC_WorldStatic, FCollisionShape::MakeCapsule(CapsuleRadius, CapsuleHalfHeight), Params);
FVector SafeTarget = Hit.bBlockingHit ? Hit.Location : End;
// Z 轴单独钳位到地面(Landscape + WorldStatic)
FHitResult GroundHit;
GetWorld()->LineTraceSingleByChannel(GroundHit,
SafeTarget + FVector(0, 0, 100), SafeTarget + FVector(0, 0, -500),
ECC_WorldStatic, Params);
if (GroundHit.bBlockingHit) {
// 只改 Z,XY 保持原方向(修复"闪现方向与镜头不对齐")
SafeTarget.Z = GroundHit.Location.Z + CapsuleHalfHeight;
}
return SafeTarget;
}
关键修复:蓝图端 bSweep 设为 false
蓝图 CF51(SetActorLocation 节点)→ bSweep = false,让蓝图位移也穿树(树靠 C++ 层 Sweep 只挡墙,不挡植被)。
三、特效清单(精确到资产路径 + 挂载点)
| 类型 | 资产 | 路径 | 挂载/注入点 | 时序 |
|---|---|---|---|---|
| Niagara 粒子 | NS_DodgeNyx | /Game/Test_02/VFX/FlashVFX/ | 角色根组件 SnapToTarget | 闪现开头立即 Spawn |
| Niagara 拖尾 | NS_FlyTrail | /Game/Effect/ | 蓝图 Niagara 组件 FlyParticle | 悬空飞行态 Activate |
| PP 径向模糊 | EF_PP_RadialBlur_Dodge | /Game/Effect/MaterialInstance/PostProcess/ | 相机 WeightedBlendables 权重 1.0 | 0.15s 后权重→0,0.4s 移除 |
| MPC 径向 | MPC_RadialBlur | 全局 MPC | BlurIntensity=0.8,WarpFlowIntensity=0.8 | 峰值 → 跟随 Timeline 衰减 |
| SpringArm | CameraLag | 相机组件内建 | CameraLagSpeed=9999,RotationLagSpeed=9999 | 闪现全程禁用 Lag,结束恢复 |
四、关键变量速查表
| 变量名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| DashLerpDistance | float | 5000 | 最大闪现距离(单位) |
| DashLerpDuration | float | 0.3 | 闪现插值时长(秒) |
| DashLerpStart | FVector | — | 闪现起点位置 |
| DashLerpTarget | FVector | — | 安全落点位置 |
| AirDashRemaining | float | 0 | 闪现剩余时间(>0 表示闪现中) |
| bIsAttacking | bool | false | 普攻进行中(闪现需先打断) |
| DashDissolveFX | TObjectPtr\ | 软路径加载 | 闪现溶解特效资产 |
| DashMontageForward/Back/Left/Right | UPROPERTY FSoftObjectPath | — | 四向闪避蒙太奇 |
五、踩坑速查
- OnDashInput execute 引脚未接线:蓝图 Custom Event 节点的 then 引脚空着 → CalculateSafeDashLocation 根本不执行,SafeTarget 恒为原点。修:把 then 接到 TimelineDodge 节点。
- 蓝图 SetActorLocation → bSweep:蓝图端默认 bSweep=true,会和 C++ Sweep 重复判定导致穿树失败。修:蓝图端设 bSweep=false,让 C++ 层独掌碰撞。
- StartDissolveOut 时序:角色溶解消失 Niagara 挂在根组件,闪现到位前如果 Niagara 提前 Deactivate → 角色还没到位就显形。修:溶解在 DoAirDash 开头就 StartDissolveOut,Tick 末尾检查 DashLerpCurTime > 0.8 * Duration 时 StartDissolveIn。
- EXEPTION_ACCESS_VIOLATION:退出变身时先销毁 Niagara 组件再 Activate → 空指针崩溃。修:Destroy 前先 IsValid() 检查。
- 方向不对齐镜头:VLerp(Start, SafeTarget, α) 里 SafeTarget 被 Sweep 改了 XY → 闪现方向偏离镜头。修:Sweep 只改 Z 轴钳位,XY 保持 Dir 方向。