위젯 풀링
위젯 오브젝트 풀, UMG ListView/TileView 가상화, 대량 데이터 효율적 표시를 학습합니다.
위젯 풀링의 필요성
동적 위젯 생성/파괴의 비용과 해결책
대량의 아이템 목록, 채팅 메시지, 리더보드 등에서 매번 위젯을 CreateWidget/RemoveFromParent하면 GC 부하와 메모리 할당 비용이 발생합니다. 위젯 풀링은 미리 생성한 위젯을 재사용하여 이 비용을 제거합니다.
Create → Use → Destroy
매번 GC 부하
Acquire → Use → Release
재사용, GC 없음
ListView / TileView
UMG의 내장 가상화 리스트 위젯
UListView와 UTileView는 UMG에 내장된 가상화(Virtualized) 리스트 위젯입니다. 화면에 보이는 항목의 위젯만 생성하고, 스크롤 시 벗어난 위젯을 재활용합니다.
// ListView의 항목 데이터 (UObject 필수)
UCLASS()
class UInventoryItemData : public UObject
{
GENERATED_BODY()
public:
UPROPERTY() FName ItemID;
UPROPERTY() FText DisplayName;
UPROPERTY() UTexture2D* Icon;
UPROPERTY() int32 Quantity;
UPROPERTY() ERarity Rarity;
};
// Entry Widget: IUserObjectListEntry 인터페이스 구현
UCLASS()
class UInventoryListEntry : public UUserWidget,
public IUserObjectListEntry
{
GENERATED_BODY()
protected:
UPROPERTY(meta = (BindWidget)) UImage* ItemIcon;
UPROPERTY(meta = (BindWidget)) UTextBlock* ItemName;
UPROPERTY(meta = (BindWidget)) UTextBlock* QuantityText;
// IUserObjectListEntry 구현
// 위젯이 항목 데이터에 바인딩될 때 호출
virtual void NativeOnListItemObjectSet(
UObject* ListItemObject) override
{
auto* ItemData = Cast<UInventoryItemData>(ListItemObject);
if (!ItemData) return;
// 위젯을 새 데이터로 갱신
ItemIcon->SetBrushFromTexture(ItemData->Icon);
ItemName->SetText(ItemData->DisplayName);
QuantityText->SetText(
FText::AsNumber(ItemData->Quantity));
}
};
UPROPERTY(meta = (BindWidget))
UListView* InventoryListView;
// ListView에 데이터 채우기
void UInventoryPanel::PopulateList(
const TArray<FInventoryItem>& Items)
{
// 기존 항목 제거
InventoryListView->ClearListItems();
// 데이터 객체 생성 후 추가
for (const auto& Item : Items)
{
auto* Data = NewObject<UInventoryItemData>(this);
Data->ItemID = Item.ItemID;
Data->DisplayName = Item.DisplayName;
Data->Icon = Item.Icon;
Data->Quantity = Item.Quantity;
// ListView에 데이터 추가
// → 화면에 보이는 항목만 Entry 위젯 생성
InventoryListView->AddItem(Data);
}
}
// 1000개 아이템이 있어도 화면에 보이는 10개만 위젯 생성!
ListView는 세로 방향의 1열 리스트, TileView는 가로로도 배치되는 그리드(타일) 형태입니다. 인벤토리 그리드에는 TileView가, 채팅 로그에는 ListView가 적합합니다. 두 위젯 모두 내부적으로 위젯 재활용 풀을 사용합니다.
커스텀 위젯 풀
ListView가 아닌 경우의 수동 풀링 구현
template<typename T>
class TWidgetPool
{
public:
T* Acquire(APlayerController* PC,
TSubclassOf<T> WidgetClass)
{
T* Widget = nullptr;
if (InactivePool.Num() > 0)
{
// 풀에서 재사용
Widget = InactivePool.Pop();
Widget->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
}
else
{
// 풀이 비었으면 새로 생성
Widget = CreateWidget<T>(PC, WidgetClass);
}
ActivePool.Add(Widget);
return Widget;
}
void Release(T* Widget)
{
ActivePool.Remove(Widget);
Widget->SetVisibility(ESlateVisibility::Collapsed);
InactivePool.Add(Widget);
}
void ReleaseAll()
{
for (T* W : ActivePool)
{
W->SetVisibility(ESlateVisibility::Collapsed);
InactivePool.Add(W);
}
ActivePool.Empty();
}
private:
TArray<T*> ActivePool;
TArray<T*> InactivePool;
};
풀링 모범 사례
효과적인 위젯 풀링을 위한 가이드라인
풀링이 필요한 경우
- + 대량 목록 (인벤토리, 리더보드)
- + 빈번한 생성/파괴 (채팅, 로그)
- + 플로팅 텍스트 (데미지 넘버)
- + 월드 마커 (NPC 체력바)
풀링이 불필요한 경우
- - 고정 UI (HUD, 메뉴 프레임)
- - 소수의 정적 위젯
- - 한 번만 표시되는 UI
- - ListView/TileView 이미 사용 시
핵심 요약
- 위젯 풀링은 CreateWidget/Destroy 비용을 제거하여 동적 UI의 성능을 대폭 개선합니다
- UMG의
ListView/TileView는 내장 가상화로 화면에 보이는 항목만 위젯을 생성하고 재활용합니다 IUserObjectListEntry::NativeOnListItemObjectSet()에서 위젯을 새 데이터에 바인딩합니다- ListView가 적합하지 않은 경우 커스텀 위젯 오브젝트 풀을 구현하여 위젯을 재사용합니다
- 대량 목록과 빈번한 생성/파괴 패턴에서 풀링의 효과가 가장 큽니다
도전 과제
배운 내용을 직접 실습해보세요
UListView를 사용하여 1000개의 아이템 데이터를 표시하는 인벤토리 리스트를 구현하세요. IUserObjectListEntry 인터페이스를 구현한 Entry Widget을 만들고, 스크롤 시 위젯이 재활용되는 것을 확인합니다.
위젯 풀 매니저 클래스를 C++로 구현하여, 데미지 숫자 팝업 같은 자주 생성/파괴되는 위젯을 풀에서 관리하세요. Acquire/Release 패턴으로 위젯을 재사용합니다.
UTileView를 활용하여 대량의 아이템을 그리드 형태로 가상화 표시하는 인벤토리를 구현하세요. 동적으로 열 수를 조정하고, 카테고리별 필터링과 정렬 기능을 추가합니다.