2024. 2. 18. 15:50ㆍ강의/UE5 기타 강의들
- Lyra는 카메라를 CameraComponent를 커스텀해서, CameraMode 클래스를 통해 카메라를 조절합니다. 이에 관한 글은 따로 작성할 것이고, 이 글에서는 Lyra에서 웅크리기 모션을할 시 카메라를 보정하는 방법에 대해 다룹니다.
- 또한, 이 글은 Lyra의 코드와 몇몇 강의를 보고 제가 이해한 대로 정리한 글이기 때문에 정확성이 떨어질 수 있습니다.
1. 웅크리기 보정?
- Lyra에서 카메라는 CameraArm 과 같은 컴포넌트를 사용하지 않고, CameraMode를 통해 구현합니다.
- 따라서, 캐릭터가 위처럼 웅크리기를 수행할 때 부드럽게 카메라를 이동시키는 것을 직접 구현해야합니다.
2. Pivot Location?
- Pivot 이란? "어떠한 대상의 중심축" 을 이야기합니다. 보통 삼인칭 게임에서는 우리의 캐릭터가 Pivot이 됩니다.
- Lyra에서는 Camera의 Pivot Location을 CameraMode::GetPivotLocation 에서 구현하여, Pivot의 위치를 가져옵니다.
- 주요 로직은 아래와 같습니다.
// CameraMode::GetPivotLocation
const AActor* TargetActor = GetTargetActor();
// 만약, Character를 타겟으로 삼는다면.. 웅크리기와 같은 movement component에 대응
if (const ACharacter* TargetCharacter = Cast<ACharacter>(TargetPawn))
return 고정한 Pivot Location
// 만약, Pawn이라면, Pawn의 View 즉, ActorLocation + eye height 반환
if (const APawn* TargetPawn = Cast<APawn>(TargetActor))
return TargetPawn->GetPawnViewLocation();
// 만약, Actor라면 그냥 Actor의 위치 반환
return TargetActor->GetActorLocation();
- Character, Pawn, Actor를 부모로 삼는지 확인 후 적절한 위치를 가져오는 것을 볼 수 있죠
- 여기서, Pawn은 PawnViewLocation을 통해 머리의 위치, 즉 눈의 위치를 가져와 우리가 흔히 보는 3인칭 카메라의 PivotLocation을 정할 수 있습니다.
- 하지만, PawnViewLocation을 PivotLocation으로 사용한다면 어떻게 될까요?
FVector APawn::GetPawnViewLocation() const
{
return GetActorLocation() + FVector(0.f,0.f,BaseEyeHeight);
}
- TargetPawn->GetPawnViewLocation()을 들여다보면 BaseEyeHeight 이라는 값과 ActorLocation을 더한 것임을 알 수 있습니다.
// ACharacter.cpp ... Crouch 상태이면, CrouchedEyeHeight으로 BaseEyeHeight이 설정됨
void ACharacter::RecalculateBaseEyeHeight()
{
if (!bIsCrouched)
{
Super::RecalculateBaseEyeHeight();
}
else
{
BaseEyeHeight = CrouchedEyeHeight;
}
}
- 하지만, BaseEyeHeight, GetActorLocation() 의 값들은 웅크리기시 변하는 값입니다. 그러므로 순간이동한 것 처럼 카메라가 이동하게됩니다.
- 사용자가(CameraMode를 상속받아서 사용하는) 부드럽게 카메라를 움직이는 기능을 구현하게 하려면, 이 PivotLocation을 고정시키는 방법밖에 없습니다.
2.1 Pivot Location 고정하는 방법?
- GetActorLocation은 RootComponent의 Location을 가져옵니다.
- 보통 ACharacter 를 상속받는 클래스들은 CapsuleComponent의 Location입니다.
- Lyra에서 이 Pivot Location을 고정하는 방법은 캡슐의 Half Height를 이용하였습니다.
- 웅크리기시 중심의 Location은 위와 같이 줄어든 사이즈의 절반만큼 이동하게됩니다.
- 따라서, 이 값을 구하여 더해주면, GetActorLocation의 변화에 대응할 수 있습니다.
- 또한, 캐릭터의 CDO의 BaseEyeHeight을 가져오면, eye height 또한 고정시킬 수 있습니다.
- 최종적으로, Lyra의 PivotLocation을 고정시키는 코드는 다음과 같습니다.
// Height adjustments for characters to account for crouching.
if (const ACharacter* TargetCharacter = Cast<ACharacter>(TargetPawn))
{
const ACharacter* TargetCharacterCDO = TargetCharacter->GetClass()->GetDefaultObject<ACharacter>();
check(TargetCharacterCDO);
const UCapsuleComponent* CapsuleComp = TargetCharacter->GetCapsuleComponent();
check(CapsuleComp);
const UCapsuleComponent* CapsuleCompCDO = TargetCharacterCDO->GetCapsuleComponent();
check(CapsuleCompCDO);
const float DefaultHalfHeight = CapsuleCompCDO->GetUnscaledCapsuleHalfHeight();
const float ActualHalfHeight = CapsuleComp->GetUnscaledCapsuleHalfHeight();
const float HeightAdjustment = (DefaultHalfHeight - ActualHalfHeight) + TargetCharacterCDO->BaseEyeHeight;
return TargetCharacter->GetActorLocation() + (FVector::UpVector * HeightAdjustment);
}
- 이제, 사용자 코드인 CameraMode_ThirdPerson 에서 PivotLocation을 가져와 웅크리기시, 부드럽게 Location을 변화시켜야합니다.
2.2 참고
// OnStartCrouch takes the change from the Default size, not the current one (though they are usually the same).
const float MeshAdjust = ScaledHalfHeightAdjust;
ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();
HalfHeightAdjust = (DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() - ClampedCrouchedHalfHeight);
ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale;
- 위 코드는 UCharacterMovementComponent의 Crouch 함수에 있는 코드입니다.
- 여기서, Crouch 상태가 되면 Height를 GetUnScaledCapsuleHalfHeight를 사용하여 조정하는 것을 확인할 수 있습니다.
3. EyeHeight를 CameraMode에서 보간 수행
- CameraMode는 매 프레임마다 UpdateView 메서드를 실행하여, 카메라의 위치, 각도 등을 조정합니다. 따라서, 이 함수에서 캐릭터가 웅크리는 것을 확인한 후, GetPivotLocation으로 가져온 PivotLocation의 높이를 줄여나가면 됩니다.
void ULyraCameraMode_ThirdPerson::UpdateView(float DeltaTime)
{
UpdateForTarget(DeltaTime);
UpdateCrouchOffset(DeltaTime);
FVector PivotLocation = GetPivotLocation() + CurrentCrouchOffset;
3.1 UpdateForTarget
void ULyraCameraMode_ThirdPerson::UpdateForTarget(float DeltaTime)
{
if (const ACharacter* TargetCharacter = Cast<ACharacter>(GetTargetActor()))
{
if (TargetCharacter->bIsCrouched)
{
const ACharacter* TargetCharacterCDO = TargetCharacter->GetClass()->GetDefaultObject<ACharacter>();
const float CrouchedHeightAdjustment = TargetCharacterCDO->CrouchedEyeHeight - TargetCharacterCDO->BaseEyeHeight;
SetTargetCrouchOffset(FVector(0.f, 0.f, CrouchedHeightAdjustment));
return;
}
}
SetTargetCrouchOffset(FVector::ZeroVector);
}
- Lyra에서는 위와 같이 Character의 bIsCrouched를 사용하여, 웅크리기시 TargetOffset을 설정합니다.
PRAGMA_DISABLE_OPTIMIZATION
void ULyraCameraMode_ThirdPerson::SetTargetCrouchOffset(FVector NewTargetOffset)
{
CrouchOffsetBlendPct = 0.0f;
InitialCrouchOffset = CurrentCrouchOffset;
TargetCrouchOffset = NewTargetOffset;
}
PRAGMA_ENABLE_OPTIMIZATION
- Pct는 Lerp를 수행할 때 사용되는 Alpha 값입니다. 각 변수는 다음과 같이 사용됩니다.
FMath::InterpEaseInOut(InitialCrouchOffset, TargetCrouchOffset, CrouchOffsetBlendPct, 1.0f);
3.2 UpdateForTarget 문제점? (5.3 기준)
- UpdateForTarget CrouchOffsetBlendPct가 매 프레임마다 초기화된다는 것입니다.
- 역시 같은 문제점을 지적한 PR을 찾아 볼 수 있었습니다.
- https://github.com/EpicGames/UnrealEngine/pull/10546
- 따라서, 저는 제 프로젝트에서는 CrouchOffsetBlendPct를 그냥 제거하는 방식으로 블렌딩하였습니다.
3.3 UpdateCrouchOffset
void ULyraCameraMode_ThirdPerson::UpdateCrouchOffset(float DeltaTime)
{
if (CrouchOffsetBlendPct < 1.0f)
{
CrouchOffsetBlendPct = FMath::Min(CrouchOffsetBlendPct + DeltaTime * CrouchOffsetBlendMultiplier, 1.0f);
CurrentCrouchOffset = FMath::InterpEaseInOut(InitialCrouchOffset, TargetCrouchOffset, CrouchOffsetBlendPct, 1.0f);
}
else
{
CurrentCrouchOffset = TargetCrouchOffset;
CrouchOffsetBlendPct = 1.0f;
}
}
- 매 프레임마다, CrouchOffsetBlendPct를 업데이트하며, Init 에서 Target으로 적절히 보간하는 것으로 부드럽게 카메라 Pivot location에 더해줄 값을 설정해줍니다.
결과는 위와 같습니다.
4. 후기
- CapsuleComponent를 이용하여, 위치를 보정할 수 있는 방법을 알 수 있었습니다.
- Lyra의 Camera Mode 이해도를 높일 수 있었습니다.
- BaseEyeHeight과 CapsuleHeight을 구하는 부분은 CDO 사용의 좋은 예시인 것 같다고 생각하였습니다.
- Lyra에서도 잘못된 코드가 있음을 알게되었고, 항상 의심하는 습관을 가져야할 것 같다고 느꼈습니다.
'강의 > UE5 기타 강의들' 카테고리의 다른 글
[UE5] Control Rig 에서 Alpha Interpolate 노드 내부 구현 (5.3) (0) | 2024.02.20 |
---|---|
[UE5] Crouch 상태일 때 Foot IK 문제 해결 (1) | 2024.02.20 |
[UE5] 애니메이션 적용 (0) | 2024.02.15 |