언리얼 엔진 네트워크 FPS 게임 개발 일지#6 에임 오프셋 기능 개선

김진우·2022년 7월 12일
2
post-thumbnail

개발일지 #4의 후속 게시글이다.
대부분의 FPS 게임에는 3인칭 캐릭터의 움직임을 자연스럽게 보이도록 카메라를 회전시킬 때 액터가 회전하지 않고 시선만 돌아가는 기능이 있다. 일정 각도 이상으로 돌아가면 캐릭터가 몸이 정면을 바라보도록 자세를 다시 잡는다. UE4에서는 캐릭터의 시선을 돌리는 기능을 에임 오프셋으로 지원한다.

CameraComponent->bUsePawnControlRotation 값이 true인 상태로 이 기능을 구현했지만 총기 반동을 위한 RecoilComponent를 추가하기 위해서는 false인 상태로 기능을 구현해야 했다. 값이 true 이라면 부모 컴포넌트는 카메라 컴포넌트의 Rotation 값에 영향을 줄 수 없기 때문이다. 즉 RecoilComponent의 Rotation 값을 수정해도 자식 컴포넌트인 카메라 컴포넌트에서 확인할 수 없다.

이 과정에서 싱글플레이 환경과는 다르게 멀티플레이 환경인 경우에 마우스를 입력해도 카메라가 돌아가지 않는 문제를 발견했다. 코드를 개선할 필요가 있었다.

개선 방향

마우스 입력을 카메라에 전달하는 코드와 3인칭 캐릭터가 시선을 돌리는 코드가 서로 영향을 주고 받음에 따라 코드가 복잡해지고 버그 발생 빈도가 늘어가고 있다. 카메라 관련 기능을 추가할 때마다 비슷한 종류의 버그가 발생하기 때문에 두 기능을 확실하게 분리할 필요성을 느꼈다.

CameraJointComponent 추가

AFpsCharacter 액터 회전, 3인칭 캐릭터 회전, 카메라 회전 기능을 확실히 분리하기 위해 USceneComponent* CameraJointPitchComponentUSceneComponent* CameraJointYawComponent를 만들고 추가했다. 이 컴포넌트는 카메라 컴포넌트의 조상 컴포넌트로써 매Tick마다 GetControlRotation()로 컨트롤러의 회전값을 가져와 스스로 업데이트하는 간단한 컴포넌트이다. Pitch를 담당하는 컴포넌트와 Yaw를 담당하는 컴포넌트를 AFpsCharacter에 추가해서 카메라의 회전을 담당하는 코드를 AFpsCharacter에서 CameraJointComponent 에게 위임했다.

FpsCharacter.h
	USceneComponent* CameraYawJointComponent;
	USceneComponent* CameraPitchJointComponent;
FpsCharacter.cpp

AFpsCharacter::AFpsCharacter()
{
	bReplicates = true;

 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	InitializeCollisionComponent();
	InitializeMovementComponent();
	InitializeCameraJointComponent();
	InitializeRecoilComponent();
	InitializeCamera();
	InitializeBodyMesh();
	InitializeGameplayVariable();
}


void AFpsCharacter::InitializeCameraJointComponent()
{
	CameraYawJointComponent = CreateDefaultSubobject<USceneComponent>(TEXT("CameraYawJointComponent"));
	CameraYawJointComponent->SetupAttachment(GetCapsuleComponent());
	CameraYawJointComponent->SetRelativeLocation(FVector(0.f, 0.f, BaseEyeHeight));

	CameraPitchJointComponent = CreateDefaultSubobject<USceneComponent>(TEXT("CameraPitchJointComponent"));
	CameraPitchJointComponent->SetupAttachment(CameraYawJointComponent);
}

void AFpsCharacter::InitializeRecoilComponent()
{
	RecoilComponent = CreateDefaultSubobject<URecoilComponent>(TEXT("RecoilComponent"));
	RecoilComponent->SetupAttachment(CameraPitchJointComponent);
	RecoilComponent->SetIsReplicated(true);
	RecoilComponent->Initialize(this);
}

void AFpsCharacter::InitializeCamera()
{
	CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	CameraComponent->SetupAttachment(RecoilComponent);
	CameraComponent->bUsePawnControlRotation = false;
	bUseControllerRotationYaw = false;
}

void AFpsCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	UpdateBodyMeshAimOffset(DeltaTime);
	UpdateCameraRotation();
	UpdateCrosshair();
	UpdateInteractiveTarget(DeltaTime);
}

void AFpsCharacter::UpdateBodyMeshAimOffset(float DeltaTime)
{
	if (GetNetMode() == NM_Client) return;

	FRotator ControlRotation = GetControlRotation();

	if (!GetVelocity().IsZero())
	{
		SetActorRotation(FRotator(0.f, ControlRotation.Yaw, 0.f));
		BodyMeshAimYaw = 0.f;
		return;
	}

	FRotator AimRotator = FRotator(BodyMeshAimPitch, BodyMeshAimYaw, 0);
	FRotator ActorRotation = GetActorRotation();
	AimRotator = UKismetMathLibrary::RInterpTo(
		AimRotator,
		UKismetMathLibrary::NormalizedDeltaRotator(ControlRotation, ActorRotation),
		DeltaTime,
		0
	);
	BodyMeshAimPitch = UKismetMathLibrary::ClampAngle(AimRotator.Pitch, -90, 90);
	BodyMeshAimYaw = UKismetMathLibrary::ClampAngle(AimRotator.Yaw, -90, 90);

	if (AimRotator.Yaw < -90 || 90 < AimRotator.Yaw)
	{
		AddActorWorldRotation(FRotator(0, AimRotator.Yaw - BodyMeshAimYaw, 0));
	}
}

void AFpsCharacter::UpdateCameraRotation()
{
	FRotator ControlRotation = GetControlRotation();
	CameraYawJointComponent->SetWorldRotation(FRotator(0.f, ControlRotation.Yaw, 0.f));
	CameraPitchJointComponent->SetRelativeRotation(FRotator(ControlRotation.Pitch, 0.f, 0.f));
}

완성 모습

위 코드를 작성한 후 에임 오프셋과 애니메이션 블루프린트를 적용하면 아래와 같은 모습을 확인할 수 있다

발을 돌리는 애니메이션이 없어서 에임 오프셋이 90도를 넘어서면 넘은 값만큼 액터를 회전시켰다. 블렌더로 애니메이션을 추가한 다음에 대체할 예정이다.

profile
게임 개발자입니다.

0개의 댓글