플러그인 시스템을 통해 개발자는 Unreal Editor 내에서 StaticMesh에 적용할 수 있는 새로운 도구, 수정자 및 렌더링 옵션을 생성할 수 있으며 이러한 새로운 기능을 다른 개발자 또는 더 넓은 커뮤니티와 쉽게 공유할 수 있다. StaticMesh 플러그인을 만들려면 개발자는 먼저 Unreal의 Asset Editor를 사용하여 수행할 수 있는 새 자산 유형을 정의해야 한다. 이 새로운 자산 유형은 새로운 재료 또는 충돌 모양과 같은 사용자 정의 속성 및 기능으로 확장될 수 있다. 플러그인이 생성되면 다른 프로젝트에서 사용할 수 있도록 패키징 및 배포할 수 있으며, 개발자는 언리얼 에디터의 사용자 인터페이스를 통해 자신의 StaticMesh에 새 기능을 쉽게 추가할 수 있다. 전반적으로 Unreal Engine의 StaticMesh 플러그인 시스템은 개발자가 엔진의 핵심 기능을 커스터마이즈하여 특정 요구 사항을 충족하고 사용자를 위해 보다 매력적이고 몰입감 있는 경험을 생성할 수 있는 강력하고 유연한 방법을 제공한다.

 

목차

     

     


     

     

     

     
    Plugins
      Example
        Example.Build.cs
    ExampleConsoleCommand.h .cpp 
    ExampleDebuggerCategory.h .cpp
    ExampleModule.h .cpp
    StaticMesh_Detail.h .cpp 생성
    Source
        Utilities
        CHelper.h 추가
    CLog.h .cpp 추가
        Global.h 추가
    CStaticMesh.h .cpp 생성
    .Build.cs
        .uproject
     

     

     

     

    Source에 Global.h와 Utilities(CHelper, CLog)폴더 추가하기

     

     


     

     

    Global.h 가져오기

     

    이전에 만들었던 Global.h를 가져와서 추가한다.

     

    Global.h

    더보기
    #pragma once
    
    #include "DrawDebugHelpers.h"
    
    #include "Kismet/KismetSystemLibrary.h"
    #include "Kismet/KismetMathLibrary.h"
    #include "Kismet/GameplayStatics.h"
    
    #include "Utilities/CHelpers.h"
    #include "Utilities/CLog.h"

     


     

     

    Utilities 폴더 가져오기 -  CHelpers.h, CLog.h .cpp

     

    이전에 만들었던 Utilites 폴더를 가져와서 추가한다.

    Utilities 폴더 안에는 CHelpers.h, CLog.h .cpp가 있다.

     

    해당 코드들의 변경사항은 없다.

     

     

     

     

     

     

     

     

     

     

     

    CHelpers.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    
    #define CheckTrue(x) { if(x == true) return; }
    #define CheckTrueResult(x, y) { if(x == true) return y; }
    
    #define CheckFalse(x) { if(x == false) return;}
    #define CheckFalseResult(x, y) { if(x == false) return y;}
    
    #define CheckNull(x) { if(x == nullptr) return;}
    #define CheckNullResult(x, y) { if(x == nullptr) return y;}
    
    
    #define CreateTextRender()\
    {\
    	CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Tex", Root);\
    	Text->SetRelativeLocation(FVector(0, 0, 100));\
    	Text->SetRelativeRotation(FRotator(0, 180, 0));\
    	Text->SetRelativeScale3D(FVector(2));\
    	Text->TextRenderColor = FColor::Red;\
    	Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center;\
    	Text->Text = FText::FromString(GetName().Replace(L"Default__", L""));\
    }
    
    
    class U2212_05_API CHelpers
    {
    public:
    	template<typename T>
    	static void CreateComponent(AActor* InActor, T** OutComponent, FName InName, USceneComponent* InParent = nullptr, FName InSocketName = NAME_None)
    	{
    		*OutComponent = InActor->CreateDefaultSubobject<T>(InName);
    
    		if (!!InParent)
    		{
    			(*OutComponent)->SetupAttachment(InParent, InSocketName); //이렇게 사용하면 Socket Name에 _를 사용하면 안 된다.
    
    			return;
    		}
    
    		InActor->SetRootComponent(*OutComponent);
    	}
    
    	//CreateActorComponent 추가
    	template<typename T>
    	static void CreateActorComponent(AActor* InActor, T** OutComponent, FName InName)
    	{
    		*OutComponent = InActor->CreateDefaultSubobject<T>(InName);
    	}
    
    	template<typename T>
    	static void GetAsset(T** OutObject, FString InPath)
    	{
    		ConstructorHelpers::FObjectFinder<T> asset(*InPath);
    		*OutObject = asset.Object;
    	}
    
    	template<typename T>
    	static void GetAssetDynamic(T** OutObject, FString InPath)
    	{
    		*OutObject = Cast<T>(StaticLoadObject(T::StaticClass(), nullptr, *InPath));
    	}
    
    	template<typename T>
    	static void GetClass(TSubclassOf<T>* OutClass, FString InPath)
    	{
    		ConstructorHelpers::FClassFinder<T> asset(*InPath);
    		*OutClass = asset.Class;
    	}
    
    	template<typename T>
    	static T* FindActor(UWorld* InWorld)
    	{
    		for (AActor* actor : InWorld->GetCurrentLevel()->Actors)
    		{
    			if (!!actor && actor->IsA<T>())
    				return Cast<T>(actor);
    		}
    
    		return nullptr;
    	}
    
    	template<typename T>
    	static void FindActors(UWorld* InWorld, TArray<T*>& OutActors)
    	{
    		for (AActor* actor : InWorld->GetCurrentLevel()->Actors)
    		{
    			if (!!actor && actor->IsA<T>())
    				OutActors.Add(Cast<T>(actor));
    		}
    	}
    
    	template<typename T>
    	static T* GetComponent(AActor* InActor)
    	{
    		return Cast<T>(InActor->GetComponentByClass(T::StaticClass()));
    	}
    
    	template<typename T>
    	static T* GetComponent(AActor* InActor, const FString& InName)
    	{
    		TArray<T*> components;
    		InActor->GetComponents<T>(components);
    
    		for (T* component : components)
    		{
    			if (component->GetName() == InName)
    				return component;
    		}
    
    		return nullptr;
    	}
    
    	static void AttachTo(AActor* InActor, USceneComponent* InParent, FName InSocketName)
    	{
    		InActor->AttachToComponent(InParent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), InSocketName);
    	}
    };

     

     

     CLog.h 

    더보기
    #pragma once
    #include "CoreMinimal.h"
    
    #define LogLine(){ CLog::Log(__FILE__, __FUNCTION__, __LINE__); }
    #define PrintLine(){ CLog::Print(__FILE__, __FUNCTION__, __LINE__); }
    
    class U2212_05_API CLog
    {
    public:
    	static void Log(int32 InValue);
    	static void Log(float InValue);
    	static void Log(const FString& InValue);
    	static void Log(const FVector& InValue);
    	static void Log(const FRotator& InValue);
    	static void Log(const UObject* InValue);
    	static void Log(const FString& InFileName, const FString& InFuncName, int32 InLineNumber);
    
    	static void Print(int32 InValue, int32 InKey = -1, float InDuration = 10, FColor InColor = FColor::Blue);
    	static void Print(float InValue, int32 InKey = -1, float InDuration = 10, FColor InColor = FColor::Blue);
    	static void Print(const FString& InValue, int32 InKey = -1, float InDuration = 10, FColor InColor = FColor::Blue);
    	static void Print(const FVector& InValue, int32 InKey = -1, float InDuration = 10, FColor InColor = FColor::Blue);
    	static void Print(const FRotator& InValue, int32 InKey = -1, float InDuration = 10, FColor InColor = FColor::Blue);
    	static void Print(const UObject* InValue, int32 InKey = -1, float InDuration = 10, FColor InColor = FColor::Blue);
    	static void Print(const FString& InFileName, const FString& InFuncName, int32 InLineNumber);
    };

     

     

     CLog.cpp

    더보기
    #include "Utilities/CLog.h"
    #include "Engine.h"
    
    DEFINE_LOG_CATEGORY_STATIC(GP, Display, All)
    
    void CLog::Log(int32 InValue)
    {
    	//GLog->Log("GP", ELogVerbosity::Display, FString::FromInt(InValue));
    	UE_LOG(GP, Display, L"%d", InValue);
    }
    
    void CLog::Log(float InValue)
    {
    	UE_LOG(GP, Display, L"%f", InValue);
    }
    
    void CLog::Log(const FString & InValue)
    {
    	UE_LOG(GP, Display, L"%s", *InValue);
    }
    
    void CLog::Log(const FVector & InValue)
    {
    	UE_LOG(GP, Display, L"%s", *InValue.ToString());
    }
    
    void CLog::Log(const FRotator & InValue)
    {
    	UE_LOG(GP, Display, L"%s", *InValue.ToString());
    }
    
    void CLog::Log(const UObject * InValue)
    {
    	FString str;
    
    	if (!!InValue)
    		str.Append(InValue->GetName());
    
    	str.Append(!!InValue ? " Not Null" : "Null");
    
    	UE_LOG(GP, Display, L"%s", *str);
    }
    
    void CLog::Log(const FString & InFileName, const FString & InFuncName, int32 InLineNumber)
    {
    	//C:\\Test\\Test.cpp
    
    	int32 index = 0, length = 0;
    	InFileName.FindLastChar(L'\\', index);
    
    	length = InFileName.Len() - 1;
    	FString fileName = InFileName.Right(length - index);
    
    	UE_LOG(GP, Display, L"%s, %s, %d", *fileName, *InFuncName, InLineNumber);
    }
    
    void CLog::Print(int32 InValue, int32 InKey, float InDuration, FColor InColor)
    {
    	GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, FString::FromInt(InValue));
    }
    
    void CLog::Print(float InValue, int32 InKey, float InDuration, FColor InColor)
    {
    	GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, FString::SanitizeFloat(InValue));
    }
    
    void CLog::Print(const FString & InValue, int32 InKey, float InDuration, FColor InColor)
    {
    	GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, InValue);
    }
    
    void CLog::Print(const FVector & InValue, int32 InKey, float InDuration, FColor InColor)
    {
    	GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, InValue.ToString());
    }
    
    void CLog::Print(const FRotator & InValue, int32 InKey, float InDuration, FColor InColor)
    {
    	GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, InValue.ToString());
    }
    
    void CLog::Print(const UObject * InValue, int32 InKey, float InDuration, FColor InColor)
    {
    	FString str;
    
    	if (!!InValue)
    		str.Append(InValue->GetName());
    
    	str.Append(!!InValue ? " Not Null" : "Null");
    
    	GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, str);
    }
    
    void CLog::Print(const FString& InFileName, const FString& InFuncName, int32 InLineNumber)
    {
    	int32 index = 0, length = 0;
    	InFileName.FindLastChar(L'\\', index);
    
    	length = InFileName.Len() - 1;
    	FString fileName = InFileName.Right(length - index);
    
    	FString str = FString::Printf(L"%s, %s, %d", *fileName, *InFuncName, InLineNumber);
    	GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Blue, str);
    }

     

     

    U2212_05 수정하기

     

     


     

     

    2212_05.uproject

     

     

     

     

     

     

     

     

     

     

     

     

     

     


     

     

     

    U2212_05.Build.cs

     

    U2212_05.Build.cs

    더보기
    // Copyright Epic Games, Inc. All Rights Reserved.
    
    using UnrealBuildTool;
    
    public class U2212_05 : ModuleRules
    {
    	public U2212_05(ReadOnlyTargetRules Target) : base(Target)
    	{
    		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
    
    
            PrivateIncludePaths.Add(ModuleDirectory);
    
            PublicDependencyModuleNames.Add("Core");
    
            PrivateDependencyModuleNames.Add("CoreUObject");
            PrivateDependencyModuleNames.Add("Engine");
            PrivateDependencyModuleNames.Add("InputCore");
            PrivateDependencyModuleNames.Add("HeadMountedDisplay");
            PrivateDependencyModuleNames.Add("NavigationSystem");
            PrivateDependencyModuleNames.Add("AIModule");
        }
    }

     

    Plugin     ->   Game의 private Module은 인식이 안 된다.

    U22212_05/CstaticMesh

     

     

    Source/U2212_05는 ModuleDirectory

     

     

    보통 private으로 만들고 필요한것만 public으로 연다.

     

     



    Plugins - Example - Source - Example - Example.Build.cs

    플러그인쪽에 있는 아래의 코드와 U2212_05쪽에 있는 위의 코드의 차이를 비교해보자.

     

    Example.Build.cs

     

    Example.Build.cs

    더보기
    using UnrealBuildTool;
    
    public class Example : ModuleRules
    {
    	public Example(ReadOnlyTargetRules Target) : base(Target) //public은 외부에 공개
    	{
    		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
    
            PublicIncludePaths.Add(ModuleDirectory);
    
            PublicDependencyModuleNames.Add("Core");
    
            PrivateDependencyModuleNames.Add("U2212_05"); //본인 모델은 private
    
            PrivateDependencyModuleNames.Add("CoreUObject");
            PrivateDependencyModuleNames.Add("Engine");
            PrivateDependencyModuleNames.Add("Slate");
            PrivateDependencyModuleNames.Add("SlateCore");
            PrivateDependencyModuleNames.Add("GameplayDebugger");
        }
    }

     

     

     



     

     

     

    CStaticMesh 생성

     

    새 C++ 클래스 - Actor - CStaticMes 생성 (직렬화 될 것이기 때문에 게임쪽에서 만든다) 

     

     

    CStaticMesh.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "CStaticMesh.generated.h"
    
    UCLASS()
    class U2212_05_API ACStaticMesh : public AActor
    {
    	GENERATED_BODY()
    	
    private:
    	UPROPERTY(VisibleAnywhere, Category = "Material")
    		class UMaterialInstanceConstant* Material;
    
    private:
    	UPROPERTY(VisibleAnywhere)
    		class UStaticMeshComponent* Mesh;
    
    public:	
    	ACStaticMesh();
    
    protected:
    	virtual void BeginPlay() override;
    };

    변수 생성

    • class UMaterialInstanceConstant* Material;
    • class UStaticMeshComponent* Mesh;

     

     

     

    CStaticMesh.cpp

    더보기
    #include "CStaticMesh.h"
    #include "Global.h"
    #include "Components/StaticMeshComponent.h"
    #include "Materials/MaterialInstanceConstant.h"
    
    ACStaticMesh::ACStaticMesh()
    {
    	CHelpers::CreateComponent<UStaticMeshComponent>(this, &Mesh, "Mesh");
    
    	UStaticMesh* mesh;
    	CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/MatineeCam_SM.MatineeCam_SM'");
    	Mesh->SetStaticMesh(mesh);
    
    	CHelpers::GetAsset<UMaterialInstanceConstant>(&Material, "MaterialInstanceConstant'/Game/M_StaticMesh_Inst.M_StaticMesh_Inst'");
    	Mesh->SetMaterial(0, Material);
    }
    
    void ACStaticMesh::BeginPlay()
    {
    	Super::BeginPlay();
    }

    Matinee Camera 생성

    • CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/MatineeCam_SM.MatineeCam_SM'");

     

    Matinee Camera 머테리얼 지정

    • CHelpers::GetAsset<UMaterialInstanceConstant>(&Material, "MaterialInstanceConstant'/Game /M_StaticMesh_Inst.M_StaticMesh_Inst'");

     

     

     

    Matinee Camera로 사용할 StaticMesh를 복사하여 생성 후 콘텐츠 폴더로 이동

    Matinee Camera에 사용할 머테리얼 생성

     

     

     

    CStaticMesh 기반 블루프린트 생성 - BP_CStaticMesh 생성

     


     

     

     

     

    Plugin 부분

     

     


     

    StaticMesh_Detail 생성

     

    새 C++ 클래스 - 없음 - StaticMesh_Detail 생성

     

     

    StaticMesh_Detail.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "IDetailCustomization.h"
    
    class EXAMPLE_API FStaticMesh_Detail : public IDetailCustomization //IDetailCustomization로부터 상속받는다.
    {
    public:
    	static TSharedRef<IDetailCustomization> MakeInstance();
    
    public:
    	void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
        
    private:
    	FReply OnClicked_Paint();
    };

     

     

     

     

    StaticMesh_Detail.cpp

    더보기
    #include "StaticMesh_Detail.h"
    #include "DetailLayoutBuilder.h"
    #include "DetailCategoryBuilder.h"
    #include "DetailWidgetRow.h"
    
    TSharedRef<IDetailCustomization> FStaticMesh_Detail::MakeInstance()
    {
    	return MakeShareable(new FStaticMesh_Detail());
    }
    
    void FStaticMesh_Detail::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
    {
    	IDetailCategoryBuilder& actor = DetailBuilder.EditCategory("Actor");
    	actor.SetCategoryVisibility(false); //actor 카테고리를 숨긴다.
    
    	IDetailCategoryBuilder& lighting = DetailBuilder.EditCategory("Lighting");
    
    	IDetailCategoryBuilder& mesh = DetailBuilder.EditCategory("Mesh");
    
    	mesh.AddCustomRow(FText::FromString("Mesh"))
    	.NameContent()
    	[
    		SNew(STextBlock) //SNew는 Slate UI들을 동적할당 한다.
    		.Text(FText::FromString("Color"))
    	];
    
    }

     

     

    "AddCustomRow"는 개발자가 주어진 위젯에 커스텀 행을 추가할 수 있게 해주는 언리얼 엔진의 기능이다. 이 기능은 엔진의 기본 위젯에서 제공하는 것 이상의 추가 기능이 필요한 사용자 정의 편집기 패널 또는 사용자 인터페이스 요소를 생성하는 데 자주 사용된다.

     

    "AddCustomRow"를 사용하려면 먼저 행을 추가할 위젯 컨테이너의 새 인스턴스를 생성해야 한다. 컨테이너가 있으면 "AddCustomRow" 함수를 호출하고 컨테이너에 대한 참조와 새 행을 생성하는 데 사용할 위임 함수를 전달할 수 있다.

     

    대리자 함수는 행의 실제 콘텐츠를 생성하는 역할을 담당하며 여기에는 필요한 위젯 또는 사용자 지정 논리의 조합이 포함될 수 있다. 행이 생성되면 컨테이너에 추가되고 화면에 표시된다.

     

     

     

     

    actor.SetCategoryVisibility(false) 적용 전후 

     

     

     

     

     

     

    StaticMesh_Detail.cpp

    더보기
    #include "StaticMesh_Detail.h"
    #include "DetailLayoutBuilder.h"
    #include "DetailCategoryBuilder.h"
    #include "DetailWidgetRow.h"
    
    TSharedRef<IDetailCustomization> FStaticMesh_Detail::MakeInstance()
    {
    	return MakeShareable(new FStaticMesh_Detail());
    }
    
    void FStaticMesh_Detail::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
    {
    	IDetailCategoryBuilder& actor = DetailBuilder.EditCategory("Actor");
    	//actor.SetCategoryVisibility(false); //actor 카테고리를 숨긴다.
    
    	TArray<TSharedRef<IPropertyHandle>> handles;
    	actor.GetDefaultProperties(handles);
    
    	for(TSharedRef<IPropertyHandle> handle : handles)
    	{
    		// GLog->Log(handle->GetProperty()->GetName());
    		// GLog->Log(handle->GetProperty()->GetPathName());
    		// GLog->Log(handle->GetProperty()->GetFullName());
    
    		if (handle->GetProperty()->GetName().Compare("bCanBeDamaged") == 0)
    			DetailBuilder.HideProperty(handle); //해당 조건이면 handle를 숨겨준다.(=actor에 있는 bCanBeDamaged를 숨겨준다.
    	}
    
    	IDetailCategoryBuilder& mesh = DetailBuilder.EditCategory("Mesh");
    
    	mesh.AddCustomRow(FText::FromString("Mesh"))
    	.NameContent()
    	[
    		SNew(STextBlock) //SNew는 Slate UI들을 동적할당 한다.
    		.Text(FText::FromString("Color"))
    	]
    	.ValueContent()
    	[
    		SNew(SButton)
    		.VAlign(VAlign_Center)
    		.HAlign(HAlign_Fill)
    		.OnClicked(this, &FStaticMesh_Detail::OnClicked_Paint)
    		//.Content()
    		[
    			SNew(STextBlock)
    			.Text(FText::FromString("Paint"))
    		]
    	];
    
    }
    
    FReply FStaticMesh_Detail::OnClicked_Paint()
    {
    
    	return FReply::Handled();
    }

     

     

     

     

     

     


     

     

    '⭐ Unreal Engine > UE Plugin - Basic' 카테고리의 다른 글

    [UE] Plugin (Save StaticMesh & RenderData, LOD)  (0) 2023.04.12
    [UE] Plugin (StaticMesh Detail)  (0) 2023.04.07
    [UE] Console Command  (0) 2023.04.05
    [UE] Console, DrawDebugLine  (0) 2023.04.04
    [UE] Plugin, Slate UI  (0) 2023.04.03