전체 소스 코드는 GitHub 에 있습니다.
CSV 로 시작하는 것이 Excel 을 번거롭게 다른이름으로 저장할 필요가 있어서,
신버전을 만들었습니다.
1. CSV -> DataTableAsset (구버전)
https://github.com/Kympy/Unreal_CSV_To_DataTableAsset
2. Excel -> DataTableAsset (신버전)
https://github.com/Kympy/Unreal_Excel2Data
제일 먼저 할 일은 모듈 생성이다.
모듈을 만드는 법에 대해서는 따로 기술하겠다.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
class FDGExcel2Data : public IModuleInterface
{
public:
// 모듈 시작 시 생성한 Extender -> 해제 시 필요함.
TSharedPtr<FExtender> Extender;
virtual void StartupModule() override;
virtual void ShutdownModule() override;
void AddMenuBar(FMenuBarBuilder& MenuBarBuilder);
void FillMenu(FMenuBuilder& MenuBuilder);
void MakeDataTableStruct();
void MakeDataTableAsset();
void CreateFolders();
};
헤더 파일을 대충 이렇게 정의하고 설명하자면,
AddMenuBar 는 에디터 상단에 커스텀 버튼을 추가 하기 위해 필요하고,
FillMenu 는 추가된 버튼에 하위 메뉴들을 만들기 위해 필요하다.
void FDGExcel2Data::AddMenuBar(FMenuBarBuilder& MenuBarBuilder)
{
MenuBarBuilder.AddPullDownMenu(
FText::FromString("DGExcel2Data"),
FText::FromString("Convert Excel file to csv and generate c++ struct file and data table assets."),
FNewMenuDelegate::CreateRaw(this, &FDGExcel2Data::FillMenu)
);
}
AddMenuBar 는 위와 같다. 메뉴버튼의 하위 동작을 &로 링크해준다.
void FDGExcel2Data::FillMenu(FMenuBuilder& MenuBuilder)
{
MenuBuilder.BeginSection("Data Table");
{
MenuBuilder.AddMenuEntry(
FDataTableMakerCommand::Get().Cmd_MakeStruct,
NAME_None,
FText::FromString("1. Make Data Table C++ Struct"),
FText::FromString("Make data table c++ struct. *FIRST*"),
FSlateIcon()
);
MenuBuilder.AddMenuEntry(
FDataTableMakerCommand::Get().Cmd_MakeAsset,
NAME_None,
FText::FromString("2. Make Data Table Asset"),
FText::FromString("Make data table asset from struct."),
FSlateIcon()
);
MenuBuilder.AddMenuEntry(
FDataTableMakerCommand::Get().Cmd_CreateFolder,
NAME_None,
FText::FromString("Create Folders"),
FText::FromString("Create necessary folders."),
FSlateIcon()
);
}
MenuBuilder.EndSection();
}
FillMenu 는 위와 같다.
여기서 3개의 하위메뉴를 만들었는데, 그대로 복붙하면 FDataTableMakerCommand 여기에서 에러가 날 것이다.
직접 만든 클래스이기 때문이다.
각 하위메뉴를 눌렀을 때 실행될 커맨드를 연결해주는 것인데 그러려면 이 클래스 부터 만들어야한다.
#pragma once
class FDataTableMakerCommand : public TCommands<FDataTableMakerCommand>
{
public:
FDataTableMakerCommand();
virtual void RegisterCommands() override;
TSharedPtr<FUICommandInfo> Cmd_MakeStruct;
TSharedPtr<FUICommandInfo> Cmd_MakeAsset;
TSharedPtr<FUICommandInfo> Cmd_CreateFolder;
};
<FDataTableMakerCommand.h>
클래스 이름은 편한대로 짓는다.
TSharedPtr<FUICommandInfo> 로 사용할 3개의 커맨드 변수를 선언했다.
RegisterCommands() 는 상속받는 인터페이스에 정의된 함수이다.
#include "DataTableMakerCommand.h"
// NSLOCTEXT( 로컬라이징된 네임스페이스 (모듈이름 등등), 텍스트 식별할 키, 실제 텍스트)
FDataTableMakerCommand::FDataTableMakerCommand() : TCommands("DGExcel2Data", NSLOCTEXT("DGExcel2Data", "GenerateDataTable", "Generate Data Table"), NAME_None, FAppStyle::GetAppStyleSetName())
{
}
<FDataTableMakerCommand.cpp>
cpp 파일에는 생성자를 써줘야 하는데 조금 까다롭다.
정확히 이해하지는 못했지만 위 같은 형식으로 사용한다.
void FDataTableMakerCommand::RegisterCommands()
{
// UI_COMMAND 사용하려면 LOCTEXT_NAMESPACE 정의가 필요함 - "" 로 설정 시 특정 네임 스페이스에 속하지 않게 가능
#define LOCTEXT_NAMESPACE "DGExcel2Data"
UI_COMMAND(Cmd_MakeStruct, "Make data table struct", "Make C++ sturct", EUserInterfaceActionType::Button, FInputGesture());
UI_COMMAND(Cmd_MakeAsset, "Make data table asset", "Make data table asset", EUserInterfaceActionType::Button, FInputGesture());
UI_COMMAND(Cmd_CreateFolder, "Create folder", "Create folder", EUserInterfaceActionType::Button, FInputGesture());
#undef LOCTEXT_NAMESPACE
}
오버라이드한 함수는 위와 같이 작성한다.
#define 부분이 중요하다.
LOCTEXT_NAMESPACE 이후 부분이 생성자의 NSLOCTEXT() 첫번째 요소와 동일해야한다는 것을
해외 포럼에서 본것 같은데 테스트는 해보지 않았다.
일단 나는 동일하도록 작성했다.
여기까지 만들었으면 FDataTableMakerCommand 에 대한 컴파일 에러는 해결되었을 것이다.
다시 돌아와 StartUpModule() 함수를 작성한다.
#include "DGExcel2Data.h"
#include "LevelEditor.h"
#include "Commands/DataTableMakerCommand.h"
IMPLEMENT_MODULE(FDGExcel2Data, DGExcel2Data)
void FDGExcel2Data::StartupModule()
{
UE_LOG(LogTemp, Log, TEXT("DGExcel2Data Module Started"));
// 커맨드 생성
TSharedPtr<FUICommandList> GenerateDataTableCommand = MakeShareable(new FUICommandList);
if (!GenerateDataTableCommand)
{
UE_LOG(LogTemp, Error, TEXT("New FUICommandList failed."));
return;
}
FDataTableMakerCommand::Register();
// 액션 맵핑
GenerateDataTableCommand->MapAction(
FDataTableMakerCommand::Get().Cmd_MakeStruct,
FExecuteAction::CreateRaw(this, &FDGExcel2Data::MakeDataTableStruct),
FCanExecuteAction()
);
GenerateDataTableCommand->MapAction(
FDataTableMakerCommand::Get().Cmd_MakeAsset,
FExecuteAction::CreateRaw(this, &FDGExcel2Data::MakeDataTableAsset),
FCanExecuteAction()
);
GenerateDataTableCommand->MapAction(
FDataTableMakerCommand::Get().Cmd_CreateFolder,
FExecuteAction::CreateRaw(this, &FDGExcel2Data::CreateFolders),
FCanExecuteAction()
);
Extender = MakeShareable(new FExtender);
Extender->AddMenuBarExtension(
"Help",
EExtensionHook::After,
GenerateDataTableCommand,
FMenuBarExtensionDelegate::CreateRaw(this, &FDGExcel2Data::AddMenuBar)
);
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(Extender);
일단 제일 첫 줄이 매우 중요하다. 없으면 안된다.
IMPLEMENT_MODULE(FDGExcel2Data, DGExcel2Data)
MakeShareable 로 생성한 포인터에 MapAction 을 호출하여 아까 만든 클래스의 커맨드들을 등록하고,
커맨드와 연결될 나의 함수를 연결한다.
Extender 를 생성하고 AddMenuBarExtension 함수를 호출하여 나의 AddMenuBar 함수를 연결한다.
"Help" 는 뭐냐면,
에디터에 상단바를 보면 Help 메뉴가 있을 건데 그 Help 를 말하는 것이다. 한글 버전이라면 도움말? 이 될 것이다.
Hook::After 는 Help 다음에 내 커스텀 메뉴를 위치하겠다는 의미다.
생성한 Extender 를 LevelEditorModule 에 추가해주는 과정까지 하게되면,
내 모듈이 실행 될 때 커스텀 버튼을 추가하고 하위메뉴를 만들고 기능까지 연결되는 것이다.
void FDGExcel2Data::ShutdownModule()
{
IModuleInterface::ShutdownModule();
if (Extender.IsValid())
{
FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr<FLevelEditorModule>("LevelEditor");
if (LevelEditorModule)
{
LevelEditorModule->GetMenuExtensibilityManager()->RemoveExtender(Extender);
}
Extender.Reset();
}
FDataTableMakerCommand::Unregister();
}
모듈 종료 시에는 위와같이 사용한 것들을 해제하는 과정이 필요하다.
void FDGExcel2Data::MakeDataTableAsset()
{
FString PythonScriptPath = FPaths::Combine(FPaths::ProjectContentDir(), "Python", "DGExcel2Data", "asset_generator.py");
PythonScriptPath = FPaths::ConvertRelativePathToFull(PythonScriptPath);
PythonScriptPath.InsertAt(0, "py ");
GEngine->Exec(nullptr, *PythonScriptPath);
}
마지막으로 하위메뉴에 각자 연결했던 실행하고자 하는 함수 중 하나만 설명하겠다.
함수를 만들고 그 안에 원하는 기능을 작성하면 되는데,
여기서 내가 필요한 건 Python 스크립트 실행이다.
py 파일의 경로를 알아 낸 후 맨 앞에 py 를 붙인다.
이 string을 엔진의 커맨드라인에 넣어 실행시킨다.
'언리얼5 & C++' 카테고리의 다른 글
[메모] 언리얼에서 배경을 투명으로 PNG 캡쳐하기 (0) | 2024.11.21 |
---|---|
런타임 생성 물체를 Navigation System 에서 인식하는법 (0) | 2024.11.18 |
언리얼5 C++ | 서브 레벨 스트리밍, 맵 로드 하기 (1) | 2024.02.16 |
언리얼5 C++ | 레벨 열기와 특정 게임모드로 레벨 열기 (0) | 2024.02.16 |
[언리얼5] Data Table 로더 만들기 *3부* (CSV) with Python / 에셋 생성하기 (1) | 2024.01.24 |