🎮 언리얼 엔진 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 게임의 핵심 기능인 마우스 감도 설정 시스템을 완성할 수 있었습니다.
UI 구성부터 입력 적용, 저장과 불러오기까지 하나씩 직접 구현해보면서 감도 설정 기능을 완성해봤습니다.
처음엔 단순히 슬라이더만 연결하는 줄 알았는데, 저장 구조나 분기 처리 등등 생각할 게 꽤 많더라고요.
앞으로 다른 설정(볼륨, 키 바인딩 등)도 이런 방식으로 하나씩 확장해볼 계획입니다.
Unreal UI #4 - UI 레이아웃 및 최적화 정리
Unreal UI #6 - 기본 HUD 추가 & 새로운 UI 구현
Unreal UI #7 - 전투 UI 개선: 부드러운 체력바, 히트마커, 데미지 텍스트 애니메이션
Unreal UI #8 - 데미지 텍스트 색상 · 위치 · 연속표시 문제 해결기
'언리얼 엔진' 카테고리의 다른 글
Unreal Engine - RPG 전투 상태 관리 시스템 설계 및 초기 구현 (0) | 2025.05.08 |
---|---|
Unreal Character - 캐릭터 전투 시스템 정리 (0) | 2025.05.07 |
Unreal Engine - FBX 헬기 모델 병합 및 Skeletal Mesh 변환 과정 (0) | 2025.04.21 |
Unreal UI #8 - 데미지 텍스트 색상 · 위치 · 연속표시 문제 해결기 (1) | 2025.04.20 |
Unreal Engine - GC 추적과 델리게이트 바인딩에는 왜 UPROPERTY, UFUNCTION이 꼭 필요한가? (1) | 2025.04.19 |