Unreal Vault, 파쿠르 구현 C++

CJB_ny·2023년 1월 31일
0

UE4구현

목록 보기
1/2
post-thumbnail

깃헙 코드

일단 유튜브나 구글링을 해보아도 거의다 블루프린트로밖에 구현을 한 것이 없었다..

일단 블루프린트로 만든 여러 영상들을 보면서 c++코드로 나만의 방법으로 옮김.

파쿠르 구현
튜토리얼 1~3
간단한 방식? 영상
Vault영상

이 세가지를 참고했는데 다 BP임. 그래도 그나마 도움됬던 부분은 튜토리얼1~3식 영상이였음.


구현 방식

Vault를 구현하기 위해서 LineTrace를 세방 쏠것이다.

  1. Jump키 (Space Bar)를 누를 때 특정거리에서 위에서 아래로 쏘는 LineTrace

  2. HitResult가 true로 반환되면은 전방으로 쏘는 LineTrace

  3. 2번을 실행하고 나면은 2번 Trace의 끝 지점에서 Characater로 향하는 LineTrace

이 세가지 값을 이용해서

HighValt, LowThinVault, LowThickVault

높은 곳 올라가는 Vault인지, 얇은 Actor 지나갈 때 Vault인지, 낮은데 두꺼운 Actor 일 때 Vault인지 나눌 수 있다.


설명

먼저 캐릭터의 길이(키) 만큼에서 부터 먼저 위에서 아래로 내리 꽂는 LinTrace를 발생시킨다.

이값에 따라 너무 높다면은 Vault할 수 없고, 또 너무 낮다면 할 수 없다.

일단 앞에있는 물체가 두껍든 아니든 높이부터 잰다.

이높이에 따라서 Actor가 일단 높이가 낮은지 높은지 분기한다.

높이가 낮다면

초록선을 GetActorLocation으로 부터 쏜다.

이것으로 부터 초록선의 HitResult의 HitResult.Location을 얻어온다.

지금 원하는 것은

파란 선 만큼의 길이를 원하기 때문에

반대쪽에서도 같은 방향으로

똑같은 길이 만큼 LineTrace를 쏴주면된다.

그리고

주황선의 Location - 초록선의 Location을 빼면은 길이가 나옴.

주의할 점

현재 좌표에서 좌표를 빼기 때문에 저 길이의 값은 캐릭터의 방향에 따라서 음수가 나올 수도 있어서 절대값을 넣어주어야한다.


AABMyCharacter::VaultMode AABMyCharacter::CanVault()
{
	FHitResult HitResult;
	FCollisionQueryParams Params(NAME_None, false, this);
	Params.AddIgnoredActor(this);

	FVector ForwardVector = GetActorLocation() + GetActorForwardVector() * 50.f;
	float CapsuleHalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight();
	FVector Start = FVector(ForwardVector.X, ForwardVector.Y, ForwardVector.Z + CapsuleHalfHeight);
	FVector End = FVector(ForwardVector.X, ForwardVector.Y, ForwardVector.Z - CapsuleHalfHeight);
	
	bool bResult = GetWorld()->LineTraceSingleByChannel
	(
		OUT HitResult,
		Start, End,
		ECollisionChannel::ECC_GameTraceChannel1,
		Params
	);

	FColor DrawColor;
	if (bResult)
		DrawColor = FColor::Green;
	else
		DrawColor = FColor::Red;
	
	DrawDebugLine(GetWorld(), Start, End, DrawColor, false, 1.f);

	if (bResult)
		return CanVaultToHit(HitResult);
	else 
		return VaultMode::CantVault;
}

아까말한 먼저 위에서 아래로 내리 꼽는 라인트레이스 이다.

bResult의 값이 true라는 것은 높이가 낮든 높든 일단 범위 안에 맞았다는 말이다.

이후 CanVaultToHit함수를 통해서 해당 Actor가 Vault할 수 있는지 없는지 본다.

  1. 높이가 어느정도 인지 확인

  2. 표면이 걸을 수 있는지 서있을 수 있는지 확인

  3. 오브젝트 위에 서있을 수 있는지

이 세가지를 확인하고 VaultMode를 반환하도록 한다.

AABMyCharacter::VaultMode AABMyCharacter::CanVaultToHit(FHitResult& HitResult)
{
	// Vault할 수 있는 높이 인지
	FVector HitLocation = HitResult.Location;
	FVector HiTTraceEnd = HitResult.TraceEnd;
	float	Height = HitLocation.Z - HiTTraceEnd.Z;

	ABLOG(Warning, TEXT("Object Height : %f"), Height);
	ABLOG(Warning, TEXT("Object InRange : %d"), UKismetMathLibrary::InRange_FloatFloat(Height, MinHighVault, MaxHighVault));

	if (UKismetMathLibrary::InRange_FloatFloat(Height, MinHighVault, MaxHighVault) == false && UKismetMathLibrary::InRange_FloatFloat(Height, MinLowVault, MaxLowVault) == false) return VaultMode::CantVault;

	// 표면이 걸을 수 있는 높이? 경사로인지
	if (CheckWalkable(HitResult.Normal.Z, CharacterMovementComp->GetWalkableFloorZ()) == false) return VaultMode::CantVault;

	// 오브젝트의 표면 위에 서있을 수 있는지
	// HighVault일 경우
	if (UKismetMathLibrary::InRange_FloatFloat(Height, MinHighVault, MaxHighVault))
	{
		CheckCapsuleCollision(FVector(HitLocation.X, HitLocation.Y, HitLocation.Z + CapsuleComp->GetScaledCapsuleHalfHeight() + CapsuleComp->GetScaledCapsuleRadius()), CapsuleComp->GetScaledCapsuleHalfHeight(), CapsuleComp->GetScaledCapsuleRadius());
		SetEndingLocation(FVector(HitLocation.X, HitLocation.Y, HitLocation.Z + CapsuleComp->GetScaledCapsuleHalfHeight()));
		return VaultMode::HighVaulting;
	}
	// LowVault일 경우
	if (CheckThinOrThick() == VaultMode::LowThickVaulting)
	{
		SetEndingLocation(FVector(HitLocation.X, HitLocation.Y, HitLocation.Z + CapsuleComp->GetScaledCapsuleHalfHeight()));
		CheckCapsuleCollision(FVector(HitLocation.X, HitLocation.Y, HitLocation.Z + CapsuleComp->GetScaledCapsuleHalfHeight() + CapsuleComp->GetScaledCapsuleRadius()), CapsuleComp->GetScaledCapsuleHalfHeight(), CapsuleComp->GetScaledCapsuleRadius());
	}
	return CheckThinOrThick();
}

해당경우 다 통과 했다면은 CheckThinOrThick를 통해서 두깨를 확인을 해준다.

AABMyCharacter::VaultMode AABMyCharacter::CheckThinOrThick()
{
	FHitResult HitResult;
	FCollisionQueryParams Params(NAME_None, false, this);
	Params.AddIgnoredActor(this);

	FVector Start = FVector(GetActorLocation().X, GetActorLocation().Y, GetActorLocation().Z - ForLowVaultCheck);
	FVector End = Start + GetActorForwardVector() * LowVaultRange;

	bool bResult = GetWorld()->LineTraceSingleByChannel
	(
		OUT HitResult,
		Start, End,
		ECollisionChannel::ECC_GameTraceChannel1,
		Params
	);
	FColor DrawColor;
	if (bResult)
		DrawColor = FColor::Green;
	else
		DrawColor = FColor::Red;
	DrawDebugLine(GetWorld(), Start, End, DrawColor, false, 1.f, 10, 1.f);
	// ################################################################

	FHitResult HitResultOtherSide;
	FCollisionQueryParams ParamsOtherSide(NAME_None, false, this);
	ParamsOtherSide.AddIgnoredActor(this);

	FVector StartOtherSide = HitResult.TraceEnd;
	FVector EndOtherSide = StartOtherSide + (GetActorForwardVector() * -1) * LowVaultRange;

	bool bResultOtherSide = GetWorld()->LineTraceSingleByChannel
	(
		OUT HitResultOtherSide,
		StartOtherSide, EndOtherSide,
		ECollisionChannel::ECC_GameTraceChannel1,
		ParamsOtherSide
	);
	FColor DrawColorOtherSide;
	if (bResultOtherSide)
		DrawColorOtherSide = FColor::Orange;
	else
		DrawColorOtherSide = FColor::Blue;

	DrawDebugLine(GetWorld(), StartOtherSide, EndOtherSide, DrawColorOtherSide, false, 1.f, 10, 1.f);

	FVector Depth = HitResultOtherSide.Location - HitResult.Location;
	float DepthX = FMath::Abs(Depth.X);

	ABLOG(Warning, TEXT("Low Actor's Depth : %f"), FMath::Abs(Depth.X));

	// Low Actor Thin
	if (DepthX >= 10.f && DepthX <= MinLowDepth)
	{
		FVector OriginalPos = FVector(HitResultOtherSide.Location.X, HitResultOtherSide.Location.Y, HitResultOtherSide.Location.Z + ForLowVaultCheck);

		FVector EndPos = OriginalPos + (GetActorForwardVector() * CapsuleComp->GetScaledCapsuleRadius() * 2);
		
		SetEndingLocation(EndPos);
		CheckCapsuleCollision(EndPos, CapsuleComp->GetScaledCapsuleHalfHeight(), CapsuleComp->GetScaledCapsuleRadius());

		return VaultMode::LowThinVaulting;
	}
	// Low Actor Thick
	else if (DepthX > MinLowDepth || DepthX < 10.f)
	{
		return VaultMode::LowThickVaulting;
	}
	return VaultMode::CantVault;
}

OtherSide가 주황선이다.

뭐 사실 방법은 많은거같다. 어떻게든

위에서 먼저 쏘고 높이 구하고

높이에 따라 낮은지 높은지 확인

높으면 그대로 파쿠르 해주고 낮다면은 그 낮은 Actor의 길이?(두깨)를 구해준다.

이 두깨에 따라서 계단 밟듯이 올라갈지 파쿠르해서 그냥 넘어갈지 결정

이렇게 구현을 해주면 된다.

그리고 중간에 애를 많이 먹었던 부분이

선형보간을 통해서 Character를 움직이게 하였는데 선형보간할 때

StartLocation이랑 EndingLocation을 잡는데 시간을 많이 잡아 먹음.

float AABMyCharacter::VaultTick(float DeltaTime)
{
	Progress += DeltaTime / VaultSpeed;
	Progress = FMath::Clamp(Progress, 0.f, 1.f);
	if (Progress >= 1.f)
	{
		SetVaultEnd();
	}
	return Progress;
}

일단 선형보간할 때 진행상태를 반환하는 VaultTick함수인데 Progress에 따라 SetActorLocation을 해줄 것이다.

void AABMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	switch (VaultState)
	{
		case VaultMode::HighVaulting:
		{
			SetActorLocation(FMath::Lerp(StartingLocation, EndingLocation, VaultTick(DeltaTime)));
		}
		break;
		case VaultMode::LowThinVaulting:
		{
			 SetActorLocation(FMath::Lerp(StartingLocation, EndingLocation, VaultTick(DeltaTime)));
		}
		break;
		case VaultMode::LowThickVaulting:
		{
			SetActorLocation(FMath::Lerp(StartingLocation, EndingLocation, VaultTick(DeltaTime)));
		}
		break;
	}
}

그러면 어떤 Vault상태인지 상관없이 선형보간한 값으로 일정하게 쭉 진행이 된다.

이것도 선형보간 방식으로 구현을 해도 되고 자기만의 방식으로 마음대로 캐릭터를 움직이게 해도된다.

근데 유튜브나 구글링했을 때는 거의 대부분

Vault할 경우 Capsulecomp를 가져와서

CapsuleComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
CharacterMovementComp->SetMovementMode(EMovementMode::MOVE_Flying);

이런식으로 했다가 Delay를 준다음에 이동이 끝나고 나면은

ECollisionEnabled::CollsiionPhysicsAndQuery
MovementMode::MOVE_Walking);

이렇게 다시 돌려놓는? 식이였는데 본인은

이 영상 참고함.

profile
공부 일기장으로 변해버린 블로그 (https://cjbworld.tistory.com/ <- 이사중)

0개의 댓글