Character Movement Component In-Depth 강의 시리즈를 공부하면서 한글로 정리한 포스트입니다. 의역과 오역이 난무하니 주의해주세요!
https://youtu.be/vw4sPZ8xhFk?si=h_zshCaMpfvAI4pc
웅크리기 기능은 언리얼에서 제공하는 기능이다. CMC에 내장되어 있으므로 이 기능을 노출하고 프로젝트에서 사용하는 방법을 알려 줄 예정이다. 또한, 카메라 전환을 원할하게 만드는 방법도 보여준다.
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를 처리할 수 있는 두 가지 방법이 있다. toggle
과 hold
.
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를 실행하는 순간 절반 높이로 즉시 줄어들어서 발생하는 현상이다.
캡슐의 높이를 천천히 줄이는 방법이 있지만, 이는 많은 비동기화를 유발시킬 수 있다. 서버에서는 충돌이 일어났지만 클라이언트에서는 일어나지 않을 수도 있음. 캡슐의 높이가 둘 중 하나라면 이러한 오류가 발생할 여지가 적다. 이렇기 때문에 캡슐이 스냅되도록 두는 것이 더 나을 것이다.
이러한 로직은 그대로 두고, 일종의 전환이 있는 것처럼 보이도록 카메라와 애니메이션을 통해 부드러운 전환을 처리한다.
#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 값을 사용하여 변화를 비교한다.
자연스럽게 블렌딩 되는 카메라를 볼 수 있다.