[UE5] Character Movement Component Series : Crouch & PlayerCameraManager

연하·2024년 9월 23일
0
post-thumbnail

Character Movement Component In-Depth 강의 시리즈를 공부하면서 한글로 정리한 포스트입니다. 의역과 오역이 난무하니 주의해주세요!
https://youtu.be/vw4sPZ8xhFk?si=h_zshCaMpfvAI4pc

웅크리기 기능은 언리얼에서 제공하는 기능이다. CMC에 내장되어 있으므로 이 기능을 노출하고 프로젝트에서 사용하는 방법을 알려 줄 예정이다. 또한, 카메라 전환을 원할하게 만드는 방법도 보여준다.

Crouch Implementation

enum CompressedFlags
{
	FLAG_JumpPressed	= 0x01,	// Jump pressed
	FLAG_WantsToCrouch	= 0x02,	// Wants to crouch
	FLAG_Reserved_1		= 0x04,	// Reserved for future use
	FLAG_Reserved_2		= 0x08,	// Reserved for future use
    
	// Remaining bit masks are available for custom flags.
	FLAG_Custom_0		= 0x10,
	FLAG_Custom_1		= 0x20,
	FLAG_Custom_2		= 0x40,
	FLAG_Custom_3		= 0x80,
};

지난번에 살펴보았던 플래그들이다. 엔진에 예약되어 있는 것이 있고, 우리가 사용할 수 있는 것이 있다. FLAG_WantsToCrouch 를 보면 웅크리기가 이미 있다는 것을 알 수 있다. 만약 이 플래그를 변경하려고 한다면, 자동으로 서버에 복제되고 모든 동작이 동기화되며, CMC가 이 플래그의 실제 구현을 자동으로 다루게 된다. 엔진이 이것을 처리하므로, 우리가 직접 다룰 수는 없다. GetCompressedFlags() 의 Super 함수로 들어가보면 Crouch 플래그를 설정하고 있다는 것을 볼 수 있다.

따라서 bWantsToCrouch 변수를 변경하면, 플래그가 자동으로 설정, 읽혀지고 자동으로 구현된다.

Crouch를 처리할 수 있는 두 가지 방법이 있다. togglehold.
toggle은 Crouch 버튼을 누를 때마다 상태를 변경하는 것을 의미한다. 웅크리고 있을때는 서 있는 상태가 되고, 서 있는 상태에서는 웅크린다는 뜻이다.
hold 는 Crouch 버튼을 누르고 있으면 웅크린 상태가 되고, 손을 떼자마자 서 있는 상태로 변경되는 것을 말한다.

UFUNCTION(BlueprintCallable) void CrouchPressed();
void UNyongMovementComponent::CrouchPressed()
{
	bWantsToCrouch = !bWantsToCrouch;
}

변수를 토글해주고,

UNyongMovementComponent::UNyongMovementComponent()
{
	NavAgentProps.bCanCrouch = true;
}

생성자에서 Crouch를 사용한다고 명시해준다. 이것이 Crouch 매커니즘을 사용하는 데 필요한 전부이다.

마찬가지로 C키를 누르면 플래그를 변경해주는 함수를 호출하고, 디버깅을 위해 캡슐의 Hidden in Game 옵션을 꺼주었다.

캡슐의 높이가 줄어들고 속도가 느려지는 것을 확인할 수 있다. 여기서 Crouch를 시작하면 카메라가 즉시 내려간다는 것을 알 수 있는데, 캡슐이 Crouch를 실행하는 순간 절반 높이로 즉시 줄어들어서 발생하는 현상이다.

캡슐의 높이를 천천히 줄이는 방법이 있지만, 이는 많은 비동기화를 유발시킬 수 있다. 서버에서는 충돌이 일어났지만 클라이언트에서는 일어나지 않을 수도 있음. 캡슐의 높이가 둘 중 하나라면 이러한 오류가 발생할 여지가 적다. 이렇기 때문에 캡슐이 스냅되도록 두는 것이 더 나을 것이다.

이러한 로직은 그대로 두고, 일종의 전환이 있는 것처럼 보이도록 카메라와 애니메이션을 통해 부드러운 전환을 처리한다.

PlayerCameraManager Implementation

#pragma once

#include "CoreMinimal.h"
#include "Camera/PlayerCameraManager.h"
#include "NyongCameraManager.generated.h"

UCLASS()
class NYONG_API ANyongCameraManager : public APlayerCameraManager
{
	GENERATED_BODY()
	
public:
	ANyongCameraManager();
};
#include "NyongCameraManager.h"
#include "NyongCharacter.h"
#include "NyongMovementComponent.h"
#include "Components/CapsuleComponent.h"

ANyongCameraManager::ANyongCameraManager()
{
	PrimaryActorTick.bCanEverTick = true;
}

PlayerCameraManager 를 상속받은 새 캐릭터를 만들어주자.

UPROPERTY(EditDefaultsOnly) float CrouchBlendDurtation = 0.5f;
float CrouchBlendTime;

virtual void UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) override;

두 개의 변수를 선언해주고, UpdateViewTarget 함수를 오버라이드 한다.

void ANyongCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime)
{
	Super::UpdateViewTarget(OutVT, DeltaTime);

	if (ANyongCharacter* NyongCharacter = Cast<ANyongCharacter>(GetOwningPlayerController()->GetPawn()))
	{
		UNyongMovementComponent* NMC = NyongCharacter->GetNyongCharacterMovement();
		FVector TargetCrouchOffset = FVector(0.f, 0.f, 
			NMC->GetCrouchedHalfHeight() - NyongCharacter->GetClass()->GetDefaultObject<ACharacter>()->GetCapsuleComponent()->GetScaledCapsuleHalfHeight());
		FVector Offset = FMath::Lerp(FVector::ZeroVector, TargetCrouchOffset, 
			FMath::Clamp(CrouchBlendTime / CrouchBlendDuration, 0.f, 1.f));

		if (NMC->IsCrouching())
		{
			CrouchBlendTime = FMath::Clamp(CrouchBlendTime + DeltaTime, 0.f, CrouchBlendDuration);
			Offset -= TargetCrouchOffset;
		}
		else
		{
			CrouchBlendTime = FMath::Clamp(CrouchBlendTime - DeltaTime, 0.f, CrouchBlendDuration);
		}

		if (NMC->IsMovingOnGround())
		{
			OutVT.POV.Location += Offset;
		}
	}
NyongCharacter->GetClass()->GetDefaultObject<ACharacter>()->GetCapsuleComponent()->GetScaledCapsuleHalfHeight());

여기서 캐릭터의 기본 객체를 사용하는 이유는 현재 인스턴스의 상태(웅크리기)와 무관하게 캐릭터가 처음 정의된 상태(기본적으로 서 있는 상태)의 캡슐 컴포넌트 높이를 얻기 위함이다. TargetCrouchOffset 을 계산할 때 웅크린 상태와 서 있는 상태의 기본 높이 차이를 알아야 하므로, 항상 기준이 되는 Default Object 값을 사용하여 변화를 비교한다.

자연스럽게 블렌딩 되는 카메라를 볼 수 있다.

0개의 댓글