플러그인 시스템을 통해 개발자는 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