플러그인 메인 클래스 구현 플러그인 메인 클래스는 모듈을 초기화하고 실행하는 역할을 한다. Unreal Editor에서 "Add New -> C++ Class"를 선택하고 "Object"를 선택하여 플러그인 메인 클래스를 만든다. 이 클래스는 모듈을 초기화하고 실행할 때 호출된다.

 

목차

     

     


     

     

     

     
    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
     

     

     

     

     

    이론 설명

     

     


     

     

    MainFrame과 WinAPI

     

     

    ----------------Viewport------------ MainFrame

     

    ---------------------------------------- WinAPI

     

    WinAPI에 종속될 파일

     

     


     

    단편화

     

    CPU의 Register

        |

    Cache Memory (Locality 지역성)

        |

    RAM

        ↕

    I/O (SSD) 가상 메모리 영역

     

     

    셋 다 간편화 발생한다.

     

     

     

    디스크 단편화 

    크기와 디스크 할당 크기가 3byte 차이가 난다.

     

    부족한 RAM 메모리를 커버하기 위해 SSD의 메모리를 사용한다. CPU에서 보기에는 같은 주소체계를 가지고 있다.  

    RAM과 I/O(SSD) 사이에도 단편화가 발생한다.

     


     

     

    와일드카드 문자

     

    https://support.microsoft.com/ko-kr/office/%EC%99%80%EC%9D%BC%EB%93%9C%EC%B9%B4%EB%93%9C-%EB%AC%B8%EC%9E%90%EC%9D%98-%EC%98%88-939e153f-bd30-47e4-a763-61897c87b3f4

     

    와일드카드 문자의 예 - Microsoft 지원

    맞춤법을 정확히 기억하지 못하면 특정 항목을 찾으려고 쿼리에서 와일드카드 문자를 사용해 엽니다. 와일드카드는 텍스트 값에서 알 수 없는 문자를 사용할 수 있는 특수 문자로, 유사하지만

    support.microsoft.com

     

     


     

     

     

     

    Plugin 코드 추가

     

     


     

     

    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");
            PrivateDependencyModuleNames.Add("DesktopPlatform");
            PrivateDependencyModuleNames.Add("MainFrame");
        }
    }

    PrivateDependencyModule 추가

    • PrivateDependencyModuleNames.Add("DesktopPlatform");
    • PrivateDependencyModuleNames.Add("MainFrame");

     


     

     

     

    StaticMesh_Detail

     

    StaticMesh_Detail.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "IDetailCustomization.h"
    
    struct FStaticMesh_DetailData //UObject로부터 상속받은것이 아니기 때문에 직접 직렬화를 해줘야 한다.
    {
    	TArray<FVector> Positions;
    	TArray<FVector> Normals;
    	TArray<FColor> Colors;
    	TArray<FVector2D> Uvs;
    	TArray<int32> Indices;
    
    	FVector Extent; //부피
    	float Radius; //반지름이지만 지름으로 사용.
    
    	friend FArchive& operator<<(FArchive& InArchive, FStaticMesh_DetailData& InValue)
    	{
    		return InArchive
    			<< InValue.Positions
    			<< InValue.Normals
    			<< InValue.Uvs
    			<< InValue.Colors
    			<< InValue.Indices
    			<< InValue.Extent
    			<< InValue.Radius;
    	}
    };
    
    class EXAMPLE_API FStaticMesh_Detail : public IDetailCustomization //IDetailCustomization로부터 상속받는다.
    {
    public:
    	static TSharedRef<IDetailCustomization> MakeInstance();
    
    public:
    	void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
    
    private:
    	FReply OnClicked_Paint();
    	FReply OnClicked_SaveMesh();
    
    private:
    	TArray<TWeakObjectPtr<UObject>> Objects;
    };

    변화없음

     

     

     

    StaticMesh_Detail.cpp

    더보기
    #include "StaticMesh_Detail.h"
    #include "DetailLayoutBuilder.h"
    #include "DetailCategoryBuilder.h"
    #include "DetailWidgetRow.h"
    #include "Misc/MessageDialog.h"
    #include "Serialization/BufferArchive.h" //행렬화 관련 헤더. Buffer를 활용하여 내보내는데 필요하다.
    #include "DesktopPlatformModule.h"
    #include "Interfaces/IMainFrameModule.h"
    
    #include "U2212_05/CStaticMesh.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"))
    		]
    	];
    
    	mesh.AddCustomRow(FText::FromString("SaveMesh"))
    	.NameContent()
    	[
    		SNew(STextBlock) //SNew는 Slate UI들을 동적할당 한다.
    		.Text(FText::FromString("Mesh"))
    	]
    	.ValueContent()
    	[
    		SNew(SButton)
    		.VAlign(VAlign_Center)
    		.HAlign(HAlign_Fill)
    		.OnClicked(this, &FStaticMesh_Detail::OnClicked_SaveMesh)
    		//.Content()
    		[
    			SNew(STextBlock)
    			.Text(FText::FromString("SaveMesh"))
    		]
    	];
    
    	DetailBuilder.GetObjectsBeingCustomized(Objects);//TWeakObjectPtr의 Objcts를 사용하니 가비지 콜렉터에 의해 관리될 것이다.
    }
    
    FReply FStaticMesh_Detail::OnClicked_Paint()
    {
    	for(TWeakObjectPtr<UObject> obj : Objects)
    	{
    		ACStaticMesh* mesh = Cast<ACStaticMesh>(obj); //CStaticMesh 캐스팅한다.
    
    		if (!!mesh) //mesh가 있다면
    			mesh->Paint(); //mesh에 Paint()를 사용하여 색을 적용.
    	}
    	return FReply::Handled();
    }
    
    FReply FStaticMesh_Detail::OnClicked_SaveMesh()
    {
    	if(Objects[0]->GetWorld()->IsPlayInEditor()) //Objecs[0]이 Editor상에서 플레이 되었다면
    	{
    		FMessageDialog dialog;
    		dialog.Debugf(FText::FromString("In GameMode, it is not working."));
    
    		return FReply::Unhandled();//처리하지 못했기 때문에 다음으로 넘겨줘야 한다. 그래서 Unhandled()로 return
    	}
    
    	TArray<ACStaticMesh*> meshActors;
    	for (TWeakObjectPtr<UObject> obj : Objects)
    	{
    		ACStaticMesh* mesh = Cast<ACStaticMesh>(obj);
    		if (mesh == nullptr) continue; //캐스팅 실패 시에 저장할 자료형이 아니므로 continue로 패스
    
    		meshActors.Add(mesh);//캐스팅 성공 시에 mesh 추가
    	}
    
    	for(int32 i=0; i< meshActors.Num(); i++)
    	{
    		UActorComponent* actorComponent = meshActors[i]->GetComponentByClass(UStaticMeshComponent::StaticClass());//Component의 최상위인 UActorComponent로 리턴
    		UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(actorComponent);//UStaticMeshComponent로 사용하기 위해 actorComponent 캐스팅 
    		if (MeshComponent == nullptr) return FReply::Unhandled();//meshComponent가 없는 경우, Unhandled()가서 처리하라.
    
    		UStaticMesh* mesh = MeshComponent->GetStaticMesh();
    		if (mesh == nullptr) return FReply::Unhandled(); //mesh가 없는 경우, , Unhandled()가서 처리하라.
    
    		FStaticMeshRenderData* renderData = mesh->RenderData.Get(); //renderData는 정점 데이터
    		if (renderData->LODResources.Num() < 1) return FReply::Unhandled();
    
    		FStaticMeshLODResources& lod = renderData->LODResources[0]; 
    		FPositionVertexBuffer& vb = lod.VertexBuffers.PositionVertexBuffer;//PositionVertexBuffer 정보
    		FStaticMeshVertexBuffer& meshVB = lod.VertexBuffers.StaticMeshVertexBuffer;//StatMeshVertexBuffer 정보(Uv, Normal 정보를 포함함)
    		FColorVertexBuffer& colorVB = lod.VertexBuffers.ColorVertexBuffer;//ColorVertexBuffer 정보
    		FRawStaticIndexBuffer& ib = lod.IndexBuffer;//IndexBuffer 정보 //인덱스는 그리기 순서. 양수여야 하지만 누락시키는 경우를 위해 음수도 넣어줄 수 있다.
    
    		uint32 vertexCount = vb.GetNumVertices();
    		int32 triangleCount = ib.GetNumIndices() / 3;//인덱스 3개가 모여야 삼각형이 되어 하나의 단위가 된다. 엔진에서는 주로 triangleCount를 사용한다. 
    
    		GLog->Logf(L"%d Mesh, Vertex Count : %d", i + 1, vertexCount);
    		GLog->Logf(L"%d Mesh, Triangle Count : %d", i + 1, triangleCount);
    
    		FStaticMesh_DetailData data; //헤더의 구조체 FStaticMesh_DetailData 사용
    
    		TArray<FColor> colors;
    		colorVB.GetVertexColors(colors);
    
    		if(colors.Num() < 1)
    		{
    			for (uint32 index = 0; index < vertexCount; index++)
    				colors.Add(FColor(255, 255, 255, 255));
    		}
    
    
    		FVector minBounds = FVector(+MAX_FLT, +MAX_FLT, +MAX_FLT);//MAX_FLT는 float의 최대값 매크로
    		FVector maxBounds = FVector(-MAX_FLT, -MAX_FLT, -MAX_FLT);
    
    		for (uint32 index = 0; index < vertexCount; index++)
    		{
    			FVector position = vb.VertexPosition(index);
    			//최대 최소를 구한다.
    			if (position.X < minBounds.X) minBounds.X = position.X;
    			if (position.Y < minBounds.Y) minBounds.Y = position.Y;
    			if (position.Z < minBounds.Z) minBounds.Z = position.Z;
    
    			if (position.X > maxBounds.X) maxBounds.X = position.X;
    			if (position.Y > maxBounds.Y) maxBounds.Y = position.Y;
    			if (position.Z > maxBounds.Z) maxBounds.Z = position.Z;
    
    			//정점 정보를 저장한다.
    			data.Positions.Add(position);//위치 저장.
    			data.Normals.Add(meshVB.VertexTangentZ(index)); //z값은 수직이기 때문에 사용.
    			data.Uvs.Add(meshVB.GetVertexUV(index, 0));//Uv 레이어 번호는 0
    			data.Colors.Add(colors[index]);
    		}
    
    		float x = FMath::Abs(minBounds.X - maxBounds.X);
    		float y = FMath::Abs(minBounds.Y - maxBounds.Y);
    		float z = FMath::Abs(minBounds.Z - maxBounds.Z);
    
    		data.Extent = FVector(x, y, z); //부피
    		data.Radius = FMath::Max(x, y);
    		data.Radius = FMath::Max(data.Radius, z); //가장 큰 값을 지름으로 설정하였다.
    
    		GLog->Logf(L"x : %0.1f, y : %0.1f, z : %0.1f", x, y, z);
    		GLog->Logf(L"Radius : %f", data.Radius);
    
    		TArray<uint32> indices;
    		ib.GetCopy(indices);
    		data.Indices.Insert((int32*)indices.GetData(), triangleCount * 3, 0);//pointer를 사용하여 Insert// triangleCount를 위에서 /3 해주었기 때문에 *3을 해서 돌려준다.
    
    		IMainFrameModule& mainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
    		void* handle = mainFrame.GetParentWindow()->GetNativeWindow()->GetOSWindowHandle();//NativeWindow()가 실제 창 객체. GetOSWindowHandle()는 각 운영체제에 맞는 WindowHandle를 리턴해준다.//WindowHandle식별자는 dword x64면 가변형이다. 4byte로 처리가능하면 4byte, 불가능하면 8byte이다.
    
    		FString title = meshActors[i]->GetName();
    
    		FString path;
    		FPaths::GetPath(path);
    
    		TArray<FString> fileNames;
    
    		IDesktopPlatform* platform = FDesktopPlatformModule::Get();
    		platform->SaveFileDialog(handle, title, path, title + ".bin", "Mesh Binary File(*.bin)|*.bin", EFileDialogFlags::None, fileNames);//title은 파일 이름, EFileDialogFlags는 공유속성(None으로 설정하였다)
    		if (fileNames.Num() < 1) continue;
    
    		// for (const FString& name : fileNames)
    		// 	GLog->Log(name);
    
    		FBufferArchive buffer;
    		buffer << data;
    
    		FFileHelper::SaveArrayToFile(buffer, *fileNames[0]);
    		buffer.FlushCache();
    		buffer.Empty();
    
    		FString text;
    		for (int32 index = 0; index < data.Positions.Num(); index++)
    		{
    			text.Append(data.Positions[index].ToString() + ", ");
    			text.Append(data.Normals[index].ToString() + ", ");
    			text.Append(data.Uvs[index].ToString() + ", ");
    			text.Append(data.Colors[index].ToString() + "\r\n");//\r 줄 끝으로 보내준다.
    		}
    
    		FString textFileName = FPaths::GetBaseFilename(fileNames[0], false);
    		FString textSaveName = textFileName + ".csv"; //파일확장자 .csv
    
    		FFileHelper::SaveStringToFile(text, *textSaveName);//확장자.csv를 포함한 파일이름으로 저장
    
    
    		FString str;
    		str.Append(FPaths::GetBaseFilename(fileNames[0]));
    		str.Append(" Saved.");
    
    		GLog->Log(str);
    	}
    
    	return FReply::Handled();
    }

     

     

     EFileDialogFlags

    • ex. 엑셀은 파일을 연 상태에서 같은 파일을 열어 동시 편집할 수 없다. 반면에 메모장은 동시 편집이 가능하다. 
    • 이와 같은 기능을 공유속성(Sharable)이라고 한다.
    • EFileDialogFlags::None으로 설정하면 공유속성이 없게 만드는 것이다.

     

     

     

    ※참고사항: FDesktopPlatformModule 클래스

     

     


     

     

     

     

    실행화면