Property Customization
IPropertyTypeCustomization으로 특정 타입의 프로퍼티가 디테일 패널에 표시되는 방식을 완전히 커스터마이징합니다
디테일 패널 커스터마이징 개요
두 가지 커스터마이징 인터페이스의 차이점
UE5는 디테일 패널 커스터마이징을 위한 두 가지 인터페이스를 제공합니다.
| 인터페이스 | 대상 | 적용 범위 | 등록 방식 |
|---|---|---|---|
IPropertyTypeCustomization |
특정 구조체/타입 | 해당 타입이 나타나는 모든 곳 | RegisterCustomPropertyTypeLayout |
IDetailCustomization |
특정 UClass | 해당 클래스의 디테일 패널 전체 | RegisterCustomClassLayout |
IPropertyTypeCustomization: 커스텀 구조체(예: FMyVector, FColorRange)가 어디에서든 일관된 UI로 표시되어야 할 때 사용합니다. IDetailCustomization: 특정 액터/컴포넌트의 디테일 패널 레이아웃 자체를 재구성할 때 사용합니다.
IPropertyTypeCustomization 구현
커스텀 구조체의 에디터 표시를 완전히 재정의하기
대상 구조체 정의
USTRUCT(BlueprintType)
struct FHealthRange
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MinHealth = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MaxHealth = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor DisplayColor = FLinearColor::Green;
};
커스터마이징 클래스
#pragma once
#include "IPropertyTypeCustomization.h"
class FHealthRangeCustomization
: public IPropertyTypeCustomization
{
public:
static TSharedRef<IPropertyTypeCustomization> MakeInstance();
// 헤더 행 커스터마이징 (접힌 상태에서 보이는 부분)
virtual void CustomizeHeader(
TSharedRef<IPropertyHandle> PropertyHandle,
FDetailWidgetRow& HeaderRow,
IPropertyTypeCustomizationUtils& Utils) override;
// 자식 프로퍼티 커스터마이징 (펼친 상태에서 보이는 부분)
virtual void CustomizeChildren(
TSharedRef<IPropertyHandle> PropertyHandle,
IDetailChildrenBuilder& ChildBuilder,
IPropertyTypeCustomizationUtils& Utils) override;
private:
TSharedPtr<IPropertyHandle> MinHealthHandle;
TSharedPtr<IPropertyHandle> MaxHealthHandle;
TSharedPtr<IPropertyHandle> ColorHandle;
};
#include "FHealthRangeCustomization.h"
#include "DetailWidgetRow.h"
#include "IDetailChildrenBuilder.h"
TSharedRef<IPropertyTypeCustomization>
FHealthRangeCustomization::MakeInstance()
{
return MakeShareable(new FHealthRangeCustomization);
}
void FHealthRangeCustomization::CustomizeHeader(
TSharedRef<IPropertyHandle> PropertyHandle,
FDetailWidgetRow& HeaderRow,
IPropertyTypeCustomizationUtils& Utils)
{
// 자식 프로퍼티 핸들 가져오기
MinHealthHandle = PropertyHandle->GetChildHandle(
GET_MEMBER_NAME_CHECKED(FHealthRange, MinHealth));
MaxHealthHandle = PropertyHandle->GetChildHandle(
GET_MEMBER_NAME_CHECKED(FHealthRange, MaxHealth));
ColorHandle = PropertyHandle->GetChildHandle(
GET_MEMBER_NAME_CHECKED(FHealthRange, DisplayColor));
// 헤더 행: Min~Max 형태로 한 줄에 표시
HeaderRow
.NameContent()
[
PropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
.MaxDesiredWidth(400.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(2.0f)
[
MinHealthHandle->CreatePropertyValueWidget()
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(4.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("To", "~"))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(2.0f)
[
MaxHealthHandle->CreatePropertyValueWidget()
]
];
}
void FHealthRangeCustomization::CustomizeChildren(
TSharedRef<IPropertyHandle> PropertyHandle,
IDetailChildrenBuilder& ChildBuilder,
IPropertyTypeCustomizationUtils& Utils)
{
// 색상 프로퍼티만 자식으로 표시
ChildBuilder.AddProperty(ColorHandle.ToSharedRef());
}
등록 및 해제
에디터 모듈에서 프로퍼티 커스터마이징 등록하기
#include "PropertyEditorModule.h"
void FMyGameEditorModule::StartupModule()
{
FPropertyEditorModule& PropertyModule =
FModuleManager::LoadModuleChecked<FPropertyEditorModule>(
"PropertyEditor");
// 구조체 타입 이름으로 등록
PropertyModule.RegisterCustomPropertyTypeLayout(
FHealthRange::StaticStruct()->GetFName(),
FOnGetPropertyTypeCustomizationInstance::CreateStatic(
&FHealthRangeCustomization::MakeInstance));
// 변경사항 반영
PropertyModule.NotifyCustomizationModuleChanged();
}
void FMyGameEditorModule::ShutdownModule()
{
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
FPropertyEditorModule& PropertyModule =
FModuleManager::GetModuleChecked<FPropertyEditorModule>(
"PropertyEditor");
PropertyModule.UnregisterCustomPropertyTypeLayout(
FHealthRange::StaticStruct()->GetFName());
}
}
등록 후 반드시 NotifyCustomizationModuleChanged()를 호출해야 이미 열려 있는 디테일 패널에 변경이 반영됩니다.
IPropertyHandle 심화
프로퍼티 핸들을 통한 값 읽기/쓰기와 변경 감지
| 메서드 | 기능 |
|---|---|
GetValue(T&) |
현재 값 읽기 |
SetValue(T) |
값 설정 (Undo 지원) |
GetChildHandle(Name) |
자식 프로퍼티 접근 |
GetNumChildren() |
자식 수 확인 |
SetOnPropertyValueChanged |
값 변경 콜백 등록 |
CreatePropertyNameWidget() |
이름 라벨 위젯 생성 |
CreatePropertyValueWidget() |
기본 값 편집 위젯 생성 |
IsValidHandle() |
핸들 유효성 검사 |
// 값 읽기
float MinVal;
MinHealthHandle->GetValue(MinVal);
// 값 쓰기 (트랜잭션 자동 처리)
MaxHealthHandle->SetValue(200.0f);
// 변경 감지
MinHealthHandle->SetOnPropertyValueChanged(
FSimpleDelegate::CreateLambda([this]()
{
// Min 값이 변경될 때 호출
float Min, Max;
MinHealthHandle->GetValue(Min);
MaxHealthHandle->GetValue(Max);
// Min이 Max보다 크면 Max를 조정
if (Min > Max)
{
MaxHealthHandle->SetValue(Min);
}
}));
// 멀티 셀렉션에서 값이 다를 때
float Val;
FPropertyAccess::Result Result =
MinHealthHandle->GetValue(Val);
if (Result == FPropertyAccess::MultipleValues)
{
// 여러 객체가 선택되어 값이 다른 경우
}
IPropertyHandle::SetValue()는 자동으로 Undo/Redo 트랜잭션을 생성합니다. 여러 프로퍼티를 한 번에 변경할 때는 GEditor->BeginTransaction() / EndTransaction()으로 그룹화하세요.
핵심 요약
- IPropertyTypeCustomization은 특정 구조체 타입의 디테일 패널 표시를 커스터마이징하며, 해당 타입이 사용되는 모든 곳에 적용됩니다
- CustomizeHeader로 헤더 행을, CustomizeChildren으로 펼친 자식 프로퍼티를 커스터마이징합니다
- IPropertyHandle로 프로퍼티 값을 읽고 쓰며, 변경 감지 콜백으로 상호 의존적인 프로퍼티 간 연동이 가능합니다
RegisterCustomPropertyTypeLayout으로 모듈 시작 시 등록하고,NotifyCustomizationModuleChanged로 변경을 반영합니다IPropertyHandle::SetValue()는 Undo/Redo를 자동 지원하므로 별도의 트랜잭션 관리가 불필요합니다
도전 과제
배운 내용을 직접 실습해보세요
커스텀 USTRUCT(예: FMyRange - Min, Max 포함)에 대한 IPropertyTypeCustomization을 구현하세요. CustomizeHeader에서 Min~Max를 한 줄에 표시하는 커스텀 위젯을 만들고, PropertyEditorModule에 등록하세요.
FString 프로퍼티를 일반 텍스트 입력 대신 SComboBox 드롭다운으로 표시하는 커스터마이징을 구현하세요. 드롭다운 항목은 데이터 에셋이나 Enum에서 동적으로 가져오도록 구성하세요.
TSoftObjectPtr