PART 5 · 강의 2/3

포커스 시스템

Slate/UMG의 위젯 포커스 시스템, 네비게이션 규칙, 커스텀 포커스 동작을 학습합니다.

01

포커스 기본 개념

Slate에서 포커스가 작동하는 원리

Slate에서 한 번에 하나의 위젯만 포커스를 가질 수 있습니다. 포커스된 위젯이 키보드/게임패드 입력을 우선적으로 받습니다. 포커스는 사용자(User) 단위로 관리되어 로컬 멀티플레이어를 지원합니다.

C++ - 포커스 제어
// 특정 위젯에 포커스 설정 FSlateApplication::Get().SetKeyboardFocus( MyButton->TakeWidget()); // UMG에서 포커스 설정 MyButton->SetKeyboardFocus(); // 현재 포커스된 위젯 확인 TSharedPtr<SWidget> FocusedWidget = FSlateApplication::Get().GetKeyboardFocusedWidget(); // 포커스 가능 여부 설정 (UMG) MyWidget->SetIsFocusable(true); // Slate에서 포커스 가능 여부 virtual bool SupportsKeyboardFocus() const override { return true; }
포커스 vs Hit Test

포커스(Focus)는 키보드/게임패드 입력의 대상이고, 히트 테스트(Hit Test)는 마우스/터치 입력의 대상입니다. 마우스 사용 시에는 히트 테스트가 주로 작동하고, 게임패드 사용 시에는 포커스가 핵심입니다. 이 구분이 멀티 플랫폼 UI 설계의 기본입니다.

02

포커스 네비게이션

방향 키로 위젯 간 이동하는 네비게이션 시스템

C++ - 네비게이션 규칙 설정
// UMG에서 위젯별 네비게이션 규칙 설정 // Navigation 프로퍼티: // Left, Right, Up, Down 방향에 대해 // Escape: 이 방향으로 나갈 수 있음 // Stop: 이 방향으로 이동 불가 // Wrap: 끝에서 처음으로 순환 // Explicit: 특정 위젯으로 명시적 이동 // Custom: 커스텀 함수로 결정 // C++에서 네비게이션 설정 void UMyMenu::NativeConstruct() { Super::NativeConstruct(); // 버튼 간 명시적 네비게이션 설정 StartButton->SetNavigationRuleExplicit( EUINavigation::Down, SettingsButton); SettingsButton->SetNavigationRuleExplicit( EUINavigation::Up, StartButton); SettingsButton->SetNavigationRuleExplicit( EUINavigation::Down, QuitButton); // 네비게이션 중단 (아래로 더 이동 불가) QuitButton->SetNavigationRule( EUINavigation::Down, EUINavigationRule::Stop); } // 커스텀 네비게이션 (동적 결정) StartButton->SetNavigationRuleCustom( EUINavigation::Right, FCustomWidgetNavigationDelegate::CreateUObject( this, &UMyMenu::GetRightNavTarget)); UWidget* UMyMenu::GetRightNavTarget(EUINavigation Direction) { // 조건에 따라 다른 위젯으로 이동 return bHasSubMenu ? SubMenuFirstButton : nullptr; }
03

CommonUI 자동 포커스

CommonUI의 자동 포커스 관리 메커니즘

C++ - CommonUI 포커스 관리
// CommonUI Activatable Widget의 자동 포커스 class UOptionsMenu : public UCommonActivatableWidget { protected: // 위젯 활성화 시 자동으로 호출 // 포커스를 받을 대상 위젯을 반환 virtual UWidget* NativeGetDesiredFocusTarget() const override { // 이전에 선택했던 항목이 있으면 그곳으로 if (LastSelectedButton) return LastSelectedButton; // 없으면 첫 번째 버튼으로 return GraphicsButton; } UPROPERTY(meta = (BindWidget)) UCommonButtonBase* GraphicsButton; UPROPERTY(meta = (BindWidget)) UCommonButtonBase* AudioButton; UPROPERTY() UCommonButtonBase* LastSelectedButton = nullptr; }; // CommonUI 포커스 흐름: // 1. PushWidget() → NativeOnActivated() // 2. 게임패드 입력 감지 시 NativeGetDesiredFocusTarget() 호출 // 3. 반환된 위젯에 자동으로 포커스 설정 // 4. DeactivateWidget() → 이전 위젯의 포커스 자동 복원
04

포커스 디버깅

포커스 문제를 진단하는 도구와 기법

콘솔 명령 - 포커스 디버깅
// 현재 포커스된 위젯 표시 Slate.Debug.FocusedWidget // 포커스 변경 로깅 Slate.EnableFocusTracking 1 // 네비게이션 디버그 시각화 Slate.Debug.NavigationEnabled 1 // 위젯 리플렉터 (Slate 위젯 트리 인스펙터) // Window > Developer Tools > Widget Reflector // C++ 디버깅: 포커스 변경 이벤트 구독 FSlateApplication::Get().OnFocusChanging().AddLambda( [](const FFocusEvent& Event, const FWeakWidgetPath& OldPath, const TSharedPtr<SWidget>& OldWidget, const FWidgetPath& NewPath, const TSharedPtr<SWidget>& NewWidget) { UE_LOG(LogUI, Log, TEXT("Focus: %s -> %s"), OldWidget ? *OldWidget->ToString() : TEXT("None"), NewWidget ? *NewWidget->ToString() : TEXT("None")); });
SUMMARY

핵심 요약

  • Slate에서 한 번에 하나의 위젯만 포커스를 가지며, 포커스된 위젯이 키보드/게임패드 입력을 받습니다
  • 네비게이션 규칙으로 Escape, Stop, Wrap, Explicit, Custom을 설정하여 방향 키 이동을 제어합니다
  • CommonUI는 NativeGetDesiredFocusTarget()으로 자동 포커스 관리를 제공합니다
  • Widget Reflector와 Slate 디버그 명령으로 포커스 문제를 진단할 수 있습니다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 위젯 포커스 네비게이션 설정

Widget Blueprint에서 IsFocusable 설정과 Navigation 규칙(Explicit, Custom, Stop)을 설정하여 버튼 그리드에서 D-Pad 방향키로 올바르게 탐색할 수 있도록 구성하세요.

실습 2: 커스텀 포커스 동작 구현

C++에서 SetNavigationRuleCustom()을 사용하여 리스트 끝에서 처음으로 순환하는 Wrap-around 네비게이션과, 특정 조건에서 포커스를 차단하는 커스텀 규칙을 구현하세요.

심화 과제

현재 포커스된 위젯을 화면에 시각적으로 표시하고, 포커스 이동 이력을 로그로 출력하는 디버그 오버레이를 구현하세요. FSlateApplication::Get().GetUserFocusedWidget()을 활용합니다.