[UE5] 기초 총기 시스템 개발 완료 (프로젝트ASR - 개발일지2)
(지난 포스팅)
- 크로스헤어 제작
- AimOffset 애니메이션 문제 해결
- 카메라 고도화
- 연사기능 구현
- 총기반동 구현
크로스헤어 제작
캐릭터의 기본 무기인 라이플을 위한 십자 형태의 크로스헤어를 위젯 블루프린트로 제작하였다.
PlayerController에서 BeginPlay 시점에 위젯을 생성하고 무기 습득에 따라 Visibility를 조정한다.
BindWidget으로 위젯 블루프린트의 컴포넌트들을 연결하여 C++ 코드에서 캐릭터의 상태에 따라 크로스헤어가 벌어지고, 좁아지도록 하였다.
void AWarComposerPlayerController::BeginPlay()
{
Super::BeginPlay();
if (RangerHUDClass != nullptr)
{
RangerHUD = CreateWidget<UWarComposerHUD>(GetWorld(), RangerHUDClass);
RangerHUD->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
RangerHUD->AddToViewport();
}
}
class WARCOMPOSER_API UWarComposerHUD : public UUserWidget
{
// ...
UPROPERTY(meta=(BindWidget))
class UBorder* CrosshairCenter;
// ...
// ...
float CrosshairInterval = 30.f;
void SetCrosshairInterval(float InCrosshairInterval);
void SetCrosshairVisibility(ESlateVisibility InVisibility);
void SetCrosshair();
};
점프, 앉기, 달리기, 걷기, 조준 상태에 따라 크로스헤어의 십자가 넓어지는 정도가 달라지며 보간을 통해 자연스럽게 크로스헤어가 변경되도록 하였다.
AimOffset 애니메이션 문제 해결
AimOffset을 사용한 에니메이션에서 실제 조준점과 총기의 끝이 일치하지 않고 벽에 가까워지면 총의 방향과 실제로 총알이 날아가는 방향이 아예 다르게 되는 문제가 있었다.
이전에 FPS 예제로 학습용 프로젝트를 만들며 한 번 겪었던 문제라 쉽게 해결하였다.
총구의 방향을 결정하는 것은 총기 Mesh의 모양, 총기가 부착된 Socket의 Transform이다. 총기 Mesh는 따로 굽어있는 부분이 없으므로 배제하고 총기가 부착된 hand_r의 Socket이 총구 방향을 결정한다.
Screen의 정중앙과 총기의 끝이 일치하도록 하기 위해 World Space에서의 hand_r의 Transform과 조준점까지의 벡터를 구하고 FindLookAtRotation으로 해당 벡터로의 Rotation을 구하였다.
(FindLookAtRotation은 코드를 보면 MakeFrom을 사용한 KismetMathLibrary의 편의 함수 / 이전 포스팅에 다루었던 내용이 있어 첨부)
FTransform RightHandMactchingTransform = WarComposerCharacter->GetMesh()->GetSocketTransform(FName("hand_r"), ERelativeTransformSpace::RTS_World);
RightHandMatchingRotation = UKismetMathLibrary::FindLookAtRotation(FVector::ZeroVector, RightHandMactchingTransform.GetLocation() - WarComposerCharacter->GetHitPoint());
이후 Transfrom Bone으로 hand_r 에만 구한 Rotation을 주도록 하여 총구가 훨씬 정확하게 중앙을 향하도록 하였다.
카메라 수정
기존에 감으로 대충 잡아두었던 카메라 위치를 다른 TPS 게임들을 참고하여 수정하였다.
골반, 옆에서 보면 허벅지 정도가 스크린에 잡히는 배틀그라운드의 카메라 시점을 기본 시점으로 정하였다. 조준시에는 간단하게 FOV를 조정하여 스크린을 확대하는 방식으로 구현하려 하였으나 다른 TPS 게임에서의 역동적인 느낌이 나지 않아 직접 SpringArm의 Offset, Length, 카메라의 여러 설정을 바꿔가며 직접 구현하였다.
조준을 하고 있는 경우 SpringArm Length가 보간을 통해 줄어들고 SocketOffset은 Y축으로는 커지고 Z축으로는 작아지게 하여 캐릭터가 기존 시점보다 왼쪽 위에 위치하도록 하였다. 조준점이 달라지지 않도록 캐릭터 Mesh에 부착한 SpringArm의 RelativeLocation의 조정은 지양하였다.
if (bIsAiming)
{
CameraBoom->TargetArmLength = FMath::FInterpTo(CameraBoom->TargetArmLength, ZoomArmLength, DeltaSeconds, 5.f);
CameraBoom->SocketOffset = FMath::VInterpTo(CameraBoom->SocketOffset, ZoomArmSocketOffset + TargetSocketOffsetAdditive, DeltaSeconds, 5.f);
}
else
{
CameraBoom->TargetArmLength = FMath::FInterpTo(CameraBoom->TargetArmLength, Character->DefaultSpringArmLength, DeltaSeconds, 10.f);
CameraBoom->SocketOffset = FMath::VInterpTo(CameraBoom->SocketOffset, Character->DefaultSpringArmSocketOffest + TargetSocketOffsetAdditive, DeltaSeconds, 10.f);
}
연사 기능, 총기 반동 추가
자동, 반자동, 단발 총기를 따로 제작하려 하였으나 기획 변경에 따라 그럴 필요가 없어져 기본 총기인 자동소총을 위한 연사기능을 개발하고 이를 총기 클래스의 기본 설정으로 지정하였다.
발사가 가능 여부를 나타내는 플래그와 Timer를 사용하여 구현하였다. 발사가 가능한 상태라면 우선 발사 불가능 상태로 지정하고 Timer를 시작한다. Timer는 총기의 연사력에 따라 Delay가 결정되며 Delay가 끝나면 콜백함수가 호출된다.
콜백함수에서는 발사 가능 상태로 플래그를 변경하고 만약 발사키가 아직 Released되지 않았다면 다시 Timer를 세팅한다.
연사 기능이 구현됨에 따라 추가로 플레이어가 매우 빠른속도로 클릭을하여도 총기의 연사 속도를 넘을 수 없게 되었다.
// Fire Timer Callback Code Snippet
bCanFire = true;
if (bStartFire && EquippedWeapon->IsAutomatic())
{
FireWithTimer();
}
총기 반동의 경우 앞서 구현한 크로스헤어의 퍼짐 정도가 곧 얼마나 캐릭터가 침착하게 사격하느냐와 동일하기 떄문에 이 값을 사용하여 구현하였다.
크로스헤어가 퍼져있는 정도를 SpreadRate라는 값에 저장하고 평균이 0이 되기끔 정규화 한 뒤, Unifrom Random으로 샘플링 하였다. (이후 다른 게임과 더 비교해보니 Gaussian Sampling으로 변경하는 것이 더 자연스러웠다)
PlayerController의 Pitch, Yaw값이 반동에 따라 달라지며 Pitch의 경우 랜덤값에 고정값을 소량 더하여 조준점이 점점 위로 상승하도록 하였다.
위 Gif를 보면 조준없이 / 조준 / 앉아서 조준에 따라 반동이 다른 것을 알 수 있다.
다음으로는 전투 시스템 추가 개발, 확장을 위한 코드 리팩토링, 클래스 구조 변경을 진행할 예정이다.