언리얼 엔진에서 데이터 에셋이나 설정 오브젝트를 만들다 보면 여러 프로퍼티를 한꺼번에 특정 값으로 맞추는 작업이 반복된다. 공격력, 사거리, 이동 속도를 테스트용 값으로 초기화할 때마다 각 필드를 직접 수정하면 실수하기 쉽고 작업 속도도 느려진다.
IDetailCustomization을 사용하면 기존 Details 패널에 버튼, 경고문, 조건부 UI를 추가할 수 있다. 별도의 에디터 창을 만들지 않고도 디자이너가 보고 있는 프로퍼티 바로 옆에 도구를 배치할 수 있다는 점이 핵심이다.
편집할 설정 클래스 준비하기
예제로 적 밸런스 설정을 담는 UObject를 만든다. 실제 프로젝트에서는 UDataAsset이나 컴포넌트에도 같은 방식을 적용할 수 있다.
UCLASS(BlueprintType)
class UEnemyBalanceConfig : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category="Combat", meta=(ClampMin="0"))
float AttackPower = 20.0f;
UPROPERTY(EditAnywhere, Category="Combat", meta=(ClampMin="0"))
float AttackRange = 150.0f;
UPROPERTY(EditAnywhere, Category="Movement", meta=(ClampMin="0"))
float MoveSpeed = 300.0f;
};
Details 확장 코드는 런타임 모듈보다 에디터 전용 모듈에 두는 것이 안전하다. 해당 모듈의 Build.cs에는 필요한 의존성을 추가한다.
PrivateDependencyModuleNames.AddRange(new[]
{
"UnrealEd",
"PropertyEditor",
"Slate",
"SlateCore"
});
IDetailCustomization 구현하기
커스터마이저는 현재 선택된 오브젝트를 받아 Details 패널의 카테고리와 행을 재구성한다. 여러 에셋을 동시에 선택할 수 있으므로 단일 포인터 대신 약한 참조 배열로 보관한다.
class FEnemyBalanceConfigDetails final : public IDetailCustomization
{
public:
static TSharedRef<IDetailCustomization> MakeInstance()
{
return MakeShared<FEnemyBalanceConfigDetails>();
}
virtual void CustomizeDetails(
IDetailLayoutBuilder& DetailBuilder) override;
private:
TArray<TWeakObjectPtr<UEnemyBalanceConfig>> Targets;
};
CustomizeDetails에서 선택된 오브젝트를 수집하고 Combat 카테고리에 프리셋 버튼을 추가한다.
void FEnemyBalanceConfigDetails::CustomizeDetails(
IDetailLayoutBuilder& DetailBuilder)
{
TArray<TWeakObjectPtr<UObject>> Objects;
DetailBuilder.GetObjectsBeingCustomized(Objects);
for (const TWeakObjectPtr<UObject>& Object : Objects)
{
if (auto* Config = Cast<UEnemyBalanceConfig>(Object.Get()))
{
Targets.Add(Config);
}
}
DetailBuilder.EditCategory("Combat")
.AddCustomRow(FText::FromString("Test Preset"))
.WholeRowContent()
[
SNew(SButton)
.Text(FText::FromString("근접 전투 테스트값 적용"))
.OnClicked_Lambda([this]
{
const FScopedTransaction Transaction(
FText::FromString("Apply Enemy Test Preset"));
for (const auto& Target : Targets)
{
if (UEnemyBalanceConfig* Config = Target.Get())
{
Config->Modify();
Config->AttackPower = 35.0f;
Config->AttackRange = 120.0f;
Config->MoveSpeed = 420.0f;
Config->PostEditChange();
Config->MarkPackageDirty();
}
}
return FReply::Handled();
})
];
}
여기서 FScopedTransaction과 Modify()를 빼면 값은 변경되지만 Undo가 제대로 동작하지 않는다. MarkPackageDirty()는 변경된 에셋이 저장 대상이라는 사실을 에디터에 알린다. 약한 참조를 사용했기 때문에 Details 패널이 갱신되는 동안 오브젝트가 파기되어도 잘못된 포인터를 역참조하지 않는다.
PropertyEditor 모듈에 등록하기
커스터마이저는 에디터 모듈 시작 시 등록하고 종료 시 해제해야 한다.
void FGameEditorModule::StartupModule()
{
auto& Module = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(
"PropertyEditor");
Module.RegisterCustomClassLayout(
UEnemyBalanceConfig::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(
&FEnemyBalanceConfigDetails::MakeInstance));
}
void FGameEditorModule::ShutdownModule()
{
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
auto& Module =
FModuleManager::GetModuleChecked<FPropertyEditorModule>(
"PropertyEditor");
Module.UnregisterCustomClassLayout(
UEnemyBalanceConfig::StaticClass()->GetFName());
}
}
라이브 코딩으로 등록 코드를 반복 실행하면 기존 레이아웃이 남을 수 있다. 커스터마이저 구조를 변경한 뒤 Details 패널에 반영되지 않는다면 에디터 모듈을 다시 로드하거나 에디터를 재시작하는 편이 확실하다.
결론
IDetailCustomization은 반복되는 에셋 설정 작업을 기존 Details 패널 안으로 가져오는 기능이다. 프리셋 적용, 값 검증, 경고 표시처럼 대상 오브젝트와 밀접한 도구에 특히 적합하다.
실전에서는 값 변경만 구현해서는 부족하다. 다중 선택, Undo와 Redo, 패키지 변경 표시, 모듈 등록 해제까지 함께 처리해야 팀에서 안정적으로 사용할 수 있는 에디터 도구가 된다.
'Unity·Unreal 실전' 카테고리의 다른 글
| Unity 에디터 도구 설정이 재컴파일마다 초기화될 때, ScriptableSingleton 저장 위치부터 확인하세요 (0) | 2026.07.16 |
|---|---|
| Unity 6 Awaitable 실전: 코루틴 없이 프레임 대기와 백그라운드 연산 처리하기 (0) | 2026.07.13 |
| Unreal Live Coding 실전: 에디터 재시작 없이 고쳐도 되는 코드와 안 되는 코드 구분하기 (0) | 2026.07.06 |
| Unity BuildPlayerWindow 커스텀 빌드 버튼으로 실수 없는 팀 빌드 파이프라인 만들기 (1) | 2026.07.02 |
| Unity DOTS 없이 멀티스레드로 수천 오브젝트 움직이기: TransformAccessArray 실전 (0) | 2026.06.29 |