PART 6 · 강의 3/3
월드 스페이스 UI
WidgetComponent를 활용한 3D 공간 UI, 빌보드, NPC 체력바, 상호작용 프롬프트를 구현합니다.
01
UWidgetComponent 기초
3D 공간에 UMG 위젯을 렌더링하는 컴포넌트
C++ - WidgetComponent 설정
UCLASS()
class AInteractableActor : public AActor
{
GENERATED_BODY()
public:
AInteractableActor()
{
WidgetComp = CreateDefaultSubobject<UWidgetComponent>(
"InteractWidget");
WidgetComp->SetupAttachment(RootComponent);
// 월드 스페이스 모드 (3D 공간에 배치)
WidgetComp->SetWidgetSpace(EWidgetSpace::World);
// 또는 스크린 스페이스 (항상 카메라를 향함)
// WidgetComp->SetWidgetSpace(EWidgetSpace::Screen);
// 위젯 크기 (월드 단위 아닌 위젯 해상도)
WidgetComp->SetDrawSize(FVector2D(200.f, 50.f));
// 위젯 클래스 지정
WidgetComp->SetWidgetClass(InteractPromptClass);
// 초기에는 숨김
WidgetComp->SetVisibility(false);
}
void ShowPrompt() { WidgetComp->SetVisibility(true); }
void HidePrompt() { WidgetComp->SetVisibility(false); }
protected:
UPROPERTY(VisibleAnywhere)
UWidgetComponent* WidgetComp;
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UUserWidget> InteractPromptClass;
};
| Widget Space | 동작 | 사용 예 |
|---|---|---|
World | 3D 공간에 고정, 원근법 적용 | 간판, 모니터 화면, 터미널 |
Screen | 항상 카메라를 향함 (빌보드) | NPC 이름표, 체력바, 프롬프트 |
02
NPC 체력바
거리에 따른 크기 조절과 가시성 관리
C++ - NPC 체력바 구현
void AEnemyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!HealthBarComp) return;
// 플레이어와의 거리 계산
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (!PC || !PC->GetPawn()) return;
float Distance = FVector::Dist(
GetActorLocation(), PC->GetPawn()->GetActorLocation());
// 거리에 따른 가시성
const float MaxVisibleDist = 3000.f;
const float FadeStartDist = 2000.f;
if (Distance > MaxVisibleDist)
{
HealthBarComp->SetVisibility(false);
}
else
{
HealthBarComp->SetVisibility(true);
// 거리 기반 페이드
float Alpha = 1.f;
if (Distance > FadeStartDist)
{
Alpha = 1.f - (Distance - FadeStartDist)
/ (MaxVisibleDist - FadeStartDist);
}
// 위젯의 Render Opacity 설정
if (auto* Widget = HealthBarComp->GetWidget())
{
Widget->SetRenderOpacity(Alpha);
}
}
}
WidgetComponent 성능
각 WidgetComponent는 독립적인 렌더 타겟을 생성합니다. 수십 개의 NPC가 동시에 체력바를 표시하면 성능에 영향을 줍니다. 대안으로 스크린 공간에 투영하여 일반 UMG 위젯으로 표시하거나, Tick에서 거리 기반으로 가시성을 제어하세요.
03
스크린 투영 방식
WidgetComponent 없이 월드 위치를 스크린에 표시
C++ - 스크린 투영 방식 체력바
// WidgetComponent 대신 스크린 좌표 투영 사용
// 장점: 렌더 타겟 없이 일반 UMG 위젯으로 표시
// 단점: 수동 위치 업데이트 필요
UCLASS()
class UWorldMarkerManager : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(meta = (BindWidget))
UCanvasPanel* MarkerCanvas;
void AddMarker(AActor* Target, TSubclassOf<UUserWidget> WidgetClass)
{
auto* Marker = CreateWidget<UUserWidget>(
GetOwningPlayer(), WidgetClass);
UCanvasPanelSlot* Slot = MarkerCanvas->AddChildToCanvas(Marker);
Slot->SetAutoSize(true);
Slot->SetAlignment(FVector2D(0.5f, 1.0f)); // 아래 중앙 피봇
ActiveMarkers.Add({Target, Marker, Slot});
}
virtual void NativeTick(const FGeometry& Geo, float DT) override
{
Super::NativeTick(Geo, DT);
auto* PC = GetOwningPlayer();
for (auto& M : ActiveMarkers)
{
if (!M.Target) continue;
FVector WorldPos = M.Target->GetActorLocation()
+ FVector(0, 0, 120.f); // 머리 위
FVector2D ScreenPos;
if (PC->ProjectWorldLocationToScreen(WorldPos, ScreenPos))
{
M.Slot->SetPosition(ScreenPos);
M.Widget->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
}
else
{
M.Widget->SetVisibility(ESlateVisibility::Collapsed);
}
}
}
};
04
상호작용 프롬프트
"E를 눌러 상호작용" 프롬프트 구현 패턴
C++ - 상호작용 프롬프트
UCLASS()
class UInteractPromptWidget : public UCommonUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(meta = (BindWidget))
UCommonActionWidget* InputActionWidget;
// → 마우스: "E" 키 아이콘 | 게임패드: "Y" 버튼 아이콘
UPROPERTY(meta = (BindWidget))
UTextBlock* ActionText;
void SetPromptText(const FText& Text)
{
ActionText->SetText(Text);
}
};
// 오버랩 기반 프롬프트 표시/숨김
void AInteractableActor::OnOverlapBegin(
AActor* OtherActor)
{
if (Cast<APlayerCharacter>(OtherActor))
{
ShowPrompt();
auto* Prompt = Cast<UInteractPromptWidget>(
WidgetComp->GetWidget());
Prompt->SetPromptText(
FText::FromString("상자 열기"));
}
}
SUMMARY
핵심 요약
UWidgetComponent의 World 모드는 3D 공간 고정, Screen 모드는 빌보드 방식으로 동작합니다- NPC 체력바는 거리 기반 가시성과 페이드를 적용하여 성능과 가독성을 모두 확보합니다
- 다수의 월드 마커에는 스크린 투영 방식이 WidgetComponent보다 성능이 유리합니다
UCommonActionWidget을 상호작용 프롬프트에 사용하면 플랫폼별 아이콘이 자동 전환됩니다
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: NPC 체력바 WidgetComponent
UWidgetComponent를 사용하여 NPC 머리 위에 체력바를 표시하세요. Screen Space 모드로 항상 카메라를 향하게 설정하고, 거리에 따라 크기가 축소되며 일정 거리 이상에서는 숨기도록 구현합니다.
실습 2: 상호작용 프롬프트 UI
플레이어가 상호작용 가능한 오브젝트에 접근하면 'E키를 눌러 상호작용' 프롬프트가 월드 스페이스에 표시되는 시스템을 구현하세요. SphereComponent 오버랩으로 감지하고 위젯을 동적 생성합니다.
심화 과제
100개 이상의 월드 스페이스 위젯이 있을 때 성능을 최적화하세요. 화면 밖 위젯 비활성화, LOD 기반 디테일 조절(먼 거리에서 간략화), WidgetComponent 풀링을 구현합니다.