首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >UE5蓝图仿刺客信条跑酷:检测、决策与Motion Warping对齐的代码实现

UE5蓝图仿刺客信条跑酷:检测、决策与Motion Warping对齐的代码实现

原创
作者头像
用户12339161
发布于 2026-11-04 16:41:55
发布于 2026-11-04 16:41:55
1250
举报

仿刺客信条跑酷的失败画面几乎总是同一种:角色“飘”上障碍、手掌穿墙、落地滑步。根源不在动画资源,而在检测阶段没有算准落点与法线,对齐阶段没有把根骨骼位移对到那个落点上。蓝图能完成这件事,但需要把检测封装成唯一事实来源,把动作选择变成阈值表,把位置修正交给Motion Warping,而不是用SetActorLocation硬拉。

检测层:Sphere Trace产出可复用的结构体

单根Line Trace只能回答“前方有东西”,回答不了“顶部在哪、顶面多宽、墙面朝哪边”。检测必须用球体扫掠加二次射线,输出一个供后续所有环节消费的结构体。以下函数可作为UBlueprintFunctionLibrary暴露给蓝图,蓝图侧只需传入前向向量与半径:

代码语言:javascript
复制
USTRUCT(BlueprintType)
struct FTraversalResult {
    GENERATED_BODY()
    UPROPERTY(BlueprintReadOnly) bool bValid = false;
    UPROPERTY(BlueprintReadOnly) float WallHeight = 0.f;
    UPROPERTY(BlueprintReadOnly) float StandDepth = 0.f;
    UPROPERTY(BlueprintReadOnly) FVector TargetLocation = FVector::ZeroVector;
    UPROPERTY(BlueprintReadOnly) FRotator TargetRotation = FRotator::ZeroRotator;
};

UFUNCTION(BlueprintCallable, Category="Parkour")
static FTraversalResult ComputeTraversalHit(AActor* Avatar, FVector Forward, float Radius) {
    FTraversalResult Out;
    if (!Avatar) return Out;
    UWorld* World = Avatar->GetWorld();
    FVector Origin = Avatar->GetActorLocation();
    FCollisionQueryParams P; P.AddIgnoredActor(Avatar);

    TArray<FHitResult> Hits;
    FVector Start = Origin + Forward * 50.f;
    World->SweepMultiByChannel(Hits, Start, Start + Forward * 150.f, FQuat::Identity,
        ECC_GameTraceChannel1, FCollisionShape::MakeSphere(Radius), P); // Parkour 通道
    if (Hits.IsEmpty()) return Out;

    const FHitResult* Highest = &Hits[0];
    for (const FHitResult& H : Hits)
        if (H.ImpactPoint.Z > Highest->ImpactPoint.Z) Highest = &H;

    FVector WallNormal = Highest->ImpactNormal;
    FHitResult TopHit;
    FVector TopStart = Highest->ImpactPoint + WallNormal * 2.f + FVector(0, 0, 200.f);
    World->LineTraceSingleByChannel(TopHit, TopStart, TopStart - FVector(0, 0, 400.f),
        ECC_GameTraceChannel1, P);
    Out.WallHeight = Origin.Z - TopHit.ImpactPoint.Z;

    FHitResult DepthHit;
    FVector DepthEnd = TopHit.ImpactPoint - WallNormal * 120.f;
    World->SweepSingleByChannel(DepthHit, TopHit.ImpactPoint, DepthEnd, FQuat::Identity,
        ECC_GameTraceChannel1, FCollisionShape::MakeSphere(15.f), P);
    Out.StandDepth = FVector::Dist(TopHit.ImpactPoint, DepthHit.ImpactPoint);
    Out.TargetLocation = DepthHit.ImpactPoint;
    Out.TargetRotation = (-WallNormal).Rotation();
    Out.bValid = true;
    return Out;
}

StandDepth小于胶囊半径时判定为栏杆,只走ThinVault;深度足够且WallHeight超过角色半高时才进入Mantle。碰撞通道必须独立于WorldStatic,否则路灯柱也会触发翻越检测。

决策层:阈值表在DataAsset里,不在动画蓝图中

检测输出后,动作类型由WallHeight与StandDepth查表决定。蓝图侧只需一个Switch on EParkourType,阈值和蒙太奇映射放在DataAsset中,设计师调参数不必改连线:

代码语言:javascript
复制
// 蓝图事件图表(文本化)
OnParkourInputPressed:
  Result = ComputeTraversalHit(Self, GetActorForwardVector(), 40)
  Branch (Result.bValid)
    True:
      Type = ParkourData->ResolveType(Result.WallHeight, Result.StandDepth)
      PlayParkourAction(Type, Result)
    False: Do Nothing

关键纪律是:决策层只选蒙太奇、写Warp Target,绝不直接SetActorLocation。位置修改由根运动和Motion Warping完成,否则检测落点与角色实际位置会分叉。

对齐层:Motion Warping写入前后缘目标

Motion Warping的机制是在蒙太奇中预设命名目标,运行时把检测到的落点和朝向写入,引擎在播放期间自动扭动根骨骼,让手和脚精确到达边缘。写入必须用AddOrUpdateWarpTargetFromLocationAndRotation,一帧内同时更新位置与旋转,避免分别设置时的短暂不同步:

代码语言:javascript
复制
void AMyCharacter::PlayParkourAction(EParkourType Type, const FTraversalResult& Hit) {
    UAnimMontage* Montage = ParkourData->GetMontage(Type);
    if (!Montage || !MotionWarping) return;

    MotionWarping->AddOrUpdateWarpTargetFromLocationAndRotation(
        TEXT("FrontLedge"), Hit.TargetLocation, Hit.TargetRotation);

    // 栏杆/箱体需要后缘目标,让蒙太奇后段身体对齐
    if (Type == EParkourType::ThinVault || Type == EParkourType::MidVault) {
        FVector BackLedge = Hit.TargetLocation + Hit.TargetRotation.Vector() * 80.f;
        MotionWarping->AddOrUpdateWarpTargetFromLocationAndRotation(
            TEXT("BackLedge"), BackLedge, Hit.TargetRotation);
    }
    PlayAnimMontage(Montage);
}

Motion Warping负责播放期间的落点校正,Two Bone IK负责播放之后的持续贴合。角色攀住边缘横移时,手掌必须跟随连续边缘线,纯蒙太奇做不到,需要在动画蓝图中用Two Bone IK把手的IK目标钉在墙面法线偏移的位置上。两者分工不能混:只挂Warping不做IK,横移会滑手;只做IK不挂Warping,初始落点已经歪了。

性能:检测不进Tick

ComputeTraversalHit不应挂在Event Tick上。用一个0.15秒的定时器做粗扫,只有短距离球体追踪命中Parkour通道时才触发完整检测,离开障碍后自动停止查询。所有输出汇入同一个FTraversalResult,动作决策、Warp Target写入和调试绘制消费同一份数据——检测只有一个事实来源,动画和IK才不会各自猜一个位置。

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

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

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档