콘텐츠로 이동

UE5 Common UI — 플랫폼 통합 UI 시스템

Common UI는 Epic Games가 Fortnite에서 추출한 멀티플랫폼 UI 프레임워크입니다. PC/콘솔/모바일에서 동일한 위젯 코드로 플랫폼별 입력(마우스, 게임패드, 터치)을 처리하고 포커스 내비게이션, 백 버튼, 액션 버튼을 일관되게 관리합니다.

기존 UMG는 “어떤 버튼이 눌렸는지”를 위젯마다 직접 처리해야 했습니다. Common UI는 입력을 태그 기반 액션으로 추상화해 플랫폼이 바뀌어도 위젯 코드를 수정할 필요가 없습니다.


Edit → Plugins → UI → Common UI Plugin → Enable

빌드 모듈:

Build.cs
PublicDependencyModuleNames.AddRange(new[]
{
"CommonUI",
"CommonInput",
"GameplayTags",
"UMG",
});

프로젝트 설정에서 UI 레이어 등록:

Project Settings → Game → Common UI Framework
→ Registered UI Layers 항목에 GameplayTag 레이어 추가

2. CommonActivatableWidget — 화면 스택

섹션 제목: “2. CommonActivatableWidget — 화면 스택”

일반 UUserWidget 대신 UCommonActivatableWidget을 사용하면 화면 스택이 자동 관리됩니다. 위젯을 Push하면 이전 위젯 위에 올라가고, Deactivate하면 자동으로 팝됩니다.

MainMenuWidget.h
#pragma once
#include "CommonActivatableWidget.h"
#include "MainMenuWidget.generated.h"
UCLASS()
class MYGAME_API UMainMenuWidget : public UCommonActivatableWidget
{
GENERATED_BODY()
UPROPERTY(meta = (BindWidget))
class UCommonButtonBase* PlayButton;
UPROPERTY(meta = (BindWidget))
class UCommonButtonBase* SettingsButton;
// 위젯 오픈 애니메이션 (BP에서 연결)
UPROPERTY(Transient, meta = (BindWidgetAnim))
UWidgetAnimation* OpenAnimation;
protected:
virtual void NativeOnInitialized() override;
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
virtual void NativeOnBackAction() override;
// 포커스 시작 위젯 지정 (게임패드 자동 포커스)
virtual UWidget* NativeGetDesiredFocusTarget() const override;
};
MainMenuWidget.cpp
void UMainMenuWidget::NativeOnInitialized()
{
Super::NativeOnInitialized();
// 버튼 클릭 바인딩
if (PlayButton)
PlayButton->OnClicked().AddUObject(this, &UMainMenuWidget::OnPlayClicked);
if (SettingsButton)
SettingsButton->OnClicked().AddUObject(this, &UMainMenuWidget::OnSettingsClicked);
}
void UMainMenuWidget::NativeOnActivated()
{
Super::NativeOnActivated();
PlayAnimation(OpenAnimation); // 진입 애니메이션
}
void UMainMenuWidget::NativeOnDeactivated()
{
Super::NativeOnDeactivated();
// 리소스 해제, 애니메이션 중단 등
}
void UMainMenuWidget::NativeOnBackAction()
{
// ESC 키 / 게임패드 B 버튼 → 이전 화면으로
DeactivateWidget(); // 스택에서 팝
}
UWidget* UMainMenuWidget::NativeGetDesiredFocusTarget() const
{
// 게임패드 연결 시 첫 포커스 위젯
return PlayButton;
}
void UMainMenuWidget::OnSettingsClicked()
{
// 설정 화면을 현재 레이어 스택에 푸시
if (auto* PC = GetOwningPlayer())
{
UCommonUIExtensions::PushContentToLayer_ForPlayer(
GetOwningLocalPlayer(),
FGameplayTag::RequestGameplayTag("UI.Layer.Menu"),
USettingsWidget::StaticClass());
}
}

레이어 구조와 스택 Push/Pop 패턴입니다.

[UI 레이어 구조]
UI.Layer.Modal ← 최상단 (확인 다이얼로그)
UI.Layer.Menu ← 메뉴 화면 스택 (일시정지, 인벤토리...)
UI.Layer.HUD ← 항상 표시 (체력바, 미니맵)
// PlayerController에서 위젯 스택 관리
#include "CommonUIExtensions.h"
// 일시정지 메뉴 열기
void AMyPlayerController::OpenPauseMenu()
{
// 위젯 스택에 푸시 — 이전 위젯은 뒤로 밀림
UCommonUIExtensions::PushContentToLayer_ForPlayer(
GetLocalPlayer(),
FGameplayTag::RequestGameplayTag("UI.Layer.Menu"),
UPauseMenuWidget::StaticClass());
}
// 확인 다이얼로그 표시
void AMyPlayerController::ShowConfirmDialog(
const FText& Message,
FSimpleDelegate OnConfirm)
{
// Modal 레이어 → 입력이 다이얼로그로 집중됨
auto* Dialog = UCommonUIExtensions::PushContentToLayer_ForPlayer<UConfirmDialog>(
GetLocalPlayer(),
FGameplayTag::RequestGameplayTag("UI.Layer.Modal"),
UConfirmDialog::StaticClass());
if (Dialog)
{
Dialog->SetMessage(Message);
Dialog->OnConfirmed.AddLambda([OnConfirm]() { OnConfirm.ExecuteIfBound(); });
}
}
// 특정 레이어의 최상단 위젯 강제 닫기
void AMyPlayerController::CloseTopMenu()
{
UCommonUIExtensions::PopContentFromLayer_ForPlayer(
GetLocalPlayer(),
FGameplayTag::RequestGameplayTag("UI.Layer.Menu"));
}

위젯에서 자신이 현재 최상단인지 확인:

void UMyWidget::NativeOnActivated()
{
Super::NativeOnActivated();
// 게임패드 포커스를 이 위젯으로 이동
// (CommonUI가 스택 최상단에 자동 처리하지만 수동 제어 시)
if (UCommonInputSubsystem* IS =
UCommonInputSubsystem::Get(GetOwningLocalPlayer()))
{
SetUserFocus(GetOwningPlayer());
}
}

4. CommonButton — 플랫폼별 스타일

섹션 제목: “4. CommonButton — 플랫폼별 스타일”

UCommonButtonBase는 스타일 에셋 기반으로 PC/콘솔/모바일에서 다른 시각적 표현을 자동으로 처리합니다.

// ActionButton.h — 게임패드 힌트 아이콘이 자동 표시되는 버튼
#include "CommonButtonBase.h"
UCLASS()
class MYGAME_API UActionButton : public UCommonButtonBase
{
GENERATED_BODY()
// 버튼에 표시할 레이블
UPROPERTY(EditAnywhere, Category="Button")
FText ButtonLabel;
// 버튼과 연결된 입력 액션 태그
// → 게임패드 연결 시 대응하는 버튼 아이콘이 자동으로 표시됨
UPROPERTY(EditAnywhere, Category="Button")
FGameplayTag BoundInputAction;
protected:
virtual void NativePreConstruct() override
{
Super::NativePreConstruct();
// 에디터 프리뷰에서 레이블 업데이트
SetButtonText(ButtonLabel);
}
virtual void NativeOnClicked() override
{
Super::NativeOnClicked();
// 클릭 효과음
PlayClickSound();
}
virtual void NativeOnHovered() override
{
Super::NativeOnHovered();
// 호버 피드백
}
};

버튼 스타일 설정:

Content Browser → 우클릭 → User Interface → Common Button Style
→ 상태별 스타일 설정:
Normal / Hovered / Pressed / Disabled
각 상태에서 Brush, 텍스트 색상, 패딩 지정
→ CommonButtonStyleBase 에셋에서 플랫폼별 분기 가능

Common UI의 입력 바인딩은 Enhanced Input과 별개로 위젯 스코프 내에서만 동작합니다. 위젯이 활성화된 동안만 입력을 소비해 게임플레이 입력과 충돌하지 않습니다.

InventoryWidget.h
#include "CommonActivatableWidget.h"
#include "Input/CommonBoundActionButton.h"
UCLASS()
class UInventoryWidget : public UCommonActivatableWidget
{
GENERATED_BODY()
// 하단 힌트 버튼 표시용
UPROPERTY(meta = (BindWidget))
class UCommonBoundActionBar* ActionBar;
protected:
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
private:
FUIActionBindingHandle SortHandle;
FUIActionBindingHandle DropHandle;
FUIActionBindingHandle EquipHandle;
};
InventoryWidget.cpp
void UInventoryWidget::NativeOnActivated()
{
Super::NativeOnActivated();
// 게임패드 Y버튼 → 정렬
// FBindUIActionArgs: 태그, 람다/함수, 표시 우선순위
FBindUIActionArgs SortArgs(
FUIActionTag::ConvertChecked(
FGameplayTag::RequestGameplayTag("CommonInput.Menu.Sort")),
false, // bDisplayInActionBar
FSimpleDelegate::CreateUObject(this, &UInventoryWidget::SortItems));
SortArgs.bDisplayInActionBar = true;
SortHandle = RegisterUIActionBinding(SortArgs);
// 게임패드 X버튼 → 버리기
FBindUIActionArgs DropArgs(
FUIActionTag::ConvertChecked(
FGameplayTag::RequestGameplayTag("CommonInput.Menu.Drop")),
false,
FSimpleDelegate::CreateUObject(this, &UInventoryWidget::DropItem));
DropArgs.bDisplayInActionBar = true;
DropHandle = RegisterUIActionBinding(DropArgs);
// 게임패드 A버튼 → 장착
FBindUIActionArgs EquipArgs(
FUIActionTag::ConvertChecked(
FGameplayTag::RequestGameplayTag("CommonInput.Menu.Confirm")),
false,
FSimpleDelegate::CreateUObject(this, &UInventoryWidget::EquipSelected));
EquipHandle = RegisterUIActionBinding(EquipArgs);
}
void UInventoryWidget::NativeOnDeactivated()
{
// 위젯 비활성화 시 바인딩 해제 (메모리 누수 방지)
SortHandle.Unregister();
DropHandle.Unregister();
EquipHandle.Unregister();
Super::NativeOnDeactivated();
}

6. CommonTextBlock — 자동 텍스트 스타일

섹션 제목: “6. CommonTextBlock — 자동 텍스트 스타일”
// 스타일 에셋 기반 텍스트 (폰트, 크기, 색상 일괄 관리)
UPROPERTY(meta = (BindWidget))
UCommonTextBlock* TitleText;
UPROPERTY(meta = (BindWidget))
UCommonTextBlock* SubtitleText;
// CommonTextStyle 에셋 레퍼런스
UPROPERTY(EditDefaultsOnly, Category="Style")
TSubclassOf<UCommonTextStyle> TitleStyleClass;
void UMyWidget::NativeConstruct()
{
Super::NativeConstruct();
// 스타일 에셋 할당 — 에디터에서도 미리보기 반영
if (TitleStyleClass)
TitleText->SetStyle(TitleStyleClass);
TitleText->SetText(NSLOCTEXT("MyGame", "MainMenuTitle", "메인 메뉴"));
// 스크롤 설정 (긴 텍스트 자동 마퀴 스크롤)
SubtitleText->SetScrollingEnabled(true);
SubtitleText->SetScrollSpeed(50.f);
}

UCommonTextStyle 에셋에서 설정 가능한 항목:

  • Font: 폰트 페이스와 크기
  • Color: 기본/강조/비활성화 색상
  • Margins: 텍스트 여백
  • Line Height Percentage: 줄 간격

입력 방식이 변경될 때(예: 게임패드 연결/해제) UI를 동적으로 업데이트하는 패턴입니다.

#include "CommonInputSubsystem.h"
#include "CommonInputTypeEnum.h"
UCLASS()
class UHUDWidget : public UCommonActivatableWidget
{
GENERATED_BODY()
// 게임패드 힌트 패널 (버튼 아이콘 표시)
UPROPERTY(meta = (BindWidget))
UPanelWidget* GamepadHintPanel;
// 키보드/마우스 힌트 패널
UPROPERTY(meta = (BindWidget))
UPanelWidget* KeyboardHintPanel;
protected:
virtual void NativeConstruct() override
{
Super::NativeConstruct();
if (auto* IS = UCommonInputSubsystem::Get(GetOwningLocalPlayer()))
{
// 입력 방식 변경 감지 (마우스 ↔ 게임패드 ↔ 터치)
IS->OnInputMethodChangedNative.AddUObject(
this, &UHUDWidget::OnInputMethodChanged);
// 초기 상태 즉시 반영
OnInputMethodChanged(IS->GetCurrentInputType());
}
}
virtual void NativeDestruct() override
{
if (auto* IS = UCommonInputSubsystem::Get(GetOwningLocalPlayer()))
IS->OnInputMethodChangedNative.RemoveAll(this);
Super::NativeDestruct();
}
private:
void OnInputMethodChanged(ECommonInputType NewInputType)
{
const bool bIsGamepad = (NewInputType == ECommonInputType::Gamepad);
const bool bIsTouch = (NewInputType == ECommonInputType::Touch);
// 힌트 패널 전환
GamepadHintPanel->SetVisibility(
bIsGamepad ? ESlateVisibility::SelfHitTestInvisible
: ESlateVisibility::Collapsed);
KeyboardHintPanel->SetVisibility(
(!bIsGamepad && !bIsTouch) ? ESlateVisibility::SelfHitTestInvisible
: ESlateVisibility::Collapsed);
// 크로스헤어 크기 조정 (게임패드는 더 크게)
CrosshairImage->SetDesiredSizeOverride(
bIsGamepad ? FVector2D(24.f, 24.f) : FVector2D(16.f, 16.f));
}
};

콘솔 전용 UI 분기:

// 플랫폼 타입 직접 쿼리
bool IsConsole()
{
#if PLATFORM_PS5 || PLATFORM_XSX
return true;
#else
return false;
#endif
}
// 또는 런타임 감지 (개발 중 에디터에서 시뮬레이션 가능)
ECommonInputType GetPlatformDefaultInput()
{
if (UCommonInputPlatformSettings* PlatformSettings =
UCommonInputPlatformSettings::Get())
{
return PlatformSettings->GetDefaultInputType();
}
return ECommonInputType::MouseAndKeyboard;
}

UI.Layer.Modal ← 확인 다이얼로그 (블로킹, 최상단 입력 독점)
UI.Layer.Menu ← 일시정지, 인벤토리, 설정 (스택)
UI.Layer.Game ← 게임 중 오버레이 (컷신 자막 등)
UI.Layer.HUD ← 체력바, 미니맵 (항상 표시, 스택 없음)

레이어를 DefaultGame.ini에 등록:

[/Script/CommonUI.CommonUISettings]
+RegisteredUILayers=(LayerTag=(TagName="UI.Layer.HUD"), LayerZOrder=0)
+RegisteredUILayers=(LayerTag=(TagName="UI.Layer.Game"), LayerZOrder=1)
+RegisteredUILayers=(LayerTag=(TagName="UI.Layer.Menu"), LayerZOrder=2)
+RegisteredUILayers=(LayerTag=(TagName="UI.Layer.Modal"), LayerZOrder=3)

레이어 간 입력 포커스 규칙:

Modal 레이어가 열려 있으면:
→ 하위 레이어의 버튼/입력 모두 차단
→ 게임패드 포커스도 Modal 위젯으로 고정
Menu 레이어가 열려 있으면:
→ HUD는 렌더링은 되지만 입력 수신 안 함
→ 게임 입력(이동, 사격 등)도 차단됨

위젯 풀링: PushContentToLayer는 매번 새 위젯을 생성합니다. 자주 열리는 위젯(인벤토리 등)은 UCommonActivatableWidgetContainerBase::GetActiveWidget()으로 재사용 여부를 확인하세요.

게임패드 포커스 손실: 스택에서 위젯을 팝한 뒤 포커스가 올바른 위젯으로 돌아가지 않는 경우, NativeGetDesiredFocusTarget()을 이전 위젯에서 명시적으로 구현하세요.

입력 블로킹: UCommonActivatableWidgetbIsFocusable = true(기본값)이면 위젯이 열린 동안 아래 레이어의 게임플레이 입력이 차단됩니다. 순수 오버레이(데미지 텍스트 등)는 bIsFocusable = false로 설정하세요.

멀티플레이어 스플릿 스크린: PushContentToLayer_ForPlayerLocalPlayer 파라미터로 각 플레이어의 UI 스택을 독립적으로 관리할 수 있습니다.


Common UI는 플랫폼별 입력 처리와 포커스 내비게이션을 추상화해 PC/콘솔/모바일에서 하나의 위젯 코드로 동작하는 UI를 만들 수 있게 합니다. CommonActivatableWidget 스택으로 화면 전환을 관리하고, RegisterUIActionBinding으로 게임패드 액션을 위젯 스코프에 바인딩하세요. UCommonInputSubsystem::OnInputMethodChangedNative를 구독해 입력 방식 전환에 즉각 반응하는 UI를 구현하면 PC와 콘솔 모두에서 완성도 높은 UX를 제공할 수 있습니다.