포커스 시스템
Slate/UMG의 위젯 포커스 시스템, 네비게이션 규칙, 커스텀 포커스 동작을 학습합니다.
포커스 기본 개념
Slate에서 포커스가 작동하는 원리
Slate에서 한 번에 하나의 위젯만 포커스를 가질 수 있습니다. 포커스된 위젯이 키보드/게임패드 입력을 우선적으로 받습니다. 포커스는 사용자(User) 단위로 관리되어 로컬 멀티플레이어를 지원합니다.
// 특정 위젯에 포커스 설정
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;
}
포커스(Focus)는 키보드/게임패드 입력의 대상이고, 히트 테스트(Hit Test)는 마우스/터치 입력의 대상입니다. 마우스 사용 시에는 히트 테스트가 주로 작동하고, 게임패드 사용 시에는 포커스가 핵심입니다. 이 구분이 멀티 플랫폼 UI 설계의 기본입니다.
포커스 네비게이션
방향 키로 위젯 간 이동하는 네비게이션 시스템
// 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;
}
CommonUI 자동 포커스
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() → 이전 위젯의 포커스 자동 복원
포커스 디버깅
포커스 문제를 진단하는 도구와 기법
// 현재 포커스된 위젯 표시
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"));
});
핵심 요약
- Slate에서 한 번에 하나의 위젯만 포커스를 가지며, 포커스된 위젯이 키보드/게임패드 입력을 받습니다
- 네비게이션 규칙으로 Escape, Stop, Wrap, Explicit, Custom을 설정하여 방향 키 이동을 제어합니다
- CommonUI는
NativeGetDesiredFocusTarget()으로 자동 포커스 관리를 제공합니다 - Widget Reflector와 Slate 디버그 명령으로 포커스 문제를 진단할 수 있습니다
도전 과제
배운 내용을 직접 실습해보세요
Widget Blueprint에서 IsFocusable 설정과 Navigation 규칙(Explicit, Custom, Stop)을 설정하여 버튼 그리드에서 D-Pad 방향키로 올바르게 탐색할 수 있도록 구성하세요.
C++에서 SetNavigationRuleCustom()을 사용하여 리스트 끝에서 처음으로 순환하는 Wrap-around 네비게이션과, 특정 조건에서 포커스를 차단하는 커스텀 규칙을 구현하세요.
현재 포커스된 위젯을 화면에 시각적으로 표시하고, 포커스 이동 이력을 로그로 출력하는 디버그 오버레이를 구현하세요. FSlateApplication::Get().GetUserFocusedWidget()을 활용합니다.