Unreal 파쿠르(vault) c++

250x250

Unreal 파쿠르(vault) c++

이 글은 Unreal에서 파쿠르(vault)를 C++로 제가 이런식으로 구현했다 라는 것을 기록으로 남기기 위한 글입니다.

해당글에서는 언리얼의 Motion Warping을 사용해 만들었습니다.

모션워핑 활성화 및 애니메이션 구하기.

언리얼에서 Motion Warping은 캐릭터의 루트 모션이 타겟과 일치하도록 동적으로 정렬하는 기능입니다.

https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Plugins/MotionWarping/UMotionWarpingComponent?application_version=5.1

 

이 기능을 사용하기 위해서 모션워핑 플러그인을 활성화 해야되고, 애니메이션이 루트 모션이어야 합니다.

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를 사용해 앞에 장애물이 있는지 검사하고, 장애물이 있으면 뛰어넘을 수 있는지 검사해서 장애물을 뛰어넘거나 올라가거나 둘중 하나를 실행하는 코드입니다.

물론 간단하게 구현한만큼 버그가 조금 있긴합니다.(벽을 뚫고 간다거나...) 

실행영상

Unreal Vaulting

Designed by JB FACTORY