개발일지공부아이디어
    • 4월
    • 3월
    • 2월
    • 1월
  • 09

    26. 4. 9.

  • 08

    26. 4. 8.

로딩 중...

2026. 4. 8.

1. 언리얼 C++ 빌드

언리얼은 게임 소스코드를 통째로 dll로 만들고 exe에 넣어 만든다

UHT 언리얼 헤더 툴: 전처리

  • 자동완성
  • 매크로 지우기
  • 컴파일에 필요한 것만 남기기 UBT 언리얼 빌드 툴:
  • C# dll 이후 컴파일 - 어셈블리 - 링킹

GameInstance

  • GameInstance: high-level manager object for an instance of the running game.
  • Spawned at game creation and not destroyed until game instance is shut down.
  • Running as a standalone game, there will be one of these.
  • Running in PIE (play-in-editor) will generate one of these per PIE instance.
UCLASS()class UEPART1_API UMyGameInstance : public UGameInstance{	GENERATED_BODY()	};

위 코드는 펼치면 아래와 같음

UCLASS()class __declspec(dllexport) UMyGameInstance : public UGameInstance{    __pragma (warning(push)) __pragma (warning(disable: 4995)) __pragma (warning(disable: 4996)) // 언리얼도 피해갈 수 없는 디버그 에러(?)    public: private: static void StaticRegisterNativesUMyGameInstance(); friend struct Z_Construct_UClass_UMyGameInstance_Statics; static UClass* GetPrivateStaticClass(); friend __declspec(dllexport) UClass* Z_Construct_UClass_UMyGameInstance_NoRegister(); public: private: UMyGameInstance& operator=(UMyGameInstance&&); UMyGameInstance& operator=(const UMyGameInstance&); public: static constexpr EClassFlags StaticClassFlags = EClassFlags((0 | CLASS_Transient | CLASS_Intrinsic)); typedef UGameInstance Super; typedef UMyGameInstance ThisClass; inline static UClass* StaticClass() {        return Z_Construct_UClass_UMyGameInstance_NoRegister();    } inline static const TCHAR* StaticPackage() {        return L"/Script/UEPart1";    } inline static EClassCastFlags StaticClassCastFlags() {        return CASTCLASS_None;    } inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) {        return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags);    } inline void* operator new(const size_t InSize, EInternal* InMem) {        return (void*)InMem;    } inline void operator delete(void* InMem) {        ::operator delete(InMem);    } friend FArchive& operator<<(FArchive& Ar, UMyGameInstance*& Res) {        return Ar << (UObject*&)Res;    } friend void operator<<(FStructuredArchive::FSlot InSlot, UMyGameInstance*& Res) {        InSlot << (UObject*&)Res;    }  UMyGameInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); UMyGameInstance(UMyGameInstance&&) = delete; UMyGameInstance(const UMyGameInstance&) = delete;  UMyGameInstance(FVTableHelper& Helper);; static UObject* __VTableCtorCaller(FVTableHelper& Helper) {        return new (EC_InternalUseOnlyConstructor, (UObject*)GetTransientPackage(), NAME_None, RF_NeedLoad | RF_ClassDefaultObject | RF_TagGarbageTemp) UMyGameInstance(Helper);    }; static void __DefaultConstructor(const FObjectInitializer& X) {        new((EInternal*)X.GetObj())UMyGameInstance(X);    }  virtual ~UMyGameInstance(); private: __pragma (warning(pop));	};

UObject

  • 언리얼의 최상위 객체

L("") 대신 TEXT() 쓰기

Ctrl Alt F11는 자다깨도 누를 정도로 외우기(언리얼 라이브코딩 컴파일) 헤더 변경하면 에디터 끄고 비주얼 스튜디오에서 컴파일해 에디터를 켜주는게 좋음 소스 파일에만 변경이 발생하면 Ctrl Alt F11

2. 언리얼 C++ 코딩 표준규칙

저작권 고지

공개 배포용으로 에픽게임즈에서 제공한 모든 소스 파일( .h , .cpp , .xaml )은 파일 첫 번째 줄에 저작권 고지를 포함해야 합니다. 저작권 고지의 포맷은 다음과 정확히 일치해야 합니다.

// Copyright Epic Games, Inc. All Rights Reserved.

이 줄이 누락되거나 올바른 양식으로 작성되지 않을 경우 CIS에서 오류를 생성하고 실패합니다.

부울을 반환하는 모든 함수는 IsVisible() 또는 ShouldClearBuffer() 등의 true/false 질문을 해야 합니다.

다음과 같은 경우 함수 파라미터 이름에 접두사 'Out'을 추가할 것이 좋습니다.

  • 함수 파라미터가 레퍼런스로 전달되는 경우
  • 함수를 그 값에 쓸 것으로 예상되는 경우

이렇게 하면 이 실행인자에 전달되는 값이 함수로 대체된다는 것을 확실히 알 수 있습니다.

In 또는 Out 파라미터도 부울인 경우 bOutResult 와 같이 In/Out 접두사 앞에 'b'를 붙입니다.

값을 반환하는 함수는 반환 값을 설명해야 합니다. 함수가 어떤 값을 반환하는지 이름을 보고 정확히 알 수 있어야 합니다. 특히 부울 함수의 경우 이는 매우 중요합니다. 예시로 다음 두 가지 방법을 확인해 보세요.

// true일 경우 무슨 의미일까요?   bool CheckTea(FTea Tea);       // 이름을 통해 true일 경우 차가 신선하다는 것을 명확히 알 수 있습니다.   bool IsTeaFresh(FTea Tea);   float TeaWeight;   int32 TeaCount;   bool bDoesTeaStink;   FName TeaName;   FString TeaFriendlyName;   UClass* TeaClass;   USoundCue* TeaSound;   UTexture* TeaTexture;

포터블 C++ 코드

int 및 부호 없는 int 타입은 플랫폼에 따라 크기가 다를 수 있습니다. 최소 너비는 32비트로 보장되며, 정수 너비가 중요치 않은 경우라면 코드에서 사용해도 괜찮습니다. 명시적으로 크기가 정해진 타입은 여전히 시리얼라이즈 또는 리플리케이트된 포맷으로 사용합니다.

표준 라이브러리 사용

과거에는 다음과 같은 이유로 UE에서 C 및 C++ 표준 라이브러리를 직접 사용하는 것을 지양했습니다.

  • 느린 구현을 자체 라이브러리로 대체하여 메모리 할당에 대한 제어력 강화
  • 널리 이용 가능해지기 전에 다음과 같은 새 함수 기능 추가:
    • 바람직하지만 비표준인 동작 변경 수행
    • 코드베이스 전체에서 문법 일관성 유지
    • UE 언어와 호환되지 않는 컨스트럭트 방지

하지만 표준 라이브러리의 완성도가 높아지면서, 추상화 레이어로 래핑하거나 직접 재구현하지 않아도 되는 함수 기능을 포함하게 되었습니다.

자체 라이브러리 대신 표준 라이브러리 기능을 사용할지 선택하는 경우, 더 나은 결과를 제공하는 옵션을 사용하는 것이 좋습니다. 일관성 또한 중요하게 고려해야 한다는 점을 명심하세요. 레거시 UE 구현이 더 이상 도움이 되지 않을 경우, 에픽은 지원을 중단하고 모든 사용을 표준 라이브러리로 이주하기로 결정할 수도 있습니다.

Const 정확도

반환 타입에는 const를 사용하지 않습니다. 복잡한 타입에 대한 이동 시맨틱이 제한되며, 기본 타입에는 컴파일 경고가 발생하게 됩니다. 이 규칙은 반환 타입 자체에만 적용되며, 포인터의 타깃 타입 또는 반환되는 레퍼런스에는 적용되지 않습니다.

예시:

// 나쁜 예 - const 배열 반환        const TArray<FString> GetSomeArray();	// 좋은 예 - const 배열로의 레퍼런스 반환    	const TArray<FString>& GetSomeArray();        	// 좋은 예- const 배열로의 포인터 반환    	const TArray<FString>* GetSomeArray();        	// 나쁜 예 - const 배열로의 const 포인터 반환    	const TArray<FString>* const GetSomeArray();

최신 C++ 언어 문법

언리얼 엔진은 기본적으로 C++20 언어 버전으로 컴파일하며, 빌드 시 요구하는 최소 언어 버전은 C++20입니다. 언리얼 엔진은 최신 컴파일러 전반에서 잘 지원되는 다수의 최신 언어 기능을 사용합니다. 경우에 따라 프리프로세서 조건문에 이러한 기능의 사용을 래핑합니다. 그러나 가끔은 포터빌리티나 다른 이유로 인해 특정 언어 기능 전체를 사용하지 않기로 결정하는 경우도 있습니다.

Override 및 Final

override 및 final 키워드는 사용할 수 있을 뿐만 아니라, 사용을 강력히 권합니다. 빠진 부분이 다수 있을 수 있으나, 서서히 수정될 예정입니다.

Nullptr

nullptr 은 모든 경우 C 스타일 NULL 매크로 대신 사용해야 합니다.

한 가지 예외로는 C++/CX 빌드(예: Xbox One)의 nullptr 사용이 있습니다. 이 경우 nullptr 사용은 사실 관리된 null 레퍼런스 타입입니다. 타입이나 일부 템플릿 인스턴스화 컨텍스트를 제외하면 네이티브 C++의 nullptr 과 거의 호환되므로, 호환성을 위해서는 좀 더 일반적인 decltype(nullptr) 대신 TYPE_OF_NULLPTR 매크로를 사용해야 합니다.

Auto

아래 몇 가지 예외를 제외하면 C++ 코드에서 auto 를 사용해서는 안 됩니다.

auto 를 사용 가능한 경우는 다음과 같습니다.

  • 변수에 람다를 바인딩해야 하는 경우. 람다 타입은 코드로 표현할 수 없기 때문입니다.
  • 이터레이터 변수의 경우. 단, 이터레이터 타입이 매우 장황하여 가독성에 악영향을 미치는 경우에 한합니다.
  • 템플릿 코드에서 표현식의 타입을 쉽게 식별할 수 없는 경우. 고급 사용 사례입니다.

범위 기반 For

TMap<FString, int32> MyMap;        // 기존 스타일    for (auto It = MyMap.CreateIterator(); It; ++It)    {        UE_LOG(LogCategory, Log, TEXT("Key: %s, Value: %d"), It.Key(), *It.Value());    }        // 새 스타일    for (TPair<FString, int32>& Kvp : MyMap)    {        UE_LOG(LogCategory, Log, TEXT("Key: %s, Value: %d"), *Kvp.Key, Kvp.Value);    }

람다 및 익명 함수

캡처 및 반환 타입

자동 캡처보다는 명시적(explicit) 캡처를 사용해야 합니다([&] 및 [=]). 대규모 람다와 지연(deferred) 실행에 사용되는 경우 가독성, 유지보수성, 퍼포먼스 측면에서 특히 중요합니다.

강 - 타입 Enum

열거형(Enumerated, Enum) 클래스는 기존 네임스페이스 열거형인 일반 열거형 및 UENUM 을 대체합니다. 예를 들어 다음과 같이 할 수 있습니다.

// 기존 열거형UENUM()namespace EThing{	enum Type	{		Thing1,		Thing2	};}// 새 열거형UENUM()enum class EThing : uint8{	Thing1,	Thing2}

이동 시맨틱

TArray , TMap , TSet , FString 과 같은 모든 주요 컨테이너 타입에는 move 컨스트럭터와 move 할당 연산자가 있습니다. 이러한 타입을 값으로 전달 또는 반환할 때 종종 자동으로 사용되지만, std::move 의 UE 해당 버전인 MoveTemp 를 통해 명시적으로 호출할 수도 있습니다.

값으로 컨테이너나 스트링을 반환하는 것은 보통 임시로 복사하는 비용이 없어 표현성에 유용하게 작용할 수 있습니다. 값 전달 관련 규칙 및 MoveTemp 사용법은 아직도 확립 중이지만, 최적화된 코드베이스 영역 일부에서는 이미 찾아볼 수 있습니다.


3. 언리얼 C++ 기본 타입과 문자열

int 타입

C++최신 규약에서 int는 최소 32비트를 보장하도록 규정되어 있음.

  • 특정 플랫폼에서는 64bit로 해석될 수 있음.

  • 따라서 데이터를 저장할 때 int타입의 크기를 확신할 수 없음
    게임 제작의 특징

  • 데이터 정보가 명확해야 한다.

  • 단일 컴퓨터에서 최대 퍼포먼스를 뽑아내야 한다.

  • 네트웍 상에서 데이터 통신이 효율적이고 안정적이어야 한다. → 데이터 타입의 애매 모호함은 게임 개발시 문제를 일으킬 수 있음!

  • 후발 언어 C#의 경우 int타입은 명확히 4바이트 32bit로 고정돼있음

  • 언리얼은 int 사용안하고 고정된 int32를 사용함

bool 타입

  • 데이터 전송을 고려한 참/거짓 데이터의 지정
  • bool은 크기가 명확하지 않음
  • 헤더에는 가급적 bool대신 uint8타입을 사용하되 BitField 오퍼레이터를 사용
    • uint8 bNetTemporarary:1; ← 1바이트 중 1비트만 사용하겠다는 오퍼레이터
    • 네트워크나 데이터로 명시적으로 저장되어야할 땐 1바이트를 사용하길 권장하고 있음
    • 그렇지 않은 경우엔 1비트 bool타입을 써도 됨.
  • 이것 때문에 일반 uint8을 사용한 불값과 bool을 사용한 불값의 구분을 위해 b접두사를 사용
  • Cpp로직에서는 자유롭게 bool을 사용

캐릭터 인코딩

언리얼은 왜 문자열을 따로 지정하는가?

  • Single byte(ANSI, ASCII)
  • Mutlibyte(EUC_KR, CP949) - 윈도우는 CP949
  • Unicode(UTF-8, UTF-16) - 언리얼은 UTF-16 사용

참고: UE 내부 스트링 표현

언리얼 엔진의 모든 스트링은 FStrings 혹은 TCHAR 정렬 상태로 UTF-16 포맷 메모리에 저장됩니다. 대부분의 코드에서 2 바이트가 하나의 코드포인트라 가정하므로, 언리얼의 내부 인코딩이 UCS-2 로 보다 정확히 설명될 수 있도록 Basic Multilingual Plane(BMP) 만 지원됩니다. 스트링은 현 플랫폼에 적합한 엔디안에 저장됩니다. ... C++ 소스 코드를 동아시아어 코드 페이지 CP949 (한국어), CP932 (일본어), CP936 (중국어 간체), CP950 (정통 중국어) Windows에서 컴파일하는 경우, 그 소스 코드에 UTF-8 로 저장된 동아시아어 캐릭터가 있는지 주의를 기울이시기 바랍니다.

FString

TEXT 매크로는 TCHAR 배열로 지정됨

  • FString::Printf
  • FString::SanitizeFloat
  • FString::FromInt

FCString

C 런타임 레벨에서 문자열을 처리하는 클래스

auto LogCharArray = TEXT("Hello Unreal"); // const wchar_t* LogCharArrayTCHAR LogCharArray[] = TEXT("Hello Unreal"); // 위와 동일

포인터가 아니라 연산자 오버로딩. 반환값은 포인터 맞음

UE_LOG()

  1. Log 카테고리 지정
  2. Log 수준: Log, Warning, Error
  3. 포맷 - 출력할 값의 타입
  4. 가변 인자 - 포맷에 지정한 타입에 알맞은 값 전달
UE_LOG(LogTemp, Log, TEXT("%s"), LogCharArray);

Hot Reload

언리얼에도 HMR이 있다 원리는 에디터 실행 중에 실행을 멈춰두고 기존 dll 링크를 끊고 새 dll 링크를 연결한다

...Successfully linked patch (0.000s)Patch creation for module C:\Workspace\Wanted4th\UEPart1\Binaries\Win64\UnrealEditor-UEPart1.dll successful (0.000s)

그래픽스 특강

Mesh

  • 뼈대 좌표계와 모델링 좌표계가 분리돼있음
  • 뼈대 좌표계 행렬을 가지고 모델링 정점에 행렬을 곱해줘야 함
  • obj 포맷은 애니메이션이나 뼈대 정보를 넣지 못함 캐릭터 초기 스텟, 재화같은거 암호화
  • 데이터와 퍼블릭 키 암호화해서 dll 뽑기
  • 그 dll을 다시 암호화해서 dll에 담기 SubMesh -< StaticMesh Material 하나에 Shader 하나
왼쪽 화살표오른쪽 화살표