Unreal's Focusing System
언리얼 엔진의 Slate/UMG 포커싱 시스템은 User Focus(사용자 포커스)라는 개념을 기반으로 합니다. 여기서 Slate User(슬레이트 유저)라 불리는 각 사용자는 한 번에 하나의 Slate Widget(슬레이트 위젯)에만 포커스를 둘 수 있습니다.
여러 Slate User가 동일한 위젯에 포커스를 둘 수는 있지만, 한 사용자가 동시에 두 개의 위젯에 포커스를 둘 수는 없습니다(이 기능이 필요하다면 직접 커스텀 포커싱 시스템을 구현해야 하며, 이는 엔진의 기본 동작과 충돌할 수 있습니다).
User Focus는 Slate Application이 각 Slate User에 대해 정수 인덱스와 현재 포커스된 Slate Widget의 경로(Widget Focus Path)로 추적합니다. 이 Slate User들은 로컬(분할 화면 등)에서 동작하는 사용자들을 의미합니다. 포커스 상태는 레벨/맵 이동 시에도 유지되므로, 일반적으로 맵 전환 시에는 포커스를 게임 뷰포트로 리셋하는 것이 좋습니다.
언리얼 엔진의 포커싱 시스템은 다음 4가지 핵심 요소로 구성됩니다:
- Slate Application: 현재 어떤 위젯이 포커스되어 있는지 추적하고, 포커스 변경 시 알림을 제공하며, 입력이 발생했을 때 포커싱 시스템에 알립니다.
- HittestGrid(클래스명 오타 주의: HitTestGrid가 아님):
FindNextFocusableWidget 함수를 통해 다음으로 포커스할 수 있는 위젯을 찾고, 그 결과를 Slate Application에 반환합니다.
- SWidget: 모든 위젯의 기본 클래스이며, 다음과 같은 기능을 오버라이드할 수 있습니다:
OnKeyDown: 이 위젯이 포커스된 상태에서 키가 눌렸을 때 동작을 정의합니다.
OnNavigation: 위젯이 포커스를 얻거나 잃을 때 등 내비게이션 관련 동작을 정의합니다.
- Navigation Config: 입력에 따라 어떤 내비게이션 방향(상/하/좌/우 등)을 사용할지 결정합니다.
Navigation Grid Explanation
히트 테스트 그리드를 시각화하려면 콘솔 명령어 Slate.HitTestGridDebugging [0/1]을 사용하세요. 이 명령어는 Development & Debug Tools for UMG/Slate 섹션의 Debug Console Commands에도 나와 있습니다.
히트 테스트 그리드는 내비게이션 처리를 위한 핵심 구조로, 각 사용자별로 별도의 Hittest Grid가 존재합니다. 내비게이션은 지정된 방향(예: 위/아래/좌/우)으로만 계산되며, 128 해상도의 그리드 셀을 따라 직선으로 탐색합니다(이 값은 변경할 필요가 거의 없습니다). 각 셀에는 히트 테스트에 참여한 위젯의 충돌 경계(Desired Size와 Geometry에 기반)가 배치됩니다.
이 예시에서는 내비게이션 방향 입력 시 어떤 버튼으로 이동할 수 있는지 보여줍니다. Button 2는 아래쪽에 있는 가장 가까운 위젯으로 항상 내비게이션됩니다.

Slate.HitTestGridDebugging 1 콘솔 명령어로 실제 내비게이션 그리드 디버그 뷰를 확인한 예시입니다.
Navigation Genesis(내비게이션 발생)이 일어나면, 현재 포커스된 위젯이 위치한 셀을 기준으로 내비게이션 방향에 따라 직선으로 셀을 탐색하며, 각 셀에 포함된 위젯의 경계 박스가 아래 조건을 통과하는지 순서대로 검사합니다. 조건을 통과하지 못하면 해당 위젯은 건너뜁니다:
- Does Not Intersect: 위젯의 경계 사각형이 탐색 경로와 교차하지 않으면 제외.
- Previous Widget Is Better: 이전에 찾은 위젯이 더 가까우면 제외(항상 가장 가까운 위젯을 우선).
- Not A Descendent: 위젯의 경계 내비게이션 규칙이 Escape가 아니고, 경계 조건 위젯의 자식이 아니면 제외(내비게이션 대상이 아님).
- Disabled: 위젯이 비활성화 상태면 제외.
- Does Not Support Keyboard Focus: 키보드 포커스를 지원하지 않으면 제외.
이 검사를 모두 통과한 위젯은 "Best Widget"으로 저장되고, 해당 위젯의 Slate Rect(내비게이션에 사용될 사각형 정보)도 함께 저장됩니다. 이후 위젯의 경계 내비게이션 규칙(Explicit, Custom, CustomBoundary, Stop, Wrap 등)에 따라 추가 처리가 이루어집니다. 탐색이 화면의 가장자리까지 도달하면, 최종적으로 가장 적합한 위젯에 포커스가 이동합니다.
또한, 포커스가 변경될 때마다(특정 사용자만 필터링 가능) Slate Application의 FocusChangingDelegate(버전에 따라 OnFocusChanging() 함수로 접근)를 통해 이벤트를 수신할 수 있습니다. 이 델리게이트의 FFocusEvent에는 포커스를 변경한 사용자 인덱스와 포커스 변경 원인이 포함되어 있으므로, 필요에 따라 필터링하여 사용할 수 있습니다.
오른쪽으로 스크롤하면 헤더 파일을 볼 수 있습니다 →
| 소스 파일(.cpp) |
헤더 파일(.h) |
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
// Slate Application이 초기화되었는지 확인
if(FSlateApplication::IsInitialized())
{
// 언리얼 엔진 버전에 따라 "FSlateApplication::Get().FocusChangingDelegate"를 직접 사용할 수도 있음
// 포커스가 변경될 때 바인딩, 문제가 있다면 언리얼의 델리게이트 프레임워크를 학습하거나
// Slate Application의 헤더/소스에서 FocusChangingDelegate 관련 내용을 참고하세요
FSlateApplication::Get().OnFocusChanging().AddUObject(this, &AMyPlayerController::FocusChanged);
}
}
void AMyPlayerController::FocusChanged(const FFocusEvent& FocusEvent, const FWeakWidgetPath& OldFocusedWidgetPath,
const TSharedPtr<SWidget>& OldFocusedWidget, const FWidgetPath& NewFocusedWidgetPath,
const TSharedPtr<SWidget>& NewFocusedWidget)
{
// 이 플레이어 컨트롤러에 유효한 Local Player 객체가 있는지 확인
if(!IsValid(GetLocalPlayer()))
{
// 아직 Local Player 객체가 세팅되지 않았다면 함수 종료
return;
}
// 포커스를 변경한 사용자가 본인인지 확인
if(GetLocalPlayer()->GetControllerId() != FocusEvent.GetUser())
{
// 본인이 아닌 경우 함수 종료
return;
}
// 원하는 동작 수행
}
|
UCLASS()
class MYGAME_API AMyPlayerController : public APlayerController
{
GENERATED_BODY()
protected:
// AActor 인터페이스
virtual void BeginPlay() override;
// ~AActor 인터페이스
/**
* Slate User가 위젯 포커스를 변경할 때 호출됩니다.
* @param FocusEvent 발생한 포커스 이벤트 정보
* @param OldFocusedWidgetPath 이전에 포커스된 위젯의 경로
* @param OldFocusedWidget 이전에 포커스된 Slate 위젯
* @param NewFocusedWidgetPath 새로 포커스될 Slate 위젯의 경로
* @param NewFocusedWidget 새로 포커스될 Slate 위젯
*/
virtual void FocusChanged(const FFocusEvent& FocusEvent,
const FWeakWidgetPath& OldFocusedWidgetPath,
const TSharedPtr<SWidget>& OldFocusedWidget,
const FWidgetPath& NewFocusedWidgetPath,
const TSharedPtr<SWidget>& NewFocusedWidget);
};
|
Navigation Genesis
내비게이션은 Navigation Genesis라 불리는 3가지 유형에 의해 발생할 수 있습니다:
- Keyboard: 키보드 입력에 의해 내비게이션 이벤트가 발생한 경우입니다.
- Controller: 게임패드 입력에 의해 내비게이션 이벤트가 발생한 경우입니다.
- User: 게임 코드, 위젯 등에서 직접 생성한 사용자 정의 내비게이션 이벤트입니다.