게임 엔진 개발기 #1
0편에서 말했 듯, 게임 엔진 개발에 가장 중요한게 프로젝트의 구조인 것 같습니다. 그래서 이번에는 구조를 좀 정해볼려고 하는데 결국은 언리얼의 모듈 방식이 가장 좋은 것 같습니다. 그래서 언리얼 방식으로 Target.cs 랑 Module.cs 을 분석해서 프로젝트를 구성하는 기능을 구현해봤습니다.
유틸리티 프로젝트 (a.k.a Framework Utility Tool)
유틸리티 프로젝트를 만들기 직전 고민했던 것 중 하나가 프로젝트를 UHT처럼 C#으로 만들지 아니면 기존 프로젝트에 사용했던 것처럼 C++로 만들지였습니다.
기능 유틸리티 프로젝트에 기능 명세로 생각한 것들은 Project 파일을 생성하고, Header를 분석해서 클래스 구조를 자동 생성하고, 프로젝트의 3rd party 라이브러리를 자동으로 세팅해주는 것 입니다.
결국 c#을 사용하기로 결정했는데, 이유는 이전 프로젝트에서 c++을 사용해서 위 기능들을 구현했었는데 다 좋고 clang도 연결해서 header 분석도 하고 했는데, 가장 큰 단점이 생산성이 낮고 빌드하는데 오래 걸린다는 점이었습니다. 그래서 c#을 알아보던 도중 nuget 에 clang wrapper 라이브러리들이 존재하는 것을 깨닫고 c#을 사용하기로 결정했습니다.
해당 구현은 다음 프로젝트 등을 참조했습니다.
프로젝트 생성
예시
using FrameworkUtilityTool.Configuration;
public class DX11RHI : ModuleRules
{
public DX11RHI(TargetRules target) : base(target)
{
PublicIncludePaths.AddRange(new string[] { "Public" });
PrivateIncludePaths.AddRange(new string[] { "Private" });
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"RHI",
});
PrivateThirdPartyModuleNames.AddRange(new string[] {
"DX11",
});
}
}
이런식으로 Module을 구성했습니다.
다만 Target.cs는 구현에 조금 복잡한 감이 있어서 일단은 Module.cs로 전체 프로젝트를 구성했습니다.

그래서 이런식으로 구조가 만들어지게 프로젝트를 구성했습니다.
Header 생성
QCLASS()
class RHI_API QTexture : public QObject {
protected:
QPROPERTY()
uint32_t Width = 0;
QPROPERTY()
uint32_t Height = 0;
QPROPERTY()
uint32_t Depth = 0;
QPROPERTY()
EPixelFormat Format = EPixelFormat::RGBA8;
QPROPERTY()
uint32_t RowPitch = 0;
QPROPERTY()
uint32_t SlicePitch = 0;
...
}
일단 예시로 Class에 Macro를 활용해서 유틸리티 툴이 클래스를 어떻게 파싱할지 표시해주는 방식을 구상해봤습니다. 기본 베이스는 언리얼의 그것이지만 자세하게 들어가면 달라질 것 같습니다.
#include "Texture.h"
QClass *Generated_Initializer_Class_ACube();
static FInitClassOnStart Generated_InitClassOnStart_Class_ACube(&Generated_Initializer_Class_ACube, &ACube::StaticClass, TEXT("ACube"), TEXT(""));
IMPLEMENT_CLASS(ACube)
QClass *Generated_Initializer_Class_ACube() {
static QClass *Instance = nullptr;
if (!Instance) {
Instance = ACube::StaticClass();
Instance->SetParentClass(QObject::StaticClass());
}
return Instance;
}
내부적으로는 이런식으로 정보들을 정의하게 하여 일단은 Class 정보를 가지고 있게 구상했습니다.
이렇게 구상 후 구현을 하던 도중 문제가 생겼는데 생각보다 clang의 분석에 시간이 오래 걸리고, 결정적으로 소스 코드까지 필요한 점이었습니다. 다행히도 소스 코드가 필요한 부분은 clang의 옵션에 -fsyntax-only를 주면 AST만 분석하게 만들 수 있습니다..
CppParserOptions Options = new CppParserOptions();
Options.ParseAsCpp = true;
Options.AdditionalArguments.Add("-xc++");
Options.AdditionalArguments.Add("-fsyntax-only"); // AST 만
Options.AdditionalArguments.Add("-std=c++17");
그리고 clang의 파싱이 오래 걸리는 문제는 미리 header를 분석해서 매크로가 있는 클래스만 뽑아내서 심볼로 가지고 있고 해당 클래스만 분석하도록 만들었습니다.
// 해더를 파싱해서 Class, Struct, Enum, Property 정보를 획득
SymbolParser.SymbolParser parser = new SymbolParser.SymbolParser(FilePath);
List<Symbol> symbols = parser.Run(source, file);
if (symbols == null)
{
Console.WriteLine($"'{file}': Error: Parsing failed.");
return;
}
else if (symbols.Count == 0)
{
return;
}
// Generator에 symbol을 넘겨주고 generator에서 symbols 에 있는 요소들만 분석하도록 적용
generator.Symbols = symbols;
3rdParty library setup
마지막으로 3자 라이브러리를 세팅하는 기능입니다. 해당 기능은 다음 라이브러리에서 아이디어를 얻어서 구현했습니다.
해당 라이브러리처럼 자동으로 소스를 다운받고 빌드하게 하였습니다.
Leave a Reply
Want to join the discussion?Feel free to contribute!