bool UAO_TraversalComponent::RunCapsuleTrace(const FVector& StartLocation, const FVector& EndLocation, float Radius,
float HalfHeight, FHitResult& OutHit, FColor DebugHitColor, FColor DebugTraceColor)
{
UWorld* World = GetWorld();
if (!World)
{
return false;
}
FCollisionShape TraceShape = FCollisionShape::MakeCapsule(Radius, HalfHeight);
FCollisionQueryParams CollisionResponseParams(FName(TEXT("TraversalTrace")), false, GetOwner());
bool bHit = World->SweepSingleByChannel(
OutHit,
StartLocation,
EndLocation,
FQuat::Identity,
ECC_Visibility,
TraceShape,
CollisionResponseParams
);
if (DrawDebugLevel >= 2)
{
FQuat CapsuleRot = FQuat::Identity;
if (bHit)
{
// 충돌 지점까지의 스윕 캡슐
DrawDebugCapsule(World, OutHit.Location, HalfHeight, Radius, CapsuleRot, DebugHitColor, false, DrawDebugDuration);
// 충돌이 시작된 곳에서 충돌 지점까지의 선
DrawDebugLine(World, StartLocation, OutHit.Location, DebugHitColor, false, DrawDebugDuration);
}
else
{
// 전체 스윕 경로의 끝 지점 캡슐
DrawDebugCapsule(World, EndLocation, HalfHeight, Radius, CapsuleRot, DebugTraceColor, false, DrawDebugDuration);
// 전체 트레이스 선 (Start -> End)
DrawDebugLine(World, StartLocation, EndLocation, DebugTraceColor, false, DrawDebugDuration);
}
}
return bHit;
}
(1) 파쿠르 가능한 오브젝트 찾고, 앞뒤 ledge 정보를 받아오기
// Perform a trace in the actor's forward direction to find a traversable object
FHitResult HitResult;
FVector TraceStart = ActorLocation + TraversalInput.TraceOriginOffset;
FVector TraceEnd = TraceStart + TraversalInput.TraceForwardDirection * TraversalInput.TraceForwardDistance + TraversalInput.TraceEndOffset;
RunCapsuleTrace(
TraceStart,
TraceEnd,
TraversalInput.TraceRadius,
TraversalInput.TraceHalfHeight,
HitResult,
FColor::Black,
FColor::Green);
// Get the front and back ledge transforms from the traversable component
UAO_TraversableComponent* TraversableComponent = Cast<UAO_TraversableComponent>(
HitResult.GetActor()->GetComponentByClass(UAO_TraversableComponent::StaticClass()));
TraversalResult.HitComponent = HitResult.GetComponent();
TraversableComponent->GetLedgeTransforms(HitResult.ImpactPoint, ActorLocation, TraversalResult);

(2) 앞 ledge 위에 파쿠르를 실행할 수 있는 공간이 있는지 확인하기
// Perform a trace from the actors location up to the front ledge location to determine if there is room for the actor to move up to it
FVector RoomCheckFrontLedgeLocation = TraversalResult.FrontLedgeLocation + TraversalResult.FrontLedgeNormal * (CapsuleRadius + 2.0f);
RoomCheckFrontLedgeLocation.Z += CapsuleHalfHeight + 2.0f;
FHitResult RoomCheckFrontHitResult;
RunCapsuleTrace(
ActorLocation,
RoomCheckFrontLedgeLocation,
CapsuleRadius,
CapsuleHalfHeight,
RoomCheckFrontHitResult,
FColor::Red,
FColor::Green);
// Save the height of the obstacle
TraversalResult.ObstacleHeight = abs((ActorLocation.Z - CapsuleHalfHeight) - TraversalResult.FrontLedgeLocation.Z);

(3) 앞 ledge 위부터 뒤 ledge 위까지 공간이 있는지 확인하고 그에 따라 장애물 너비 저장
-> 공간이 있다면 뒤 바닥 위치까지 저장
// Perform a trace across the top of the obstacle from the front ledge to the back ledge to see if theres room for the actor to move across it.
FVector RoomCheckBackLedgeLocation = TraversalResult.BackLedgeLocation + TraversalResult.BackLedgeNormal * (CapsuleRadius + 2.0f);
RoomCheckBackLedgeLocation.Z += CapsuleHalfHeight + 2.0f;
FHitResult RoomCheckBackHitResult;
bool bRoomCheckBackHit = RunCapsuleTrace(
RoomCheckFrontLedgeLocation,
RoomCheckBackLedgeLocation,
CapsuleRadius,
CapsuleHalfHeight,
RoomCheckBackHitResult,
FColor::Red,
FColor::Green);
if (bRoomCheckBackHit)
{
TraversalResult.ObstacleDepth = (RoomCheckBackHitResult.ImpactPoint - TraversalResult.FrontLedgeLocation).Size2D();
TraversalResult.bHasBackLedge = false;
}
else
{
TraversalResult.ObstacleDepth = (TraversalResult.FrontLedgeLocation - TraversalResult.BackLedgeLocation).Size2D();
// Trace downward from the back ledge location to find the floor
FVector BackFloorTrace = TraversalResult.BackLedgeLocation + TraversalResult.BackLedgeNormal * (CapsuleRadius + 2.0f);
BackFloorTrace.Z -= 50.f;
FHitResult BackFloorHitResult;
RunCapsuleTrace(
RoomCheckBackLedgeLocation,
BackFloorTrace,
CapsuleRadius,
CapsuleHalfHeight,
BackFloorHitResult,
FColor::Red,
FColor::Green);
if (BackFloorHitResult.bBlockingHit)
{
TraversalResult.bHasBackFloor = true;
TraversalResult.BackFloorLocation = BackFloorHitResult.ImpactPoint;
TraversalResult.BackLedgeHeight = abs(BackFloorHitResult.ImpactPoint.Z - TraversalResult.BackLedgeLocation.Z);
}
else
{
TraversalResult.bHasBackFloor = false;
}
}

(4) 계산한 데이터를 기반으로 Chooser Table을 통해 가장 알맞은 몽타주를 선택하는 과정 구현
// Evaluate a chooser to select all montages that match the conditions of the traversal check.
const FInstancedStruct ResultInstances = UChooserFunctionLibrary::MakeEvaluateChooser(AnimChooserTable);
FChooserEvaluationContext EvaluationContext;
FInstancedStruct InputStruct;
InputStruct.InitializeAs<FTraversalChooserInput>();
FTraversalChooserInput& InputData = InputStruct.GetMutable<FTraversalChooserInput>();
FTraversalChooserInput ChooserInput = FTraversalChooserInput(
TraversalResult.ActionType,
TraversalResult.bHasFrontLedge,
TraversalResult.bHasBackLedge,
TraversalResult.bHasBackFloor,
TraversalResult.ObstacleHeight,
TraversalResult.ObstacleDepth,
TraversalResult.BackLedgeHeight,
CharacterMovement->MovementMode,
PlayerCharacter->Gait,
CharacterMovement->Velocity.Size2D());
InputData = ChooserInput;
EvaluationContext.Params.Add(InputStruct);
FInstancedStruct OutputStruct;
OutputStruct.InitializeAs<FTraversalChooserOutput>();
FTraversalChooserOutput& OutputData = OutputStruct.GetMutable<FTraversalChooserOutput>();
EvaluationContext.Params.Add(OutputStruct);
TArray<UObject*> EvaluateObjects = UChooserFunctionLibrary::EvaluateObjectChooserBaseMulti(
EvaluationContext, ResultInstances, UAnimMontage::StaticClass());
(5) Chooser Table을 통해 선택한 몽타주들 중에서 가장 현재 포즈와 연결되는 몽타주를 선택하는 과정
UPoseSearchLibrary::MotionMatch()를 통해 AnimMontage 선택 및 해당 몽타주에 대한 정보 저장// Perform a Motion Match on all the montages that were chosen by the chooser to find the best result.
// This match will elect the best montage AND the best entry frame (start time) based on the distance to the ledge, and the current characters pose.
FPoseSearchContinuingProperties ContinuingProperties;
FPoseSearchFutureProperties FutureProperties;
FPoseSearchBlueprintResult MotionMatchResult;
UPoseSearchLibrary::MotionMatch(
Character->GetMesh()->GetAnimInstance(),
EvaluateObjects,
FName(TEXT("PoseHistory")),
ContinuingProperties,
FutureProperties,
MotionMatchResult);
UAnimMontage* SelectedAnim = Cast<UAnimMontage>(MotionMatchResult.SelectedAnim);
TraversalResult.ChosenMontage = SelectedAnim;
TraversalResult.StartTime = MotionMatchResult.SelectedTime;
TraversalResult.PlayRate = MotionMatchResult.WantedPlayRate;
FPoseSearchContinuingProperties ContinuingProperties;
FPoseSearchFutureProperties FutureProperties;
FPoseSearchBlueprintResult MotionMatchResult;
UPoseSearchLibrary::MotionMatch(
Character->GetMesh()->GetAnimInstance(),
EvaluateObjects,
FName(TEXT("PoseHistory")),
ContinuingProperties,
FutureProperties,
MotionMatchResult);
UObject* ResultObject = MotionMatchResult.SelectedAnim;
UPoseSearchLibrary::MotionMatch()를 통해서 알맞은 애님 몽타주를 골라내는 과정에서 MotionMatchResult.SelectedAnim 값이 정상적으로 반환되지 않고, 항상 nullptr이 나오게 됨// PoseSearchLibrary.cpp
// this function is looking for UPoseSearchDatabase(s) to search for the input AssetToSearch
static bool AddToSearch(FAssetsToSearchPerDatabaseMap& AssetsToSearchPerDatabaseMap, const UObject* AssetToSearch)
{
bool bAsyncBuildIndexInProgress = false;
if (const UAnimSequenceBase* SequenceBase = Cast<const UAnimSequenceBase>(AssetToSearch))
{
for (const FAnimNotifyEvent& NotifyEvent : SequenceBase->Notifies)
{
if (const UAnimNotifyState_PoseSearchBranchIn* PoseSearchBranchIn = Cast<UAnimNotifyState_PoseSearchBranchIn>(NotifyEvent.NotifyStateClass))
{
if (!PoseSearchBranchIn->Database)
{
UE_LOG(LogPoseSearch, Error, TEXT("improperly setup UAnimNotifyState_PoseSearchBranchIn with null Database in %s"), *SequenceBase->GetName());
continue;
}
// we just skip indexing databases to keep the experience as smooth as possible
if (AddToSearchForDatabase(AssetsToSearchPerDatabaseMap, SequenceBase, PoseSearchBranchIn->Database, true))
{
bAsyncBuildIndexInProgress = true;
}
}
}
}
...
}
UAnimNotifyState_PoseSearchBranchIn 노티파이 스테이트가 필수적으로 필요한 것을 알 수 있다.