본문 바로가기
둥지/Unreal

Unreal 에디터 유틸리티 다뤄보기

by 까닭 2024. 2. 28.

1) Unreal Editor Utilities 소개

언리얼에서 필요한 툴을 만들기 위해 사용되는 Unreal Editor Utilities에 대해 간략히 알아보자.

 

좌측 이미지와 같이 콘텐츠 브라우저에서 우클릭을 하면 Unreal Editor Utilities 전용 탭이 보인다. 탭에서 두 가지 요소를 선택할 수 있는데 해당 두 기능을 묶어 Unreal Editor Utilities라고 부른다.

 

1. Editor Utility Blueprint

에셋과 액터 메뉴에 항목을 추가하거나 툴바에 버튼을 추가하는 등 에디터를 확장하여 도구를 만들 수 있다. 즉, 에디터에서 호출할 수 있는 블루프린트 그래프를 만들 수 있다.

 

2. Editor Utility Widget

UI를 디자인하고 로직을 구성하여 커스텀 툴을 만들 수 있다.

 

1. Editor Utility Blueprint

Editor Utility Blueprint는 언리얼 4에선 Blutility(블류틸리티)라고 불렸다.

 

블루프린트 유틸리티 (블류틸리티)

실험단계 Blutilities, 블류틸리티를 사용하면 블루프린트로 언리얼 에디터의 툴과 워크플로를 직접 만들 수 있습니다.

docs.unrealengine.com

 

Editor Utility Blueprint는 기본적으로 위 세 가지 클래스를 상속받을 수 있다.

 

1. Editor Utility Actor

게임 플레이가 아닌 에디터 환경에서 사용되는 특수한 액터 클래스. 스크립트 액션과 같은 에디터 전용 기능에 접근하고 싶고 레벨에 배치까지 하고 싶을 경우 사용한다. 즉, 에디터 전용 기능을 작동시킬 대상이 레벨 상의 액터라면 위 클래스를 상속받는다.

class UInputComponent;
class UObject;
struct FFrame;
struct FPropertyChangedEvent;

UCLASS(Abstract, Blueprintable, meta = (ShowWorldContextPin))
class BLUTILITY_API AEditorUtilityActor : public AActor
{
	GENERATED_UCLASS_BODY()

	// Standard function to execute
	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Editor")
	void Run();

	virtual void OnConstruction(const FTransform& Transform) override;
#if WITH_EDITOR
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif

	/** Returns the current InputComponent on this utility actor. This will be NULL unless bReceivesEditorInput is set to true. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Input|Editor")
	UInputComponent* GetInputComponent() const
	{ 
		return EditorOnlyInputComponent.Get();
	}
	
	UFUNCTION(BlueprintSetter, Category = "Input|Editor")
	void SetReceivesEditorInput(bool bInValue);
	
	UFUNCTION(BlueprintGetter, BlueprintPure, Category = "Input|Editor")
	bool GetReceivesEditorInput() const
	{
		return bReceivesEditorInput;
	}	
	
private:

	/** Creates the EditorOnlyInputComponent if it does not already exist and registers all subobject callbacks to it */
	void CreateEditorInput();

	/** Removes the EditorOnlyInputComponent from this utility actor */
	void RemoveEditorInput();
	
	UPROPERTY(Transient, DuplicateTransient)
	TObjectPtr<UInputComponent> EditorOnlyInputComponent;
	
	/** If set to true, then this actor will be able to recieve input delegate callbacks when in the editor. */
	UPROPERTY(EditAnywhere, Category = "Input|Editor", BlueprintSetter = SetReceivesEditorInput, BlueprintGetter = GetReceivesEditorInput)
	bool bReceivesEditorInput = false;
};

 


2. Editor Utility Object

사용자가 작성한 스크립트나 블루프린트를 실행시키는 기반 클래스.

UCLASS(Abstract, Blueprintable, meta = (ShowWorldContextPin))
class BLUTILITY_API UEditorUtilityObject : public UObject
{
	GENERATED_UCLASS_BODY()

	// Standard function to execute
	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Editor")
	void Run();
};

 

 

3. Aseet Action Utility

에셋 액션 관련 유틸리티의 기본 클래스. 해당 클래스를 상속한 파생한 클래스가 올바른 시그니처를 갖는 함수가 이벤트를 노출시킨다면 해당 함수나 이벤트는 콘텐츠 브라우저의 메뉴 옵션에 포함된다. 즉, 스크립트 액션을 작동시킬 대상이 콘텐츠 브라우저라면 위 클래스를 상속받는다.

/** 
 * Base class for all asset action-related utilities
 * Any functions/events that are exposed on derived classes that have the correct signature will be
 * included as menu options when right-clicking on a group of assets in the content browser.
 */
UCLASS(Abstract, hideCategories=(Object), Blueprintable)
class BLUTILITY_API UAssetActionUtility : public UEditorUtilityObject, public IEditorUtilityExtension
{
	GENERATED_BODY()

public:
	/**
	 * Return the class that this asset action supports (if not implemented, it will show up for all asset types)
	 * Do not do custom logic here based on the currently selected assets.
	 */
	UE_DEPRECATED(5.2, "GetSupportedClasses() instead, but ideally you're not requesting this directly and are instead using the FAssetActionUtilityPrototype to wrap access to an unload utility asset.")
	UFUNCTION(BlueprintPure, BlueprintImplementableEvent, Category="Assets", meta=(DeprecatedFunction, DeprecationMessage="If you were just returning a single class add it to the SupportedClasses array (you can find it listed in the Class Defaults).  If you were doing complex logic to simulate having multiple classes act as filters, add them to the SupportedClasses array.  If you were doing 'other' logic, you'll need to do that upon action execution."))
	UClass* GetSupportedClass() const;

	/**
	 * Returns whether or not this action is designed to work specifically on Blueprints (true) or on all assets (false).
	 * If true, GetSupportedClass() is treated as a filter against the Parent Class of selected Blueprint assets
	 */
	UFUNCTION(BlueprintPure, BlueprintImplementableEvent, Category="Assets")
	bool IsActionForBlueprints() const;

	/**
	 * Gets the statically determined supported classes, these classes are used as a first pass filter when determining
	 * if we can utilize this asset utility action on the asset.
	 */
	UFUNCTION(BlueprintPure, Category = "Assets")
	const TArray<TSoftClassPtr<UObject>>& GetSupportedClasses() const { return SupportedClasses; }

public:
	// Begin UObject
	virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const override;
	// End UObject

protected:
	/**
	 * The supported classes controls the list of classes that may be operated on by all of the asset functions in this
	 * utility class.
	 */
	UPROPERTY(EditDefaultsOnly, Category="Asset Support", meta=(AllowAbstract))
	TArray<TSoftClassPtr<UObject>> SupportedClasses;

	/**
	 * The supported conditions for any asset to use these utility functions.  Note: all of these conditions must pass
	 * in sequence.  So if you have earlier failure conditions you want to be run first, put them first in the list,
	 * if those fail, their failure reason will be handled first.
	 */
	UPROPERTY(EditDefaultsOnly, Category="Asset Support")
	TArray<FAssetActionSupportCondition> SupportedConditions;
};

이 밖에도 여러 가지 에디터에 관한 유틸리티 클래스를 상속받을 수 있다.
다른 건 제쳐두고, 핵심적인 블튜틸리티 예제만 살펴보자.

 

1. 콘텐츠 브라우저 에셋 액션 추가

먼저 위 글을 따라 Aseet Action Utility 클래스를 상속받는 블류틸리티 클래스를 하나 만들어준다.

그리고 간단한 함수와 확인할 수 있는 로직을 넣는다.

 

컴파일 후 콘텐츠 브라우저에서 아무 에셋이나 눌러 Scripted Asset Actions/{함수이름}이 잘 뜨는지 확인한다. {함수이름} 버튼을 눌렀을 경우 함수 내 로직이 문제없이 실행되는지 확인해보자.

 

2. 에디터 시작 시 블루프린트 로직 실행하기

먼저 Editor Utility Object 클래스를 상속받는 블류틸리티 클래스를 하나 만들어준다.

그리고 Run 함수를 오버라이딩한다.

 

간단하게 테스트용 로직을 넣는다.

위 작업만으로는 언리얼 에디터 시작 시 로직이 실행되지 않는다.

블루프린트를 컴파일, 저장한 후 에디터를 종료한다.

그리고 프로젝트 경로의 Config 폴더 속 DefaultEditorPerProjectUserSettings.ini 파일을 텍스트 에디터로 연다.

파일 내 텍스트에서 아래와 같은 섹션을 찾아내고, 찾지 못할 경우 직접 추가한다.

[/Script/Blutility.EditorUtilitySubsystem]

 

이제 위 섹션에 에디터 시작 시 동작하는 스타트업 오브젝트를 추가할 것이다.

스타트업 오브젝트로 동작하기 원하는 블루프린트 클래스 경로를 StartupObjects라는 배열에 추가해준다.
경로는 /Game/으로 시작해야하며 콘텐츠 브라우저 속 블루프린트 클래스 경로를 넣으면 된다. 
오브젝트 이름 뒤에 마침표를 붙이고 오브젝트 이름을 한 번 더 작성해준다.

[/Script/Blutility.EditorUtilitySubsystem]
StartupObjects=/Game/EditorUtilities/EUO_Test.EUO_Test

만약 스타트업 오브젝트를 여러 개로 두고 싶은 경우 +를 사용하여 추가한다.

[/Script/Blutility.EditorUtilitySubsystem]
StartupObjects=/Game/EditorUtilities/EUO_Test.EUO_Test
StartupObjects=/Game/EditorUtilities/EUO_Test.EUO_Test1
StartupObjects=/Game/EditorUtilities/EUO_Test.EUO_Test2

그러면 언리얼 에디터가 시작되면서 로직이 실행되는 것을 확인할 수 있다.

 

3. 액터 디테일 버튼 만들기

해당 구현은 매우 간단하다. 일반 블루프린트 클래스를 만들고 파라미터가 없는 커스텀 이벤트나 함수를 만든 후 디테일 패널에서 Call In Editor 체크박스를 체크해주면 끝이다.

Call In Editor 속성 true.

참고로 블류틸리티 클래스는 디테일 패널에 버튼을 노출시킬 수 없는데 언리얼 엔진 문서에 아래와 같이 써져 있다.

액터 베이스 클래스에서 파생된 에디터 유틸리티 블루프린트(Editor Utility Blueprint) 클래스의 경우, 에디터에서 호출 가능하다고 표시되는 함수나 커스텀 이벤트의 버튼이 디테일(Details) 패널에 노출되지 않습니다. 반드시 디테일 패널의 버튼을 사용하여 블루프린트 로직을 유도해야 한다면, 에디터 유틸리티 블루프린트 클래스가 아닌 일반 블루프린트 클래스에서 그래프를 생성해야 합니다. 그러나 언리얼 에디터에서 블루프린트 로직을 구동하기 위한 커스텀 UI를 생성하는 훨씬 유연하고 강력한 방법인 에디터 유틸리티 위젯(Editor Utility Widget)을 사용하는 것도 고려해 보세요.

 

즉, Editor Utility Actor를 상속받은 블류틸리티 클래스를 만들어도, 일반 블루프린트 클래스가 아닌 이상 위처럼 디테일 패널에 버튼을 노출시킬 수 없다. 만약 블루프린트 로직을 구동시키는 UI를 만들고 싶다면 Editor Utility Widget 사용을 추천하는 모양이다.

 

 

2. Editor Utility Widget

이번에는 Editor Utility Widget을 통해 에디터 전용 GUI까지 만들어보자.

 

에디터 유틸리티 위젯

Editor Utility, 에디터 유틸리티 위젯과 그 생성 방법을 설명합니다.

docs.unrealengine.com

Editor Utility Widget은 기본적으로 위 두 가지 Root 위젯을 지정할 수 있다.

 

위 Root 위젯은 ProjectSettings에서 지정이 가능하다.

 

VerticalBox를 Root Widget으로 지정하고 생성해준 다음 본격적으로 커스텀 툴을 구현해보자.

 

2) 커스텀 툴 구현

Editor Utility Widget을 만들면 UMG와 마찬가지로 Disgner 탭과 Graph 탭으로 나뉜다.

UMG와 동일하게 Disgner 탭에서 UI를 배치하고 Graph 탭에서 변수와 함수로 기능을 채워나가면 된다.

 

 

먼저 에디터에 여러 가지 속성을 집어넣기 위해 Designer 탭의 Hierachy 창에 Details View를 추가해준다.

 

 

Details View은 위 이미지처럼 지정된 카테고리(Planet) 안에 속성들을 표시하며 접고 여는 기능을 가진 위젯이다.

 

 

그리고 기능을 동작시킬 버튼(Editor Utility Button) 하나를 추가해준다.

 

 

Editor Utility Button과 Details View의 Detail 창에서 IsVariable을 체크한다.

 

 

그리고 Details View의 경우 표시할 카테고리명을 지정해준다. 이제 Graph 탭으로 넘어가서 UI를 표시하고 로직을 구성하면 된다.

카테고리명 Properties

 

IsVariable로 체크하면 My Blueprint 창에 배치했던 위젯과 동일한 이름, 자료형의 변수를 자동으로 추가된다.

 

 

변수의 Category를 Details View의 카테고리와 같은 이름으로 지정해주면 Details View에 포함되게 된다.

 

 

Set Object 노드를 사용하면 객체의 레퍼런스를 세팅하여 객체가 가진 변수를 Details View에 표시할 수 있다. 이는 에디터 창이 생기면서 초기화돼야 하므로 생성자에서 호출한다.

 

 

이후는 사용자가 원하는 툴의 기능 로직을 채워나가면 된다.