캐릭터의 팔이 닿을수 있는 거리면 난간을 잡고 올라가기 기능을 제작하였습니다.
ParkourComponent형식으로 컴포넌트 형식으로 모듈화하여 제작하였습니다
이전에 만들었던 파쿠르 컴포넌트에서
라인트레이서를 쏴 해당 물체의 높이와 폭을 구해줍니다
파쿠르 컴포넌트를 제작한뒤 라인트레이서를 쏴 구하는 값들은 이와 같습니다
.h
AActor* hitObstracle;
float obstacleHeight; //z
float obstacleWidth; //x
float obstracleDepth; //y
float obstacleTopZ;
.cpp
UStaticMeshComponent* mesh = hitActor->FindComponentByClass<UStaticMeshComponent>();
if (!mesh)
return;
hitObstracle = hitActor;
FVector extent = mesh->Bounds.BoxExtent;
obstacleHeight = extent.Z * 2.f; // 높이 = Z 반폭 * 2 -> 전체높이
obstacleWidth = extent.X * 2.f; // 폭 = X 반폭 * 2 -> x축 가로 기준 폭
obstacleTopZ = hitActor->GetActorLocation().Z + extent.Z;
obstracleDepth = extent.Y * 2.f;
캐릭터에 파쿠르 추적 화살표 타입을 달아주었습니다
.h
//파쿠르 추적 화살표 타입
UENUM(BlueprintType)
enum class EParkourArrowType : uint8
{
Center = 0, Up, Down, Left, Right, Max,
};
.cpp
void AMainCharacter::InitParkourSystem()
{
arrowGroup = CreateDefaultSubobject<USceneComponent>(TEXT("ArrowGroup"));
arrowGroup->SetupAttachment(GetCapsuleComponent());
for (int32 i = 0; i < (int32)EParkourArrowType::Max; i++)
{
FString name = StaticEnum<EParkourArrowType>()->GetNameStringByIndex(i);
FName compName(*name);
arrows[i] = CreateDefaultSubobject<UArrowComponent>(compName);
arrows[i]->SetupAttachment(arrowGroup);
switch ((EParkourArrowType)i)
{
case EParkourArrowType::Center:
arrows[i]->ArrowColor = FColor::Red;
break;
case EParkourArrowType::Up:
arrows[i]->ArrowColor = FColor::Green;
arrows[i]->SetRelativeLocation(FVector(0, 0, 30));
break;
case EParkourArrowType::Down:
arrows[i]->ArrowColor = FColor::Blue;
arrows[i]->SetRelativeLocation(FVector(0, 0, -80));
break;
case EParkourArrowType::Left:
arrows[i]->ArrowColor = FColor::Magenta;
arrows[i]->SetRelativeLocation(FVector(0, -30, 0));
break;
case EParkourArrowType::Right:
arrows[i]->ArrowColor = FColor::Magenta;
arrows[i]->SetRelativeLocation(FVector(0, 30, 0));
break;
}
}
parkourComp = CreateDefaultSubobject<UCParkourComponent>(TEXT("CParkourComponent"));
}
그리고 모션워핑 플러그인을 설치해줍니다

플레이어 윗 방향에서 쏘는 라인트레이서이며
캐릭터가 손으로 잡을 그랩 포인트를 계산합니다 장애물의 윗면 혹은 모서리의 좌표입니다
또한 장애물과의 높이 차이를 계산합니다
(캐릭터가 올라가야하는 수직 높이)
.h
AActor* hitObstracle_Climb;
FVector climbGrabPoint;
float distanceToObstacle;
float zDiff;
.cpp
void UCParkourComponent::CheckTrace_Up()
{
LineTrace(EParkourArrowType::Up);
const FHitResult& hit = hitResults[(int32)EParkourArrowType::Up];
if (!hit.bBlockingHit)
{
hitObstracle_Climb = nullptr;
climbGrabPoint = FVector::ZeroVector;
return;
}
hitObstracle_Climb = hit.GetActor();
climbGrabPoint = hit.ImpactPoint;
zDiff = obstacleTopZ - ownerCharacter->GetActorLocation().Z;
distanceToObstacle = FVector::Dist(ownerCharacter->GetActorLocation(), climbGrabPoint);
}
그리고 저번에 scv 연동을 하여 가지고있는 데이터테이블값을 연동시키는 함수를 만들어주었습니다
2025.10.29 - [Unreal5 프로젝트 다이어리2] - Unreal - CSV 데이터 테이블 만들기
Unreal - CSV 데이터 테이블 만들기
모든 에셋, 내부변수값을 직접 하드코딩하게되면 매번 할때마다 작업이 오래걸린다그래서 필자가 선택한방식은 scv파일형식으로 외부로 저장하여 엑셀로 추가 및 삭제 그리고 수정을 할수있게
lucodev.tistory.com
.h
public:
void InitializeParkourDT();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DataTable")
UDataTable* parkDataTable;
const FParkourDataRow* GetParkourData(FName rowName);
private:
TMap<FName, FParkourDataRow> parkourDataMap;
.cpp
void UCParkourComponent::InitializeParkourDT()
{
if (!parkDataTable)
return;
static const FString Context(TEXT("ParkourContext"));
TArray<FName> rowNames = parkDataTable->GetRowNames();
for (const FName& rowName : rowNames)
{
if (FParkourDataRow* row = parkDataTable->FindRow<FParkourDataRow>(rowName, Context))
{
parkourDataMap.Add(rowName, *row);
}
}
}
const FParkourDataRow* UCParkourComponent::GetParkourData(FName rowName)
{
if (const FParkourDataRow* findRow = parkourDataMap.Find(rowName))
return findRow;
return nullptr;
}
scv파일과 연동된 데이터테이블의 값들은 이와 같습니다

파쿠르의 분기를 결정하고 실행할 함수를 만들어주었습니다 해당 함수로 파쿠르를 실행합니다
일단 만들어진게 잡고올라가기밖에없으니 그쪽만 추가해주었습니다
void UCParkourComponent::TryParkour()
{
struct FParkourCondition
{
TFunction<bool()> canExecute;
TFunction<void()> execute;
};
TArray<FParkourCondition> conditions =
{
{ [this]() { return CanParkour_Climb(); }, [this]() { DoParkour_Climb(); } },
//{ [this]() { return CanParkour_Grip(); }, [this]() { DoParkour_Grip(); } },
//{ [this]() { return CanParkour_Slide(); }, [this]() { DoParkour_Slide(); } },
//{ [this]() { return CanParkour_ShortVault(); }, [this]() { DoParkour_ShortVault(); } },
//{ [this]() { return CanParkour_NormalVault(); }, [this]() { DoParkour_NormalVault(); } },
};
bool bParkourExecuted = false;
for (auto& cond : conditions)
{
if (cond.canExecute())
{
cond.execute();
bParkourExecuted = true;
break;
}
}
// 조건 만족하는 파쿠르 동작이 없으면 점프
if (!bParkourExecuted && ownerCharacter)
{
ownerCharacter->Jump();
}
}
climb의 조건을 체크해주는 함수입니다
내부조건과 외부조건인 데이터테이블 값과 연동하였습니다
bool UCParkourComponent::CanParkour_Climb()
{
if (climbGrabPoint.IsZero() || !hitObstracle_Climb)
return false;
if (!ownerCharacter->GetCharacterMovement()->IsFalling())
return false;
const FParkourDataRow* row = GetParkourData("Climb");
if (!row)
return false;
//기본값 zDiff 55이상 77이하
if (zDiff >= row->minHeight && zDiff <= row->maxHeight &&
obstacleWidth >= row->minWidth && obstacleWidth <= row->maxWidth)
return true;
if (distanceToObstacle >= 77)
return false;
return false;
}
난간의 위치를 구하고 잡아야할 그랩 위치를 계산하며 모션워핑컴포넌트에 ParkourTarget 이라는 이름으로 보내줄 함수를 제작하였습니다
void UCParkourComponent::DoParkour_Climb()
{
const FParkourDataRow* row = GetParkourData("Climb");
if (!row || !ownerCharacter || !hitObstracle_Climb)
return;
ownerCharacter->GetCharacterMovement()->SetMovementMode(MOVE_Flying);
if (row->PlayMontage)
ownerCharacter->PlayAnimMontage(row->PlayMontage, row->playRatio);
FVector handLoc = ownerCharacter->GetMesh()->GetBoneLocation(TEXT("hand_r"));
const FHitResult& upHit = hitResults[(int32)EParkourArrowType::Up];
if (!upHit.bBlockingHit)
return;
FVector grabPoint = upHit.ImpactPoint;
grabPoint.Z += 5.f;
climbGrabPoint = grabPoint;
FVector currentRoot = ownerCharacter->GetActorLocation();
FVector targetLocation;
targetLocation.X = grabPoint.X; // X는 난간 기준
targetLocation.Y = grabPoint.Y; // Y는 난간 기준
targetLocation.Z = currentRoot.Z + (grabPoint.Z - handLoc.Z); // Z는 손 높이 기준 보정
// 벽 바라보도록
FRotator targetRotation = (-upHit.ImpactNormal).Rotation();
if (motionWarpComp)
{
motionWarpComp->AddOrUpdateWarpTargetFromLocationAndRotation(
FName("ParkourTarget"),targetLocation,targetRotation
);
}
}
모션워핑컴 노티파이를 추가해주고 타깃 이름을 설정해줍니다
또한 노티파이를 하나 추가하여 Climb할때 실행한 Flying모드 변경을 EndParkourClimb노티파이를 제작하여 다시 Walking으로 되돌려주었습니다 (제가 설계한 모션매칭 시스템과 연동하기위해 루트슬롯으로 변동해주었습니다)
(애니메이션은 반드시 루트 모션이어야합니다)

결과


'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글
| Unreal - 파쿠르 벽차기 (0) | 2025.11.03 |
|---|---|
| Unreal - 파쿠르 볼트(Vault) (0) | 2025.11.02 |
| Unreal - CSV 데이터 테이블 만들기 (0) | 2025.10.29 |
| Unreal - 파쿠르 방해물 탐색 (0) | 2025.10.29 |
| Unreal - 적 타게팅 (0) | 2025.10.29 |