이 튜토리얼에서는 커스텀 프로퍼티 타입(구조체) 및 액터 클래스를 위한 디테일 패널 커스터마이제이션(Details Panel Customization) 생성 방법을 안내합니다.
필수 구성
이 튜토리얼을 시작하기 전에 런타임 모듈과 해당 에디터 모듈을 설정해야 합니다. 이 튜토리얼에서는 CustomGameplay 및 CustomGameplayEditor 모듈을 사용하는데, 둘 다 에디터 모듈 튜토리얼에 정의되어 있습니다. 폴더 구조는 다음과 같은 모습이어야 합니다.
- 프로젝트 소스 폴더
- CustomGameplay
- CustomGameplay.Build.cs
- Target.cs
- Editor.Target.cs
- Private
- Public
- CustomGameplayEditor
- CustomGameplayEditor.Build.cs
- Private
- CustomGameplayEditorModule.cpp
- Public
- CustomGameplayEditorModule.h
- CustomGameplay
프로퍼티 타입 디테일 커스터마이징하기
구조체의 커스텀 디테일 패널을 만드는 절차는 다음과 같습니다.
-
CustomGameplay 모듈의
Public폴더에 새 헤더 파일(.h)을 생성합니다. 파일 이름을CustomDataProperty.h로 지정합니다. -
CustomDataProperty.h에 다음 C++ 코드를 추가합니다.CustomDataProperty.h
#pragma once #include "CoreMinimal.h" #include "CustomDataType.generated.h" USTRUCT(BlueprintType, Category="Custom Data") struct CUSTOMGAMEPLAY_API FCustomDataProperty { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Custom Data") TSoftObjectPtr<UTexture> CustomTexture; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Data") FName CustomName; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Data") FString CustomString; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Data") int32 CustomInt; };FCustomDataProperty에BlueprintType지정자가 있고 모든 해당 프로퍼티에EditAnywhere지정자가 있는UPROPERTY매크로가 있는지 확인하세요. 이는 블루프린트 클래스에서FCustomDataProperty에 액세스하고 구조체의 프로퍼티를 표시하는 데 필요합니다. -
CustomGameplayEditor 모듈의
Public폴더에 새 헤더 파일(.h)을 생성합니다. 파일 이름을CustomDataPropertyCustomization.h로 지정합니다. -
CustomDataPropertyCustomization.h에 다음 C++ 코드를 추가합니다.CustomDataPropertyCustomization.h
#pragma once #include "CoreMinimal.h" #include "IPropertyTypeCustomization.h" class IPropertyHandle; //Forward declaration for IPropertyHandle interface class FCustomDataPropertyCustomization : public IPropertyTypeCustomization { public: // IPropertyTypeCustomization에서 상속됨 virtual void CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; virtual void CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) override; //프로퍼티 커스터마이제이션의 인스턴스를 생성하기 위한 유틸리티 함수입니다. static TSharedRef<IPropertyTypeCustomization> MakeInstance(); }; -
CustomGameplayEditor 모듈의
Private폴더에 새 클래스 본문(.) 파일을 생성합니다. 파일 이름을CustomDataPropertyCustomization.cpp` 로 지정합니다. -
CustomDataPropertyCustomization.cpp에 다음 C++ 코드를 추가합니다.CustomDataPropertyCustomization.cpp
#include "CustomDataDetailsCustomization.h" #include "Widgets/SWidget.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "DetailWidgetRow.h" #include "IDetailChildrenBuilder.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #include "HAL/PlatformApplicationMisc.h" #include "IPropertyUtilities.h" void FCustomDataDetailsCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { } void FCustomDataDetailsCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) { //계속하기 전에 액세스하려는 프로퍼티가 유효한지 확인합니다. if (!PropertyHandle->IsValidHandle()) { return; } //"Hello World!" 텍스트를 표시하는 커스텀 행을 추가합니다 ChildBuilder.AddCustomRow( FText::FromString("HelloWorldTest")) .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("Hello, World!"))) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; //구조체의 자손 프로퍼티를 반복할 수 있고 해당 디폴트 프로퍼티 디테일을 추가하는 for 루프를 설정합니다. uint32 NumChildren = 0; PropertyHandle->GetNumChildren(NumChildren); //구조체에 대한 원래 자손 프로퍼티를 표시합니다. for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { ChildBuilder.AddProperty(PropertyHandle->GetChildHandle(ChildIndex).ToSharedRef()); } } //디테일 커스터마이제이션의 스태틱 인스턴스를 생성합니다. TSharedRef<IPropertyTypeCustomization> FCustomDataDetailsCustomization::MakeInstance() { return MakeShareable(new FCustomDataDetailsCustomization); }위의 코드는 다음과 같은 작업을 수행합니다.
-
include 지시문은 다음을 포함한 디테일 커스터마이제이션에 필요한 클래스에 대한 액세스를 추가합니다.
-
위젯 관련 클래스(SButton, STextBlock)
-
디테일 위젯 모델링을 위한 클래스(DetailLayoutBuilder, DetailWidgetRow, DetailCategoryBuilder, IDetailChildrenBuilder)
-
디테일 패널 커스터마이제이션을 정의하기 위한 클래스(IDetailChildrenBuilder, IPropertyUtilities)
-
-
FCustomDataDetailsCustomization::CustomizeHeader는IPropertyTypeCustomization구현 요구 사항을 충족하기 위해 구현되어야 하지만, 이 예시에서는 사용되지 않습니다. -
FCustomDataDetailsCustomization::CustomizeChildren에는 디테일 커스터마이제이션의 본문을 표시하기 위한 로직이 포함되어 있습니다.-
IDetailChildrenBuilder::AddCustomRow는 새로운 슬레이트 디테일 위젯 행을 정의합니다. 그 안에 다른 슬레이트 위젯을 선언할 수 있습니다. -
그 후, for 루프가 원래 구조체의 각 자손 요소에 대한 디폴트 디스플레이를 설정합니다.
-
-
FCustomDataDetailsCustomization::MakeInstance는 디테일 커스터마이제이션의 인스턴스를 생성하고TSharedRef에 반환하며, 이는 다음 단계에서 디테일 패널에 프로퍼티를 표시하는 데 사용됩니다.
-
-
CustomGameplayEditor 모듈의 구현 파일(
CustomGameplayEditor.cpp)을 엽니다.OnModuleStartup에 다음 코드를 추가합니다.CustomGameplayEditor.cpp
#include "CustomGameplayEditorModule.h" #include "CustomDataDetailsCustomization.h" #include "PropertyEditorDelegates.h" #include "PropertyEditorModule.h" IMPLEMENT_GAME_MODULE(FCustomGameplayEditorModule, CustomGameplayEditor); //StartupModule 함수에 디테일 커스터마이제이션을 등록합니다. void FCustomGameplayEditorModule::StartupModule() { //프로퍼티 모듈에 대한 레퍼런스를 가져옵니다. FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); /* 커스텀 프로퍼티 타입의 레이아웃을 등록합니다. 여기에는 프로퍼티 타입의 이름이 필요합니다. 스트링("CustomData")로 직접 제공해도 되고 StaticStruct에서 FName을 가져와도 됩니다. 또한, 디테일 커스터마이제이션의 인스턴스를 생성하는 함수에 대한 델리게이트도 제공해야 합니다. 이 경우에는 앞서 생성한 MakeInstance 함수입니다. */ PropertyModule.RegisterCustomPropertyTypeLayout(FCustomDataProperty::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FCustomDataDetailsCustomization::MakeInstance)); } //ShutdownModule 함수에서 디테일 커스터마이제이션을 제거합니다. void FCustomGameplayEditorModule::ShutdownModule() { PropertyModule.UnregisterCustomPropertyTypeLayout(FCustomDataProperty::StaticStruct()->GetFName()); }위의 코드는 다음과 같은 작업을 수행합니다.
PropertyEditorDelegates.h및PropertyEditorModule.h에 대한 include 지시문은 프로퍼티 타입 커스터마이제이션을 등록하는 데 필요한 클래스를 추가합니다.CustomDataPropertyCustomization.h에 대한 include 지시문은 이전 단계에서 생성한 프로퍼티 타입 커스터마이제이션을 추가합니다.-
FCustomGameplayEditorModule::StartupModule의 코드는 언리얼 에디터가 모듈을 로드할 때 다음 작업을 수행합니다.- 프로퍼티 에디터 모듈의 인스턴스를 로드하거나 가져옵니다.
-
FPropertyEditorModule::RegisterCustomPropertyTypeLayout을 사용하여 앞서 생성한 프로퍼티 타입 커스터마이제이션을 사용하도록 에디터에 알립니다.- 여기에는 구조체 이름("CustomDataType")과 에디터에서 표시해야 할 때 프로퍼티 타입 커스터마이제이션의 인스턴스를 생성하는 메서드(
FCustomDataPropertyCustomization::MakeInstance)가 포함됩니다.
- 여기에는 구조체 이름("CustomDataType")과 에디터에서 표시해야 할 때 프로퍼티 타입 커스터마이제이션의 인스턴스를 생성하는 메서드(
클래스 디테일 커스터마이징하기
-
Actor(
UActor) 를 부모 클래스로 사용하여 새로운 C++ 클래스를 만듭니다. 클래스 이름을 CustomActor 로 지정합니다. -
CustomActor.h를 열고 다음 멤버를 추가합니다.CustomActor.h
UPROPERTY(EditAnywhere) TSoftObjectPtr<UStaticMesh> CustomMesh; UPROPERTY(EditAnywhere) float CustomFloat; UPROPERTY(EditAnywhere) bool CustomBool; UPROPERTY(EditAnywhere) FCustomData CustomData;이러한 프로퍼티에는 카테고리가 포함되지 않는데, 그 이유는 디테일 패널 커스터마이제이션을 위한 코드에서 카테고리를 제공하기 때문입니다.
-
CustomGameplayEditor 모듈의
Public폴더에 새 헤더 파일(.h)을 생성합니다. 파일 이름을CustomActorClassCustomization.h로 지정합니다. -
CustomActorClassCustomization.h에 다음 C++ 코드를 추가합니다.CustomActorClassCustomization.h
#pragma once #include "CoreMinimal.h" #include "IDetailCustomization.h" //IPropertyHandle의 포워드 선언입니다. class IPropertyHandle; //커스텀 클래스 디테일 커스터마이제이션입니다. class FCustomClassDetailsCustomization : public IDetailCustomization { public: //디테일 패널을 커스터마이징하는 함수입니다. virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; //디테일 패널 커스터마이제이션의 스태틱 인스턴스를 반환합니다. static TSharedRef<IDetailCustomization> MakeInstance(); };
프로퍼티 타입 커스터마이제이션에서와 마찬가지로 이 클래스에는 나중에 이를 에디터 모듈에 등록하기 위한 FCustomClassDetailsCustomization::MakeInstance 함수가 포함되어 있습니다.
-
CustomGameplayEditor 모듈의
Private폴더에 새 본문(.cpp) 파일을 생성합니다. 파일 이름을CustomActorClassCustomization.cpp로 지정합니다. -
CustomActorClassCustomization.cpp에 다음 C++ 코드를 추가합니다.CustomActorClassCustomization.cpp
#include "Widgets/SWidget.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "DetailWidgetRow.h" #include "IDetailChildrenBuilder.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #include "HAL/PlatformApplicationMisc.h" #include "IPropertyUtilities.h" #include "CustomActor.h" #include "CustomClassDetailsCustomization.h" void FCustomClassDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { IDetailCategoryBuilder& CustomCategory = DetailBuilder.EditCategory("CustomSettings"); //CustomData 및 CustomBool을 목록의 처음에 배치하여 헤더에 나열된 순서가 아니라 이 코드의 순서대로 프로퍼티가 추가된다는 것을 보여줍니다. CustomCategory.AddProperty(GET_MEMBER_NAME_CHECKED(ACustomActor, CustomData)); CustomCategory.AddProperty(GET_MEMBER_NAME_CHECKED(ACustomActor, CustomBool)); CustomCategory.AddProperty(GET_MEMBER_NAME_CHECKED(ACustomActor, CustomMesh)); CustomCategory.AddProperty(GET_MEMBER_NAME_CHECKED(ACustomActor, CustomFloat)); } //모듈 시작 시 등록하는 데 필요한 이 디테일 커스터마이제이션의 스태틱 인스턴스를 생성합니다. TSharedRef<IDetailCustomization> FCustomClassDetailsCustomization::MakeInstance() { return MakeShareable(new FCustomClassDetailsCustomization); }클래스의 디테일 패널 커스터마이제이션에 필드를 추가하는 가장 간단한 방법은 디테일 카테고리를 정의한 다음 그 카테고리에 필드를 추가하는 것입니다. 위의 코드는
CustomSettings카테고리를 추가하고IDetailCategoryBuilder::AddProperty함수를 사용하여 각 프로퍼티의 디테일에 대한 디폴트 구현을 추가합니다. 이를 통해 이러한 프로퍼티의UPROPERTY지정자에 정의된 모든 카테고리가 오버라이드됩니다. -
CustomGameplayEditor 모듈의 구현 파일(
CustomGameplayEditor.cpp)을 엽니다.StartupModule함수에PropertyModule.RegisterCustomClassLayout호출을,ShutdownModule함수에PropertyModule.UnregisterCustomClassLayout호출을 추가합니다. 그 코드는 다음과 같을 것입니다.CustomGameplayEditorModule.cpp
IMPLEMENT_GAME_MODULE(FCustomGameplayEditorModule, CustomGameplayEditor); void FCustomGameplayEditorModule::StartupModule() { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); PropertyModule.RegisterCustomPropertyTypeLayout(FCustomDataProperty::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FCustomDataDetailsCustomization::MakeInstance)); PropertyModule.RegisterCustomClassLayout(ACustomActor::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FCustomClassDetailsCustomization::MakeInstance)); } void FCustomGameplayEditorModule::ShutdownModule() { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); PropertyModule.UnregisterCustomPropertyTypeLayout(FCustomDataProperty::StaticStruct()->GetFName()); PropertyModule.UnregisterCustomClassLayout(ACustomActor::StaticClass()->GetFName()); } -
코드를 컴파일하고 프로젝트를 엽니다. 맵에 CustomActor의 인스턴스를 배치하고 디테일 패널을 확인합니다. 그러면 다음과 같은 모습일 것입니다.
예상대로 'Hello, World!' 스트링 및 각 구조체 멤버와 함께 커스텀 데이터 타입이 먼저 표시되고, 이어서 커스텀 boolean과 기타 프로퍼티가 표시됩니다.
추가 자료
다음 페이지에서는 커스텀 디테일 패널을 생성할 때의 일반적인 작업을 살펴봅니다.