이 문서는 CustomUI 플러그인을 실제 프로젝트에서 사용하는 사람을 위한 작업 가이드다. 코드 구조 설명보다 무엇을 만들고, 어디에 설정하고, 어떻게 열고 닫고, 어떤 규칙을 지켜야 하는지에 초점을 둔다.
CustomUI는 다음 단위로 UI를 구성한다.
| 이름 | 설명 |
|---|---|
| WidgetBase | Show/Hide 상태, 애니메이션, 사운드, 이벤트를 가진 기본 UserWidget |
| PopupBase | OpenPopup으로 열고 Close로 닫는 팝업 위젯 |
| Widget Registry | 자주 쓰는 공용 위젯을 FName으로 찾아 재사용하기 위한 목록 |
| WidgetSubsystem | LocalPlayer 단위로 Registry 위젯과 Popup 스택을 관리하는 Subsystem |
| WidgetHelper | Blueprint/C++에서 Registry, Popup, String Table 기능을 호출하는 헬퍼 |
| Button Components | Click, Toggle, Radio, RadioButtonGroup 기반 버튼 위젯 |
| TypingRichTextBlock | RichText 텍스트를 타이핑 효과로 표시하는 텍스트 블록 |
| MarqueeWidgetBase | 콘텐츠를 흐르게 표시하는 위젯 |
가장 흔한 흐름은 다음과 같다.
Project Settings 설정
-> Widget Blueprint가 UWidgetBase/UPopupBase 상속
-> 필요한 곳에서 UWidgetHelper::OpenPopup 또는 GetRegisteredWidget 호출
-> Show/Hide/Close와 이벤트로 UI 수명 주기 처리
Popup 시스템은 LocalPlayer의 PlayerController를 AWidgetPlayerController로 캐스팅해서 사용한다.
권장:
- 프로젝트 PlayerController Blueprint 또는 C++ 클래스가
AWidgetPlayerController를 상속하게 한다. - GameMode의 PlayerController Class에 이 클래스를 지정한다.
- PlayerController Details에서 초기 입력 모드를 설정한다.
주요 설정:
| 설정 | 추천 기준 |
|---|---|
| InitialInputMode | 게임 위주면 GameOnly, UI 조작이 필요하면 GameAndUI 또는 UIOnly |
| InMouseLockMode | UI 전용 화면이면 DoNotLock, 게임 화면이면 프로젝트 정책에 맞게 설정 |
| HideCursorDuringCapture | GameAndUI에서 마우스 캡처 중 커서를 숨길지 결정 |
| ConsumeCaptureMouseDown | GameOnly에서 첫 클릭 소비 여부 |
| ShowMouseCursor | 시작 시 커서 표시 여부 |
공용으로 재사용할 위젯이 있다면 Registry를 만든다.
- Content Browser에서
Data Asset을 생성한다. - 클래스 선택 창에서
WidgetRegistryDataAsset을 선택한다. _WidgetClassMap에 이름과 Widget Class를 등록한다.
예:
| Key | Widget Class |
|---|---|
EditableTextBox |
UI_EditableTextBox_BP |
CommonConfirmPopup |
UI_ConfirmPopup_BP |
주의:
- Key가
None이면 등록되지 않는다. - Widget Class가 비어 있으면 등록되지 않는다.
- Registry 위젯은 처음 조회할 때 생성되고 이후 재사용된다.
- Registry 위젯은 자동으로 Viewport에 올라가지 않는다. 필요한 위치에 직접
AddToViewport또는 Panel 추가를 수행한다.
Project Settings에서 CustomUI 설정을 찾고 다음을 지정한다.
| 설정 | 필수 | 설명 |
|---|---|---|
| WidgetRegistryDataAsset | Registry 기능 사용 시 필수 | 방금 만든 Registry Data Asset |
| StringTables | String Table 헬퍼 사용 시 필수 | GETTEXT/GetStringTableText로 조회할 String Table 목록 |
String Table은 에셋 이름으로 조회한다.
FText Text = UWidgetHelper::GetStringTableText(TEXT("ST_Office"), TEXT("Set_Nickname_IsEmpty"));또는 매크로를 사용할 수 있다.
FText Text = GETTEXT("ST_Office", "Set_Nickname_IsEmpty")주의: GETTEXT 매크로 정의에는 세미콜론이 포함되어 있다. 일반 대입문처럼 쓰는 용도에 맞춰 사용한다.
일반 UI는 WidgetBase를 부모로 만든다.
사용할 수 있는 이벤트:
OnShowOnIdleOnStartHideOnHideOnSynchronizeProperties
사용할 수 있는 함수:
ShowHideCloseGetWidgetStateGetWidgetID
WidgetBase는 다음 이름의 Widget Animation을 자동으로 바인딩한다.
| 애니메이션 이름 | 용도 |
|---|---|
ShowAnim |
Show 진입 시 재생 |
IdleAnim |
Show 완료 후 Idle 상태에서 재생 |
HideAnim |
Hide/Close 시 재생 |
모두 선택 사항이다. 없으면 상태가 즉시 다음 단계로 넘어간다.
직접 SetVisibility를 호출하지 말고 다음 함수를 사용한다.
MyWidget->Show(EWidgetShowType::Visible);
MyWidget->Hide(EWidgetHideType::Collapsed);
MyWidget->Close(); // RemoveFromParentBlueprint에서도 같은 규칙을 따른다.
| 하고 싶은 일 | 함수 |
|---|---|
| 보이기 | Show |
| 숨기되 Viewport/부모에는 남기기 | Hide(Collapsed) 또는 Hide(Hidden) |
| 닫고 제거하기 | Close |
| 애니메이션 없이 즉시 처리 | Show(..., true) / Hide(..., true) / Close(true) |
Show 순서:
Show 호출
-> OnShow / OnShowEvent
-> ShowAnim 재생
-> OnIdle / OnIdleEvent
-> IdleAnim 재생
Close 순서:
Close 호출
-> OnStartHide / OnStartHideEvent
-> HideAnim 재생
-> OnHide / OnHideEvent
-> RemoveFromParent
정리 작업 추천 위치:
| 작업 | 추천 이벤트 |
|---|---|
| 표시 직후 데이터 갱신 | OnShow |
| ShowAnim이 끝난 뒤 입력 허용 | OnIdle |
| 닫기 시작 시 입력 차단 | OnStartHide |
| 이벤트 바인딩 해제/참조 정리 | OnHide |
- Widget Blueprint를 만든다.
- 부모 클래스를
PopupBase로 지정한다. - Details에서
_PopupConfig를 설정한다.
Popup Config:
| 설정 | 설명 |
|---|---|
| IgnoreOtherPopup | 이 Popup이 최상단에 있으면 다른 Popup 열기를 막는다. 모달 팝업에 적합하다. |
| AllowDuplicate | 같은 Popup Class를 여러 개 열 수 있게 한다. |
| ShowMouseCursor | Popup이 열려 있는 동안 PlayerController 커서를 보일지 결정한다. |
| RemainOnLevelChanged | PlayerController 변경/레벨 변경 후 같은 Popup Class를 다시 연다. |
Blueprint에서 Open Popup 노드를 사용한다.
입력:
- World Context
- Popup Class
반환값이 유효하면 Popup이 이미 Viewport에 추가된 상태다.
닫을 때는 Popup 자기 자신에게 Close를 호출하거나 Close Popup 헬퍼를 사용한다.
#include "WidgetHelper.h"
#include "Widgets/PopupBase.h"
UPopupBase* Popup = UWidgetHelper::OpenPopup(this, PopupClass);
if (IsValid(Popup))
{
Popup->_OnHideEvent.AddDynamic(this, &UMyObject::OnPopupClosed);
}구체 타입으로 받고 싶으면 템플릿 오버로드를 사용할 수 있다.
UMyPopup* Popup = UWidgetHelper::OpenPopup<UMyPopup>(this, UMyPopup::StaticClass());UWidgetHelper::ClosePopup(Popup, false);또는:
Popup->Close(false);팝업 스택에서 제거되려면 Close를 사용하는 것이 안전하다. Hide(Collapsed)나 Hide(Hidden)은 Viewport에서 제거하지 않으므로 Popup 스택에 남을 수 있다.
Popup 열기가 실패할 수 있는 경우:
| 상황 | 결과 |
|---|---|
| Popup Class가 비어 있음 | 열기 실패 |
같은 Class가 이미 열려 있고 AllowDuplicate=false |
열기 실패 |
최상단 Popup의 IgnoreOtherPopup=true |
새 Popup 열기 실패 |
PlayerController가 AWidgetPlayerController가 아님 |
열기 실패 |
Registry는 공용 위젯 인스턴스를 이름으로 찾아 재사용할 때 사용한다.
Get Registered Widget 노드를 호출한다.
입력:
- World Context
- Widget Name
반환값을 원하는 위젯 타입으로 Cast해서 사용한다.
#include "WidgetHelper.h"
UWidgetBase* Widget = UWidgetHelper::GetRegisteredWidget(this, TEXT("EditableTextBox"));
if (IsValid(Widget))
{
Widget->AddToViewport(20);
Widget->Show(EWidgetShowType::Visible);
}프로젝트 코드 예시 패턴:
_EditableTextBoxWidget = Cast<UUI_EditableTextBox>(
UWidgetHelper::GetRegisteredWidget(this, TEXT("EditableTextBox"))
);Registry 위젯은 재사용되므로 이벤트를 바인딩했다면 닫힐 때 해제하는 것이 안전하다.
void UMyWidget::OnHideRegisteredWidget(UWidgetBase* _widget, EWidgetHideType _hide_type)
{
RegisteredWidget->_OnHideEvent.RemoveAll(this);
RegisteredWidget = nullptr;
}ClickButton, ToggleButton, RadioButton 계열은 내부에 다음 BindWidget 이름을 가진 위젯이 필요하다.
| 이름 | 타입 | 역할 |
|---|---|---|
SizeBox |
SizeBox | 고정 크기 제어 |
Border |
Border | Brush와 ContentColor 적용 |
NS_Content |
NamedSlot | 버튼 내부 콘텐츠 |
권장 위젯 트리:
Root Canvas/Overlay
└─ SizeBox (이름: SizeBox)
└─ Border (이름: Border)
└─ NamedSlot (이름: NS_Content)
_StateStyles에 상태별 스타일을 넣는다.
| 상태 | 용도 |
|---|---|
| Normal | 기본 상태 |
| Hovered | 마우스 오버 |
| Pressed | 누른 상태 |
| Disabled | 비활성 상태 |
ToggleButton과 RadioButton은 선택 상태에서 _SelectedStateStyles를 우선 사용한다.
일반 클릭 버튼이다.
사용 이벤트:
_OnClicked_OnDoubleClicked_OnSubClicked
설정:
| 설정 | 설명 |
|---|---|
| _UseSubClick | 우클릭 등 보조 클릭 사용 여부 |
| _SubClickSound | 보조 클릭 사운드 |
| _DontResetButtonStateOnMouseLeaveWhenPressed | 누른 채 마우스가 나갈 때 Pressed 유지 여부 |
클릭할 때 선택 상태가 바뀐다.
사용 이벤트:
_OnClicked_OnSelectChanged
Blueprint/C++에서 강제로 선택 상태를 바꿀 수 있다.
ToggleButton->SetIsSelected(true);
bool bNowSelected = ToggleButton->ToggleSelected();여러 RadioButton 중 하나만 선택하려면 RadioButtonGroup을 사용한다.
필수 구조:
RadioButtonGroup Widget
└─ StackBox (이름: StackBox)
├─ RadioButton
├─ RadioButton
└─ RadioButton
각 RadioButton에는 _WidgetID를 지정하는 것이 좋다.
선택 API:
RadioButtonGroup->SelectRadioButtonByWidgetID(TEXT("GraphicsHigh"));
RadioButtonGroup->SelectRadioButtonByIndex(0);선택 결과는 _OnRadioButtonSelected에서 받는다.
주의:
- StackBox 자식은 모두
URadioButton이어야 한다. - WidgetID가
None이면 WidgetID 기반 선택을 할 수 없다.
버튼을 비활성화할 때 SetIsEnabled(false)를 직접 호출하지 않는다.
대신:
Button->SetButtonDisabled(true);다시 활성화:
Button->SetButtonDisabled(false);- Widget Blueprint에
TypingRichTextBlock을 배치한다. _FullText에 전체 텍스트를 입력한다._TypingSpeed를 설정한다.- 자동 시작이 필요하면
_AutoStart를 켠다.
TypingText->StartTyping(false); // 처음부터 시작
TypingText->StopTyping(false); // 현재 텍스트 유지하고 중지
TypingText->StopTyping(true); // 비우고 초기화
TypingText->SetTypingSpeed(20.0f);스타일 태그는 유지된다.
<Red>경고</> 메시지입니다.
이미지 같은 셀프 클로징 태그는 하나의 토큰으로 취급한다.
<img id="Icon"/> 아이콘 설명
주의:
- 스타일 종료 태그는
</>형태를 기준으로 처리한다. - Design Time에서는 타이핑하지 않고 전체 텍스트가 즉시 보인다.
MarqueeWidgetBase를 부모로 Widget Blueprint를 만든다.
필수 위젯 트리:
Root
└─ CanvasPanel (이름: CanvasPanel)
└─ NamedSlot (이름: NS_Content)
└─ 실제 표시할 텍스트/이미지/위젯
CanvasPanel은 자동으로 ClipToBounds가 적용된다.
| 설정 | 설명 |
|---|---|
| MarqueeType | Scroll 또는 AlternateScroll |
| Orientation | 가로/세로 방향 |
| IsReverse | 시작 방향 반전 |
| MarqueeSpeed | 이동 속도 |
| DelayTime | 끝점에서 대기하는 시간 |
| 타입 | 사용 상황 |
|---|---|
| Scroll | 공지 문구처럼 한 방향으로 계속 흐르게 할 때 |
| AlternateScroll | 영역 안에서 왔다 갔다 움직이게 할 때 |
Project Settings의 CustomUI StringTables 배열에 사용할 String Table 에셋을 추가한다.
Get String Table Text 노드를 사용한다.
입력:
- Table Name: String Table 에셋 이름
- Key: String Table Key
FText ErrorText = UWidgetHelper::GetStringTableText(TEXT("ST_Office"), TEXT("Set_Nickname_IsEmpty"));매크로:
FText ErrorText = GETTEXT("ST_Office", "Set_Nickname_IsEmpty")드래그 시작 위치와 로컬 오프셋이 필요한 경우 AdvancedDragDropOperation을 사용한다.
보관되는 값:
| 값 | 설명 |
|---|---|
| LocalOffset | 위젯 내부 Pivot 기준 마우스 오프셋 |
| DragStartScreenPos | 드래그 시작 Screen Position |
Blueprint DragDrop 구현에서 기본 DragDropOperation 대신 이 클래스를 쓰면 드래그 중 위치 계산에 필요한 값을 함께 전달할 수 있다.
새 UI를 만들 때는 이 순서가 가장 안전하다.
- PlayerController가
AWidgetPlayerController를 상속하는지 확인한다. - Project Settings의 CustomUI 설정을 채운다.
- 공용 위젯이면
WidgetRegistryDataAsset에 등록한다. - 일반 위젯은
WidgetBase, 팝업은PopupBase를 부모로 만든다. - 필요하면
ShowAnim,IdleAnim,HideAnim을 만든다. - 버튼은 요구되는
SizeBox,Border,NS_Content이름을 맞춘다. - RadioButtonGroup은
StackBox와 자식RadioButton구조를 맞춘다. - 표시/숨김은
Show,Hide,Close로만 처리한다. - Popup은
OpenPopup으로 열고Close로 닫는다. - Registry 위젯에 바인딩한 이벤트는
OnHide에서 해제한다. - PIE에서 Popup 중복, 커서 표시, HideAnim 종료 후 제거 여부를 확인한다.
- UI 수명 주기는
SetVisibility보다Show/Hide/Close기준으로 설계한다. - Popup은
Hide(Collapsed)보다Close()로 닫는 것이 스택 정리에 안전하다. - Registry 위젯은 재사용되므로 이전 상태/이벤트가 남지 않게 초기화 루틴을 둔다.
- Modal 팝업은
IgnoreOtherPopup=true, 일반 알림 팝업은AllowDuplicate정책을 명확히 정한다. - 버튼 비활성은
SetButtonDisabled로 처리해 Disabled 스타일과 상태가 일관되게 유지되도록 한다. - RichText 타이핑은 복잡한 중첩 마크업보다 단순한
<Style>text</>패턴을 권장한다. - Page 시스템은 Deprecated이므로 신규 화면 전환 설계에는 사용하지 않는다.