언리얼 엔진

Unreal UI #9 - FPS 게임 감도 설정 시스템 구축기

로안님 2025. 4. 26. 05:53

🎮 언리얼 엔진 5.5 - FPS 게임 감도 설정 시스템 구축기

FPS 게임을 만들다 보면 필수적으로 추가하게 되는 기능 중 하나가 마우스 감도 설정입니다. 이번 글에서는 Unreal Engine 5.5를 사용해 감도 설정 시스템을 단계별로 구축한 과정을 공유합니다.


1. 🎨 옵션 메뉴 UI 위젯 (WBP_OptionsMenu) 제작

먼저 옵션 메뉴를 위한 UI 위젯 WBP_OptionsMenu를 새로 만들었습니다.

  • Canvas Panel을 사용해 배경 레이아웃을 잡고,
  • "마우스 감도"라는 텍스트(TextBlock)와 감도 조절용 Slider를 추가했습니다.
  • 메뉴를 빠져나갈 수 있도록 "뒤로" 버튼도 배치했습니다.

TIP: 위젯 이름을 명확하게 지정하면 후속 작업에서 편합니다.


2. ⚙️ 슬라이더 값으로 감도 적용 기능 연결 시도

슬라이더를 조작했을 때 캐릭터에 실시간으로 감도가 반영되도록 하기 위해 ApplyMouseSensitivity 커스텀 이벤트를 만들었습니다.

  • OnValueChanged 이벤트를 연결하여 슬라이더 조작 시 ApplyMouseSensitivity를 호출하고,
  • 컨트롤러/캐릭터의 MouseSensitivity 변수에 슬라이더 값을 적용하는 방식으로 처리했습니다.

하지만 이 방식만으로는 감도 값이 저장되지 않아, 옵션 메뉴를 다시 열었을 때 슬라이더 값이 초기화되는 문제가 발생했습니다.
따라서 단순 적용만으로는 부족하다는 것을 확인하고, 이후 SaveGame 시스템과 GameInstance를 통한 저장/불러오기 구조를 추가로 도입하게 되었습니다.

플레이어의 카메라 회전을 담당하는 Look 입력 함수에는 감도 값을 곱해 회전 속도를 조정했습니다.

void APlayerCharacter::Look(const FInputActionValue& Value)
{
    FVector2D LookAxis = Value.Get<FVector2D>();
    AddControllerYawInput(LookAxis.X * MouseSensitivity);
    AddControllerPitchInput(LookAxis.Y * MouseSensitivity);
}

3. 🔁 퍼즈 메뉴 옵션 메뉴 전환 흐름

게임이 일시정지(Pause)된 상태에서 퍼즈 메뉴와 옵션 메뉴 간의 전환 흐름을 구성했습니다.

퍼즈 메뉴 구성

  • '계속하기', '옵션', '메인 메뉴로 돌아가기' 버튼 제공
  • '옵션' 버튼 클릭 시 LocalOptionMenu() 함수 호출

옵션 메뉴 진입 (LocalOptionMenu())

  • 기존 퍼즈 메뉴를 RemoveFromParent()로 화면에서 제거
  • 옵션 메뉴 위젯 생성 후 화면에 표시

옵션 메뉴 종료 (CloseOptionMenu())

  • 옵션 메뉴에서 '뒤로 가기' 버튼 클릭 시 CloseOptionMenu() 호출
  • 메뉴가 닫힌 후 다시 퍼즈 메뉴로 자연스럽게 복귀할 수 있도록 UX를 설계했습니다. 퍼즈 메뉴에서 옵션으로 진입한 경우 사용자는 다시 퍼즈 메뉴로 복귀하는 것이 자연스럽다고 판단했습니다.

메뉴 분기 처리 (퍼즈 ↔ 메인 메뉴) 옵션 메뉴는 퍼즈 메뉴 외에도 메인 메뉴에서 접근 가능했기 때문에, 뒤로 가기 시 어느 메뉴로 돌아갈지 결정하는 로직이 필요했습니다. 처음에는 메뉴 상태를 구분하는 방법에 대해 고민이 많았습니다.

이 과정에서 GameState 코드를 검토하던 중 팀원이 작성해 둔 bIsPaused 변수를 발견했습니다.

  • 게임 일시정지 상태 → bIsPaused = true
  • 메인 메뉴 상태 → bIsPaused = false (기본값)

이 구조를 활용하면 옵션 메뉴에서 '뒤로 가기' 버튼 클릭 시, 퍼즈 메뉴와 메인 메뉴 중 어디로 돌아갈지 자연스럽게 결정할 수 있었습니다. 결과적으로 기존 구조를 최대한 활용하여 간단하고 명확하게 메뉴 전환 흐름을 구현했습니다.


4. 🧠 GameState 구조 점검 및 위젯 포인터 연결

퍼즈 메뉴와 옵션 메뉴를 관리하기 위해 GameState 쪽 구조를 정리했습니다.

  • 옵션 메뉴 위젯 생성 시 if (OptionMenuWidgetClass && OptionMenuWidget == nullptr) 조건을 걸어, 위젯 블루프린트가 유효하고 중복 생성되지 않도록 했습니다.
  • 하지만 메뉴를 닫을 때 RemoveFromParent()만 호출하고 포인터를 초기화하지 않으면, 위젯은 사라졌어도 포인터가 남아 있어 다시 메뉴를 열 때 OptionMenuWidget == nullptr 조건을 통과하지 못하는 문제가 발생했습니다.
  • 이 문제를 해결하기 위해, RemoveFromParent() 호출 이후 반드시 포인터를 nullptr로 세팅하여 안정성을 확보했습니다.

5. 💾 감도 저장 기능 도입 결정 → SaveGame 시스템 설계

감도 설정을 매번 초기화하지 않고 저장하기 위해 SaveGame 시스템을 도입했습니다.

  • USOTDSaveGame 클래스를 C++로 생성하고, 감도 값을 저장할 float 변수만 포함시켰습니다.
  • 저장용 SaveSensitivity(), 불러오기용 LoadSensitivity() 함수를 따로 만들었습니다.
UCLASS()
class USOTDSaveGame : public USaveGame
{
    GENERATED_BODY()

public:
    UPROPERTY(BlueprintReadWrite)
    float SavedSensitivity;
};

6. 🧩 GameInstance에 Save/Load 로직 통합

감도 설정을 전역적으로 관리하기 위해 GameInstance에 저장/불러오기 기능을 구현했습니다.
UI나 캐릭터 코드에서 직접 SaveGame에 접근하지 않고, GameInstance를 통해 간접적으로 제어할 수 있도록 했습니다.

// 감도 저장 함수
void UShadow_of_the_DesertGameInstance::SaveSensitivity(float InSensitivity)
{
    USOTDSaveGame* SaveGameObject = Cast<USOTDSaveGame>(UGameplayStatics::CreateSaveGameObject(USOTDSaveGame::StaticClass()));
    if (SaveGameObject)
    {
        SaveGameObject->SavedMouseSensitivity = InSensitivity;
        UGameplayStatics::SaveGameToSlot(SaveGameObject, TEXT("SettingsSlot"), 0);
    }
}

// 감도 불러오기 함수
float UShadow_of_the_DesertGameInstance::LoadSensitivity()
{
    if (UGameplayStatics::DoesSaveGameExist(TEXT("SettingsSlot"), 0))
    {
        USOTDSaveGame* Loaded = Cast<USOTDSaveGame>(UGameplayStatics::LoadGameFromSlot(TEXT("SettingsSlot"), 0));
        if (Loaded)
        {
            return Loaded->SavedMouseSensitivity;
        }
    }
    return 1.0f; // 저장된 값이 없으면 기본값
}

감도 값이 저장되어 있지 않은 경우에는 기본값으로 1.0f를 반환하도록 하여 예외 상황에도 안정적으로 동작하도록 설계했습니다.


7. 🔄 슬라이더 값 저장 및 캐릭터에 실시간 반영

슬라이더 조작 시 감도 값을 GameInstance에 저장하고, 동시에 플레이어 캐릭터에도 실시간으로 반영하도록 구성했습니다.

  • OnValueChanged 이벤트에서 SaveSensitivity()를 호출하여 현재 감도 값을 저장하고,
  • Get Player Controller → Get Controlled Pawn 순서로 캐릭터를 참조해 MouseSensitivity 변수에 바로 적용합니다.
  • 이를 통해 사용자가 슬라이더를 조작하면 즉시 회전 속도에 반영되는 피드백을 제공할 수 있습니다.

또한, 옵션 메뉴를 열었을 때 슬라이더에 저장된 값을 불러와 초기화할 수 있도록

  • 위젯의 Construct 이벤트에서 LoadSensitivity()를 호출하고,
  • 결과 값을 슬라이더의 SetValue()로 전달해 현재 설정값이 UI에 반영되도록 했습니다.

8. 🔢 감도 값 직접 입력 기능 추가 (슬라이더 ↔ 텍스트 박스 동기화)

슬라이더만으로는 정밀한 감도 조절이 어렵다고 판단해, 숫자를 직접 입력할 수 있도록 TextBox를 추가했습니다.

  • 텍스트 박스 이름: MouseSensitivityTextBox
  • 슬라이더 옆에 배치하여 감도 수치를 실시간으로 보여주고 직접 입력도 가능하게 구성

슬라이더 → 텍스트 박스 동기화:

  • 슬라이더의 OnValueChanged 이벤트에서 값을 FString으로 변환 후 TextBox.SetText() 호출
  • 슬라이더를 움직일 때 텍스트에도 실시간으로 숫자가 반영되도록 구성

텍스트 박스 → 슬라이더 반영:

  • 텍스트 박스의 OnTextCommitted 이벤트를 사용
  • FText → FString → Float 변환 후 Clamp(0.01~1.0) 범위 제한 적용
  • 텍스트 입력 후 슬라이더 값만 설정(SetValue)하면,
    슬라이더의 OnValueChanged 로직을 그대로 타기 때문에 감도 적용 및 저장까지 자동으로 처리됩니다.
    이 방식으로 중복 로직 없이 깔끔한 연동이 가능했습니다.

이 구조를 통해 사용자는 슬라이더로 조정하거나 직접 수치를 입력하는 방식 중 편한 쪽을 선택할 수 있고,
두 방식 모두 실시간으로 반영 및 저장되도록 구성했습니다.


✅ 정리

이 과정을 통해 FPS 게임의 핵심 기능인 마우스 감도 설정 시스템을 완성할 수 있었습니다.

https://youtu.be/-gCfcUZga64

 

UI 구성부터 입력 적용, 저장과 불러오기까지 하나씩 직접 구현해보면서 감도 설정 기능을 완성해봤습니다.
처음엔 단순히 슬라이더만 연결하는 줄 알았는데, 저장 구조나 분기 처리 등등 생각할 게 꽤 많더라고요.
앞으로 다른 설정(볼륨, 키 바인딩 등)도 이런 방식으로 하나씩 확장해볼 계획입니다.

 

 

 

 

 

 

Unreal UI #1 - 버튼 호버 효과 및 설정

Unreal UI #2 - 최적화 및 해상도 테스트

Unreal UI #3 - 버튼 애니메이션 효과

Unreal UI #4 - UI 레이아웃 및 최적화 정리

Unreal UI #5 - 블러 및 페이드 효과 적용

Unreal UI #6 - 기본 HUD 추가 & 새로운 UI 구현

Unreal UI #7 - 전투 UI 개선: 부드러운 체력바, 히트마커, 데미지 텍스트 애니메이션

Unreal UI #8 - 데미지 텍스트 색상 · 위치 · 연속표시 문제 해결기

Unreal UI #9 - FPS 게임 감도 설정 시스템 구축기

Unreal UI #10 - 설정 탭 UI 시스템 구성 가이드