Unreal 파쿠르(vault) c++
- 프로그래밍/언리얼
- 2024. 6. 11.
Unreal 파쿠르(vault) c++
이 글은 Unreal에서 파쿠르(vault)를 C++로 제가 이런식으로 구현했다 라는 것을 기록으로 남기기 위한 글입니다.
해당글에서는 언리얼의 Motion Warping을 사용해 만들었습니다.
모션워핑 활성화 및 애니메이션 구하기.
언리얼에서 Motion Warping은 캐릭터의 루트 모션이 타겟과 일치하도록 동적으로 정렬하는 기능입니다.
이 기능을 사용하기 위해서 모션워핑 플러그인을 활성화 해야되고, 애니메이션이 루트 모션이어야 합니다.
Mixamo같은데서 이런 애니메이션은 구하기 쉬우니 따로 올리지는 않겠습니다.
에디터->Edit->Plugin으로 가서 Motion Warping을 아래 사진처럼 활성화.
프로젝트이름.Build.cs로 가서 "MotionWarping"을 추가합니다.
애니메이션 설정.
Animation montage에 파쿠르 애니메이션을 추가하고, Add Notify State에서 Motion Warping을 추가해줍니다.
이런식으로 설정했습니다.
1. WarpTargetName= ValutStart
Warp Translation= true
ignore ZAxis=false
2. WarpTargetName= ValutMiddle
Warp Translation= true
ignore ZAxis=true
3. WarpTargetName= VaultEnd
Warp Translation= true
ignore ZAxis=false
파쿠르를 시작할때와 끝날때는 z축이 바뀔 수 있으므로 Ignore ZAxis를 false로 설정해주었습니다.
아래 사진에 Warp Rotation true도 있긴한데 이건 필요 없을거같네요.
Vaulting C++
모션워핑을 적용하기 위해 Vault를 사용할 캐릭터에 아래와같이 Motion Warping Component를 만들었습니다.
(변수 선언 생략)
#include "MotionWarpingComponent.h"
생성자{
MotionWarpComponent = CreateDefaultSubobject<UMotionWarpingComponent>(TEXT("MotionWarpComponent"));
}
Vault구현.
#pragma region Vault
void ATroyCharacter::Vault()
{
bIsCanWarp = false;
FVector LocTmp = GetActorLocation();
FVector LocEnd = GetActorForwardVector() * 180;
for (int32 i = 0; i < 3; i++) {
LocTmp = LocTmp + (FVector(0, 0, 30) * i);
FHitResult FirstHit;
TArray<AActor*> IgnoreActor;
IgnoreActor.Add(this);
IgnoreActor.Add(EquippedWeapon);
//UKismetSystemLibrary::SphereTraceSingle(GetWorld(), LocTmp, LocEnd+LocTmp,5.f,ETraceTypeQuery::TraceTypeQuery1,false, IgnoreActor,EDrawDebugTrace::ForDuration, Hit,true);
if (UKismetSystemLibrary::SphereTraceSingle(GetWorld(), LocTmp, LocEnd + LocTmp, 10.f, ETraceTypeQuery::TraceTypeQuery1, false, IgnoreActor, EDrawDebugTrace::None, FirstHit, true)) {
FHitResult SecondHit;
FVector Start = FirstHit.Location + GetActorForwardVector();
for (int32 j = 0; j < 5; j++) {
FVector NextLoc = GetActorForwardVector() * j * 50;
if (UKismetSystemLibrary::SphereTraceSingle(GetWorld(), Start + NextLoc + FVector(0, 0, 100), Start + NextLoc, 10.f, ETraceTypeQuery::TraceTypeQuery1, false, IgnoreActor, EDrawDebugTrace::ForDuration, SecondHit, true)) {
if (j == 0) {
VaultStartPos = SecondHit.Location;
UKismetSystemLibrary::DrawDebugSphere(GetWorld(), VaultStartPos, 10.f, 12, FColor::Purple, 3);
}
else {
bIsCanWarp = true;
if (j == 4) {
VaultLandPos = VaultMiddlePos;
VaultMotionWarping();
break;
}
//bIsCanWarp = true;
VaultMiddlePos = SecondHit.Location;
UKismetSystemLibrary::DrawDebugSphere(GetWorld(), VaultMiddlePos, 10.f, 12, FColor::Orange, 3);
}
}
else {
FHitResult HitLast;
if (UKismetSystemLibrary::LineTraceSingle(GetWorld(), GetActorForwardVector() * 80 + SecondHit.TraceStart, GetActorForwardVector() * 80 + SecondHit.TraceStart - FVector(0, 0, 1000), ETraceTypeQuery::TraceTypeQuery1, false, IgnoreActor, EDrawDebugTrace::ForDuration, HitLast, true, FLinearColor::Blue)) {
VaultLandPos = HitLast.Location;
UKismetSystemLibrary::DrawDebugSphere(GetWorld(), VaultLandPos, 10.f, 12, FColor::Purple, 3);
VaultMotionWarping();
break;
}
}
}break;
}
}
}
void ATroyCharacter::VaultMotionWarping()
{
if (bIsCanWarp/* || Z가 어느정도 범위 안에 있는지.*/) {
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Flying);
SetActorEnableCollision(false);
FMotionWarpingTarget VaultStartMotionWarping;
VaultStartMotionWarping.Name = FName("VaultStart");
VaultStartMotionWarping.Location = VaultStartPos;
VaultStartMotionWarping.Rotation = GetActorRotation();
MotionWarpComponent->AddOrUpdateWarpTarget(VaultStartMotionWarping);
FMotionWarpingTarget VaultMiddleMotionWarping;
VaultMiddleMotionWarping.Name = FName("VaultMiddle");
VaultMiddleMotionWarping.Location = VaultMiddlePos;
VaultMiddleMotionWarping.Rotation = GetActorRotation();
MotionWarpComponent->AddOrUpdateWarpTarget(VaultMiddleMotionWarping);
FMotionWarpingTarget VaultLandMotionWarping;
VaultLandMotionWarping.Name = FName("VaultEnd");
VaultLandMotionWarping.Location = VaultLandPos;
VaultLandMotionWarping.Rotation = GetActorRotation();
MotionWarpComponent->AddOrUpdateWarpTarget(VaultLandMotionWarping);
AnimInstanceRef = GetMesh()->GetAnimInstance();
if (AnimInstanceRef && VaultMontage) {
AnimInstanceRef->Montage_Play(VaultMontage);
/*몽타주 애셋으로부터 인스턴스가 새롭게 생성되서 매번 실행할 때 델리게이트를 설정해주고 있습니다.*/
FOnMontageEnded OnMontageEnded;
OnMontageEnded.BindUFunction(this, FName("EndVault"));
AnimInstanceRef->Montage_SetEndDelegate(OnMontageEnded, VaultMontage);
//AnimInstance->Montage_JumpToSection(SectionName, Montage);
}
}
}
void ATroyCharacter::EndVault()
{
UE_LOG(LogTemp, Display, TEXT("EndVault"));
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
SetActorEnableCollision(true);
bIsCanWarp = false;
}
#pragma endregion
대충 아래 사진처럼 SphereTrace를 사용해 앞에 장애물이 있는지 검사하고, 장애물이 있으면 뛰어넘을 수 있는지 검사해서 장애물을 뛰어넘거나 올라가거나 둘중 하나를 실행하는 코드입니다.
물론 간단하게 구현한만큼 버그가 조금 있긴합니다.(벽을 뚫고 간다거나...)