首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >UE5 跑酷系统:用 Custom Movement Mode 与 Motion Warping 解耦“检测、决策、驱动”

UE5 跑酷系统:用 Custom Movement Mode 与 Motion Warping 解耦“检测、决策、驱动”

原创
作者头像
用户12339161
发布于 2026-11-02 17:47:00
发布于 2026-11-02 17:47:00
1270
举报

仿《刺客信条》的跑酷手感,问题不在动画数量,而在同一个翻越输入,面对不同高度和深度的障碍物,如何让角色每次都停在正确的位置。纯根运动动画做不到这一点——动画的位移是固定的,障碍物却是变化的;把角色强行瞬移到目标点又会破坏物理连续性。UE5 的工程答案是三件事各司其职:Custom Movement Mode 接管长时间的特殊运动,Sphere Trace 在运行时量化障碍物几何,Motion Warping 把动画的位移“拉伸”到检测到的真实接触点。三者解耦,跑酷系统才具备可扩展性。

自定义移动组件:通过 FObjectInitializer 正确替换

跑酷中的攀爬、悬挂、墙跑属于“持续数秒以上的运动状态”,用 Launch() 或根运动源做一次性位移并不合适,正确入口是 UCharacterMovementComponent 的自定义移动模式。但替换组件的方式有且只有一种:在 ACharacter 子类构造函数的 ObjectInitializer 中重设子对象类,在 BeginPlay 里替换会让移动子系统处于不一致状态。

代码语言:javascript
复制
// ParkourCharacter.cpp —— 唯一正确的替换方式
AParkourCharacter::AParkourCharacter(const FObjectInitializer& OI)
    : Super(OI.SetDefaultSubobjectClass<UParkourMovementComponent>(
          ACharacter::CharacterMovementComponentName))
{
    ParkourMove = Cast<UParkourMovementComponent>(GetCharacterMovement());
}

// ParkourMovementComponent.cpp —— 自定义物理步的核心
void UParkourMovementComponent::PhysCustom(float DeltaTime, int32 Iterations)
{
    Super::PhysCustom(DeltaTime, Iterations);
    switch (CustomMovementMode)
    {
    case CMOVE_Climbing:  PhysClimbing(DeltaTime, Iterations); break;
    case CMOVE_WallRun:   PhysWallRun(DeltaTime, Iterations);  break;
    }
}

void UParkourMovementComponent::PhysClimbing(float DeltaTime, int32 Iterations)
{
    // 将移动输入投影到墙面切平面,而非世界空间
    const FVector ClimbInput = ProjectInputOntoSurface(GetLastInputVector());
    Velocity = ClimbInput * ClimbSpeed;
    SafeMoveUpdatedComponent(Velocity * DeltaTime, UpdatedComponent->GetComponentQuat(),
                             true, Hit);
    if (Hit.bBlockingHit) SlideAlongSurface(Velocity * DeltaTime, 1.f, Hit.Normal, Hit, true);
}

PhysClimbing 中输入必须投影到墙面切平面,否则玩家按住“前进”时角色会试图穿墙,SafeMoveUpdatedComponent 会持续触发阻塞命中,表现为卡顿或抖动。SlideAlongSurface 负责在凹凸墙面上“滑”向可通行方向,这是跑酷在复杂几何上不卡死的关键。

Sphere Trace:把障碍物翻译成决策参数

检测层的职责不是“判断能不能翻”,而是输出结构化的几何描述:障碍物的有效高度、深度、前缘位置和表面法线。单一射线容易从边缘漏检,生产级实现用球体扫描(Sphere Trace)取第一个阻挡命中,再用一次向下的线检测得到顶面边缘,二者相减即得高度。

代码语言:javascript
复制
// ParkourDetectionComponent.cpp
bool UParkourDetectionComponent::DetectObstacle(FObstacleMetrics& Out) const
{
    const FVector Start = GetOwner()->GetActorLocation();
    const FVector End   = Start + GetOwner()->GetActorForwardVector() * ForwardReach;

    FHitResult WallHit;
    // 球体半径约等于角色胶囊半径,避免从边缘缝隙漏检
    if (!GetWorld()->SweepSingleByChannel(WallHit, Start, End, FQuat::Identity,
            ECC_GameTraceChannel1, FCollisionShape::MakeSphere(TraceRadius)))
        return false;

    Out.Normal = WallHit.ImpactNormal;
    Out.Top    = TraceDownToLedge(WallHit.ImpactPoint);   // 前缘顶面点
    Out.Height = Out.Top.Z - Start.Z;
    Out.Depth  = FVector::Dist2D(WallHit.ImpactPoint, Out.Top);
    Out.LedgeLocation = WallHit.ImpactPoint + WallHit.ImpactNormal * LedgeOffset;

    return Out.Height > MinLedgeHeight && Out.Height < MaxLedgeHeight;
}

TraceRadius 的取值直接决定误检率:过大,狭窄栅栏会被当成墙体;过小,球体可能从两根装饰柱之间穿过。ECC_GameTraceChannel1 应映射到自定义的 Parkour 通道,让场景中只有标记了该通道的 Actor 参与检测,而不是让所有静态网格都成为可翻越物。

决策层消费这份 FObstacleMetrics,按高度和深度分档:低障碍(Height < 120)走 Mantle,中等高度且深度有限走 Vault,超过角色头顶则尝试 Ledge Grab。动作选择是纯函数,不直接驱动动画,这是检测与驱动解耦的核心。

Motion Warping:让动画位移对齐真实接触点

动画蒙太奇自带根运动位移,但它的终点是固定的。UMotionWarpingComponent 在运行时修改根运动轨迹,把动画中指定的“接触点”对齐到检测层输出的目标变换,动画的剩余位移由 warping 补足。蒙太奇中的 Warp 通知必须与代码里的 WarpTargetName 完全一致(大小写敏感),否则 warping 静默失效。

代码语言:javascript
复制
// ParkourMovementComponent.cpp —— 驱动翻越
void UParkourMovementComponent::ExecuteVault(const FObstacleMetrics& M, UAnimMontage* Montage)
{
    WarpComp->AddOrUpdateWarpTargetFromLocationAndRotation(
        FName("VaultTarget"),
        M.Top + FVector(0, 0, VaultZOffset),
        (-M.Normal).Rotation());          // 面朝障碍物前方

    PlayAnimMontage(Montage);
    SetMovementMode(MOVE_Custom, CMOVE_Vaulting);
}

Warp 目标的 Z 轴偏移需要按障碍物高度做缩放,而不是恒定值;否则矮栅栏上角色会“浮”起来,高箱体上又会下陷。bFollowComponent 在目标静止时保持默认 false 即可,移动平台才需要开启。

模块化收敛:把跑酷做成可插拔的 ActorComponent

跑酷系统最忌讳把检测、决策、位移全部堆进 Character 蓝图。正确的工程形态是一个自包含的 UParkourComponent:它在 TickComponent 中调用检测,用 GameplayTag 缓存当前跑酷子状态,通过委托通知动画蓝图切换 Motion Matching 的 Database 或播放蒙太奇。自定义移动组件只关心 PhysCustom 中的物理步进,检测组件只产出几何数据,动画层只消费状态和 Warp 目标。三者通过接口和事件通信,换一个角色骨骼或一套动画资源时,不需要重写运动逻辑。Motion Matching 的 Pose Search 数据库在这里扮演的是“基础步态的动态替代方案”,跑酷动作本身仍由蒙太奇加 Warping 驱动,两者分工明确。

跑酷系统的专业性,体现在每一个“看起来流畅”的动作背后,都有一组显式的几何参数和位移对齐规则在兜底。球体扫描把障碍物翻译成高度和深度,决策函数把几何翻译成动作类型,Motion Warping 把动画翻译成与真实接触点对齐的位移,自定义移动组件把这一切约束在物理步进之内。四层各管一段,调参才有可归因的抓手。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 自定义移动组件:通过 FObjectInitializer 正确替换
  • Sphere Trace:把障碍物翻译成决策参数
  • Motion Warping:让动画位移对齐真实接触点
  • 模块化收敛:把跑酷做成可插拔的 ActorComponent
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档