Ch3 팀 프로젝트(1) - Common UI 도입

yys·2026년 5월 6일

TIL

목록 보기
41/80

오늘 한 내용


  • C++ 코드카타
  • 알고리즘 세션 강의 수강
  • Ch3 팀 프로젝트

Common UI


기존의 전통적인 방식의 문제점을 개선하기 위해 나타난 플러그인이다.
기존의 UMG를 사용하는 방식과의 차이는 다음과 같다.

기존 UMG

  • Create Widget -> Add To Viewport로 사용 -> Hard Reference
  • 입력의 제어를 수동으로 전환해줘야 함
  • 위젯의 순서를 구분하기 어려움

Common UI

  • Stack에 상황에 따라 위젯을 Soft Reference로 Push/Pop
  • 자동으로 입력의 제어가 위젯이 켜질 때 가로채고, 꺼지면 반환
  • Stack 형식이기 때문에 위젯의 순서를 구분하기 쉬움

해당 플러그인은 Lyra 프로젝트에서도 사용되기 때문에 꼭 알아둘 필요가 있다고 생각해서 한 번 사용해보기로 했다.

참고로 CommonUI 플러그인이 켜져있어야 하며, 모듈에 "CommonUI", "CommonInput"을 추가해야 한다.

Common UI의 스택 구조


Common UI의 전반적인 스택 구조는 다음과 같다.

위젯들은 위 4가지의 스택에 삽입되어서 관리되며, 상위 스택의 위젯이 하위 스택의 위젯을 비활성화 시킬 수 있다.
(즉, Modal에 위치한 위젯이 Frontend에 위치한 위젯을 비활성화 시킬 수 있음)

스택을 관리하는 위젯으로, UCommonUserWidget을 상속한 PDPrimaryWidget을 만들어주고
각 스택들은 GameplayTag로 구분해준 뒤, TMap을 통해서 스택과 매핑을 해준다.

// PDFrontendGameplayTags.h
namespace PDFrontendGameplayTags
{
	// Frontend WidgetStack
	PROJECTD_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Frontend_WidgetStack_Modal);
	PROJECTD_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Frontend_WidgetStack_GameMenu);
	PROJECTD_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Frontend_WidgetStack_GameHud);
	PROJECTD_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Frontend_WidgetStack_Frontend);
}
// PDFrontendGameplayTags.cpp
namespace PDFrontendGameplayTags
{
	UE_DEFINE_GAMEPLAY_TAG(Frontend_WidgetStack_Modal, "Frontend.WidgetStack.Modal");
	UE_DEFINE_GAMEPLAY_TAG(Frontend_WidgetStack_GameMenu, "Frontend.WidgetStack.GameMenu");
	UE_DEFINE_GAMEPLAY_TAG(Frontend_WidgetStack_GameHud, "Frontend.WidgetStack.GameHud");
	UE_DEFINE_GAMEPLAY_TAG(Frontend_WidgetStack_Frontend, "Frontend.WidgetStack.Frontend");
}

// PDPrimaryLayout.h
UCLASS(Abstract, BlueprintType, meta = (DisableNativeTick))
class PROJECTD_API UPDPrimaryLayout : public UCommonUserWidget
{
	GENERATED_BODY()
public:
	/** Subsystem에서 Push 대상 스택을 찾을 때 사용. */
	UCommonActivatableWidgetContainerBase* FindStackByTag(const FGameplayTag& StackTag) const;
	
protected:
	/** BP에서 BindWidget으로 묶은 스택을 태그와 함께 등록. */
	UFUNCTION(BlueprintCallable, Category = "Layout")
	void RegisterStack(UPARAM(meta = (Categories = "Frontend.WidgetStack"))FGameplayTag StackTag, UCommonActivatableWidgetContainerBase* Stack);

private:
	UPROPERTY(Transient)
	TMap<FGameplayTag, TObjectPtr<UCommonActivatableWidgetContainerBase>> WidgetStackMap;
};

// PDPrimaryLayout.cpp
UCommonActivatableWidgetContainerBase* UPDPrimaryLayout::FindStackByTag(const FGameplayTag& StackTag) const
{
	checkf(WidgetStackMap.Contains(StackTag), TEXT("태그에 맞는 스택이 존재하지 않습니다."));
	
	return WidgetStackMap.FindRef(StackTag);
}

void UPDPrimaryLayout::RegisterStack(UPARAM(meta = (Categories = "Frontend.WidgetStack"))FGameplayTag StackTag, UCommonActivatableWidgetContainerBase* Stack)
{
	if (!IsDesignTime())
	{
		if (!WidgetStackMap.Contains(StackTag))
		{
			WidgetStackMap.Add(StackTag, Stack);
		}
	}
}

이후 PDPrimaryLayout을 상속하는 위젯 블루프린트를 만들고 Common Activatable Widget Stack을 각각 배치해주고 모든 Alignment를 Fill로 맞춰준다.
(이때, 마지막 위젯이 가장 위에 배치되므로 순서가 중요)

그리고 각 스택을 TMap에 매핑해주기 위해 각각 이벤트 그래프에서 RegisterStack를 호출해주고 태그를 할당해준다.

이후 PlayerController에 Possess 될 때, 해당 위젯만 Add to Viewport를 수행한다. 이 이후로 스택에 위젯을 할당하기 때문에 Add to Viewport를 할 일은 없다.
(참고로 GameInstance의 Init에 먼저 해보았지만, 너무 빨리 호출되서 그런지 해당 작업이 수행이 안되었다.)

현재까지는 기본 틀만 작성하기 때문에 시각적으로 보이는 것은 없다. 다음은 간단한 위젯을 노출하는 작업을 해볼 예정이다.

profile
게임 개발 지망생

0개의 댓글