Unreal 5/FPS Shooting

4. [Unreal 5 / C++] 반동(Camera Shake)과 연사

돼지표 2025. 1. 7. 21:40

이전 포스트에서 발사체를 구현하고 에임 사이즈에 따라서 탄이 튀는 것 까지 구현을 하였다.

이번 시간에는 발사시 발생하는 반동과 재장전 로직에 대해서 다루겠다.

 

1.Camera Shake를 사용한 반동 구현

ULegacyCameraShake 클래스를 상속받는 UiCameraShake클래스를 생성해준다.

 

UiCameraShake.h

#pragma once

#include "CoreMinimal.h"
#include "Shakes/LegacyCameraShake.h"
#include "UiCameraShake.generated.h"

UCLASS()
class PROJECT_4_API UUiCameraShake : public ULegacyCameraShake
{
    GENERATED_BODY()
public:
    UUiCameraShake();
};

 

UiCameraShake.cpp

#include "Player/Ui/UiCameraShake.h"

UUiCameraShake::UUiCameraShake() {
    // 카메라 쉐이크 설정
    OscillationDuration = 0.1f;
    OscillationBlendInTime = 0.05f;
    OscillationBlendOutTime = 0.05f;

    RotOscillation.Pitch.Amplitude = 1.0f;
    RotOscillation.Pitch.Frequency = 25.0f;

    RotOscillation.Yaw.Amplitude = 1.0f;
    RotOscillation.Yaw.Frequency = 25.0f;
}

 

생성자에서 초기화하는 변수들은 다음과 같다.

 

설정값 설명 효과
OscillationDuration 카메라 쉐이크가 지속되는 총 시간 (초 단위). 0.1f 0.1초 동안 진동이 발생합니다.
OscillationBlendInTime 쉐이크가 서서히 시작되는 시간 (초 단위). 0.05f 0.05초 동안 진동이 부드럽게 증가합니다.
OscillationBlendOutTime 쉐이크가 서서히 종료되는 시간 (초 단위). 0.05f 0.05초 동안 진동이 부드럽게 감소합니다.
RotOscillation.Pitch.Amplitude 피치(Pitch) 축의 회전 진폭 (위아래 흔들림의 정도). 1.0f 카메라가 위아래로 약간 흔들립니다.
RotOscillation.Pitch.Frequency 피치(Pitch) 축의 진동 빈도 (초당 진동 횟수). 25.0f 카메라가 초당 25번 위아래로 진동합니다.
RotOscillation.Yaw.Amplitude 요(Yaw) 축의 회전 진폭 (좌우 흔들림의 정도). 1.0f 카메라가 좌우로 약간 흔들립니다.
RotOscillation.Yaw.Frequency 요(Yaw) 축의 진동 빈도 (초당 진동 횟수). 25.0f 카메라가 초당 25번 좌우로 진동합니다.

 

void UWeaponComponent::ApplyCameraShake() const
{
    APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
    if (PlayerController)
    {
        PlayerController->ClientStartCameraShake(UUiCameraShake::StaticClass(), WeaponData->Recoil);
    }
}

 

 WeaponComponent에서 발사할때마다 해당 함수를 호출하여 카메라에 진동을 발생시킬 수 있다.

여기서 매개변수로 전달되는 Recoil 값은 스케일값이며 UiCameraShake에서 초기화한 값 * Recoil 으로 반동 세기를 조절해줄 수 있으며 따라서 0일때에는 반동이 사라진다.

 

2. 연사 시스템 구현

일반적인 FPS 게임처럼 마우스 우클릭을 꾹 누르면 총알이 연속적으로 발사되도록 구현해보자.

우선, 이전에 작성한 WeaponComponent에 현재 발사 중인지 여부를 확인할 수 있는 플래그 IsShooting과, 일정 시간마다 함수를 반복 호출할 TimerHandle을 선언한다. 또한, 마지막 발사 시간을 기록할 LastFireTime 변수도 함께 추가한다.

private:
	FTimerHandle ShootingTimerHandle;
	bool IsShooting;
    float LastFireTime

 

이제 기존에 총알을 발사하던 함수를 살짝 수정해서 FireWeaponStopFireWeapon으로 나눈뒤 Shoot이라는 함수에서 총알이 발사되도록 한다.

private:
	void Shoot();
    
public:
    UFUNCTION(BlueprintCallable, Category = "Weapon")
    void FireWeapon();

    UFUNCTION(BlueprintCallable, Category = "Weapon")
    void StopFireWeapon();

 

기존의 총알을 발사하는 함수인 FireWeapon 함수가 호출되면, IsShooting true로 설정한 다음 Shoot 함수가 호출되어 총알이 발사 조건에 맞을 때만 한 발 발사되도록 한다. 그 다음 TimerManager를 통해 Shoot 함수를 일정 간격으로 호출하도록 스케줄러를 등록한다.

void UWeaponComponent::FireWeapon()
{
    IsShooting = true;

    float CurrentTime = GetWorld()->GetTimeSeconds();
    // 발사 간격 확인
    if (CurrentTime - LastFireTime > 1.0f / WeaponData->FireRate)
    {
        Shoot();
    }
    GetWorld()->GetTimerManager().SetTimer(
        ShootingTimerHandle, this, &UWeaponComponent::Shoot, 1.0f / WeaponData->FireRate, true
    ); // 연사 타이머 설정
}

 

발사가 끝나면(플레이어가 좌클릭 버튼을 떼면) StopFireWeapon 함수가 호출된다. 이 함수는 IsShooting을 false로 설정하고, 등록된 Shoot 함수 스케줄러를 삭제하여 타이머를 중지한다.

void UWeaponComponent::StopFireWeapon()
{
    IsShooting = false;
    GetWorld()->GetTimerManager().ClearTimer(ShootingTimerHandle); // 타이머 중지
}

 

Shoot 함수는 기존 FireWeapon을 그대로 복사하되 맨 앞에 IsShootingfalse일때 return되는 구문과 발사가 완료되면 마지막 발사시간을 갱신해주는 코드를 추가하면 된다.

void UWeaponComponent::Shoot(){
    if (CurrentAmmoCount == 0 || !SkeletalMeshComponent || !UiComponent || !IsShooting) return;
    
    // ... 기존 코드
    
    LastFireTime = GetWorld()->GetTimeSeconds();// 마지막 발사시간 갱신
}