파쿠르의 볼트 제작입니다
2025.10.31 - [Unreal5 프로젝트 다이어리2] - Unreal - 파쿠르 잡고 올라가기
Unreal - 파쿠르 잡고 올라가기
캐릭터의 팔이 닿을수 있는 거리면 난간을 잡고 올라가기 기능을 제작하였습니다.ParkourComponent형식으로 컴포넌트 형식으로 모듈화하여 제작하였습니다 이전에 만들었던 파쿠르 컴포넌트에서라
lucodev.tistory.com
이전글과 이어집니다
캐릭터는 ParkourComponent라는 파쿠르 담당 모듈컴포넌트가 있습니다
해당 컴포넌트에서 물체를 감지하고 scv 데이터테이블의 값에 따라 볼팅의 종류가 정해집니다
먼저 볼팅을 하며 플레이어가 물체를 넘어갈것이기때문에 파쿠르 프리셋을 적용할 물체의 콜리전 프리셋을
카메라를 무시하도록 설정해주었습니다
해당 설정을 하지않는다면 파쿠르하면서 플레이어가 물체를 넘을때마다 카메라가 튀는 현상이 발생합니다

그리고 파쿠르를 할때 다른 행동예외처리를 해주어야합니다
하지만 모든 !플래그 변수로 제어한다면 코드가 너무 더러워지고 유지보수도 힘들다고 판단하여
파쿠르 State를 만들어주어 제어하였습니다
먼저 파쿠르컴포넌트 내에서 파쿠르 State를 만들어주었습니다
UENUM(BlueprintType)
enum class EParkourState : uint8
{
None UMETA(DisplayName = "None"),
ShortVault UMETA(DisplayName = "Short Vault"),
NormalVault UMETA(DisplayName = "Normal Vault"),
Climing UMETA(DisplayName = "Climing"),
Sliding UMETA(DisplayName = "Sliding"),
};
캐릭터에서 락 함수를 만들어주었습니다
.h
public:
bool CanPerformAction(FName actionName) const;
void LockAction(FName actionName, bool bLock);
private:
TMap<FName, bool> actionLocks;
.cpp
bool AMainCharacter::CanPerformAction(FName actionName) const
{
const bool* bIsLocked = actionLocks.Find(actionName);
//없거나 같으면 false를 수행한다
return !(bIsLocked && *bIsLocked);
}
void AMainCharacter::LockAction(FName actionName, bool bLock)
{
actionLocks.Add(actionName, bLock);
}
처음은 State를 None으로 초기화해주며 캐릭터를 전역변수로 잡아주었습니다
public:
EParkourState currentParkourState = EParkourState::None;
class AMainCharacter* mainChar;
해당 파쿠르를 실행하는 함수에서 State를 정의해줍니다
currentParkourState = EParkourState::NormalVault;
예외처리를 해야하는 함수에서 이와같이 작성해주었습니다
if (!CanPerformAction("Guard"))
return;
파쿠르를 실행하는 Execute 함수입니다
가드와 드로우를 예외처리해준경우입니다
void UCParkourComponent::TryParkour()
{
if (!ownerCharacter)
return;
TArray<C_ParkourCondition> conditions =
{
{ [this]() { return CanParkour_Climb(); }, [this]() { DoParkour_Climb(); } },
{ [this]() { return CanParkour_ShortVault(); }, [this]() { DoParkour_ShortVault(); } },
{ [this]() { return CanParkour_NormalVault(); }, [this]() { DoParkour_NormalVault(); } },
};
bExecuted = false;
for (auto& cond : conditions)
{
bExecuted = cond.TryExecute();
if (bExecuted)
{
mainChar->LockAction("Guard", true);
mainChar->LockAction("Draw", true);
break;
}
}
if (!bExecuted || !hitObstracle)
ownerCharacter->Jump();
}
이렇게 설계하여 파쿠르를 실행할때 LockAction("종류" 실행여부)에 따라서 해당 액션이 잠기게됩니다
파쿠르가 끝날때 노티파이로 호출할 EndParkour 함수또한 제작해주었습니다
다시 State를 None으로 또한 락을 풀어주었습니다
void UCParkourComponent::EndParkour()
{
currentParkourState = EParkourState::None;
mainChar->LockAction("guard", false);
mainChar->LockAction("Draw", false);
}
그럼 물체를 감지하고 해당 물체의 정보를 가져오겠습니다
라인트레이서에 감지된 물체의 로컬 x y z를 가져옵니다
.h
public:
AActor* hitObstracle;
private:
float obstacleHeight; //z
float obstacleWidth; //x
float obstracleDepth; //y
float obstacleTopZ;
.cpp
void UCParkourComponent::CheckTrace_Center()
{
EParkourArrowType type = EParkourArrowType::Center;
LineTrace(type);
const FHitResult& hitResult = hitResults[(int32)type];
if (!hitResult.bBlockingHit)
return;
AActor* hitActor = hitResult.GetActor();
if (!hitActor)
return;
UStaticMeshComponent* mesh = hitActor->FindComponentByClass<UStaticMeshComponent>();
if (!mesh)
return;
hitObstracle = hitActor;
FVector localExtent = mesh->GetStaticMesh()->GetBoundingBox().GetExtent();
FVector scale = mesh->GetComponentScale();
obstacleWidth = localExtent.X * 2.f * scale.X; // 로컬 X 기준
obstracleDepth = localExtent.Y * 2.f * scale.Y; // 로컬 Y 기준
obstacleHeight = localExtent.Z * 2.f * scale.Z; // 로컬 Z 기준
obstacleTopZ = hitActor->GetActorLocation().Z + (localExtent.Z * scale.Z);
}
해당 정보로 파쿠르를 감지하고 조건이 만족되면 파쿠르가 실행하도록 하였습니다
NormalVault를 수행하는 함수입니다
void UCParkourComponent::DoParkour_NormalVault()
{
if (!hitObstracle || !ownerCharacter)
return;
currentParkourState = EParkourState::NormalVault;
const FParkourDataRow* row = GetParkourData("NormalVault");
if (!row)
return;
ownerCharacter->GetCharacterMovement()->SetMovementMode(MOVE_Flying);
if (row->playMontage.Num() > 0)
{
int32 randIdx = FMath::RandRange(0, row->playMontage.Num() - 1);
UAnimMontage* selectRandomMontage = row->playMontage[randIdx];
if (selectRandomMontage)
ownerCharacter->PlayAnimMontage(selectRandomMontage, row->playRatio);
}
UStaticMeshComponent* vaultMesh = hitObstracle->FindComponentByClass<UStaticMeshComponent>();
if (!vaultMesh)
return;
FVector vaultTop = vaultMesh->GetComponentLocation() + FVector(0, 0, vaultMesh->Bounds.BoxExtent.Z);
FVector rootLoc = ownerCharacter->GetActorLocation();
FVector handLoc = ownerCharacter->GetMesh()->GetBoneLocation(TEXT("hand_r"));
float deltaZ = vaultTop.Z - handLoc.Z;
FVector approachDir = ownerCharacter->GetActorForwardVector();
FVector targetLocation = rootLoc + approachDir * 20.f;
targetLocation.Z += deltaZ;
FRotator targetRotation = approachDir.Rotation();
if (motionWarpComp)
{
motionWarpComp->AddOrUpdateWarpTargetFromLocationAndRotation(
FName("ParkourTarget"), targetLocation, targetRotation
);
}
SetParkourCollision(ownerCharacter, true);
}
해당 벡터로 워프하는 모션워핑을 적용해주고 필요한 노티파이를 적용해주었습니다

결과물

'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글
| Unreal - 상호작용(Edge Traversal) (0) | 2025.11.09 |
|---|---|
| Unreal - 파쿠르 벽차기 (0) | 2025.11.03 |
| Unreal - 파쿠르 잡고 올라가기 (0) | 2025.10.31 |
| Unreal - CSV 데이터 테이블 만들기 (0) | 2025.10.29 |
| Unreal - 파쿠르 방해물 탐색 (0) | 2025.10.29 |