4. 언리얼 오브젝트 소개
게임 프로그래밍의 특수성
- 사용자 입장: 쾌적한 경험을 위해 단일 컴퓨터에서 최대 성능을 뽑아내야 한다
- 개발자 입장: 게임의 규모가 커질수록 방대하고 복잡한 기능을 안정적으로 관리해야 한다. → 점점 커지고 복잡해지는 게임을 안정적이면서 빠르고 최대 성능을 뽑아내야 한다

C++ 언어의 단점
- 오래됐다
- 초반 러닝커브가 가파르다
- 위험하다
언리얼 엔진의 선택
- 성능을 위해 기존 C++ 언어를 포기할 수 없었음
- 기존 C++ 언어를 확장해 모던 OOP 설계가 가능하게 만듦 → '언리얼 C++' 제작
- 메모리 직접 제어
- Cache 활용 극대화
- 저수준 API 직접 호출
- 복사 작업 최소화
- 유지보수성 향상
- 크래시 보호
- 자동 메모리 관리
- 실수 예방 기능
언리얼 C++ 빌드
유니티와 다르게 언리얼은 소스코드가 모두 공개돼있어 프로그램이 시작되는 지점까지 노출이 된다

Attach to Process / Detach All
소스코드 제거
VS Solution Explorer에서 추가/삭제하면 잘 반영이 안됨
직접 파일을 /Source/위치에 추가/삭제하고
루트 .uproject 우클릭 > Generate Visual Studio proejct files 클릭해야 Reload됨
UObject
- header 파일에서
#include "MyObject.generated.h"는 가장 마지막에 둬야함 - cpp 파일에서
#include "MyObject.h"는 가장 위에 둬야함 - UMyObject 소멸자는 언리얼엔진이 알아서 생성해주기 때문에(
GENERATED_BODY()매크로) 생성하면 안됨

공식문서
언리얼에는 게임 오브젝트 처리용 탄탄한 시스템이 있습니다. 언리얼에서 오브젝트의 베이스 클래스는 UObject 입니다. UObject 에서 파생되는 클래스에 UCLASS 매크로를 사용하여 태그를 해 주면 UObject 처리 시스템에서 인식하게 됩니다.
UClass 매크로는 UObject 에게 자신의 언리얼에서 기반으로 삼은 유형에 대해 설명해 주는 UClass 로의 레퍼런스를 넘겨줍니다. 각 UClass 는 Class Default Object, 줄여서 CDO 라 불리는 오브젝트를 하나 유지합니다. CDO 는 본질적으로 기본 "템플릿" 오브젝트로, 클래스 생성자에 의해 생성된 이후 변경되지 않습니다(헤더 변경 시 기본값을 바꾸는 작업이 많음, CDO가 바뀌어야 함 → 에디터를 끄고 재빌드를 해야하는 이유). 주어진 오브젝트 인스턴스에 대해 UClass 와 CDO 둘 다 구할 수는 있지만, 일반적으로는 읽기 전용으로 간주되어야 합니다. 오브젝트 인스턴스에 대한 UClass 는 GetClass() 함수를 사용하여 언제든 접근 가능합니다.

오브젝트 생성
일반 new ✗, NewObject<class>() 사용해야 함

UObject에 제공되는 함수성
- 가비지 컬렉션
- 레퍼런스 업데이트
- 리플렉션
- 시리얼라이제이션
- 디폴트 프로퍼티 변경사항 자동 업데이트
- 자동 프로퍼티 초기화
- 자동 에디터 통합
- 실행시간에 유형 정보 사용가능(RTTI)
- 네트워크 리플리케이션
헤더 파일 포맷
#include "MyObject.generated.h"- 이 줄은 파일의 마지막 #include 지시자일 것으로 기대됩니다. 이 헤더 파일이 다른 클래스에 대해 알아야 하는 경우, 파일 내 어디서도 앞서 선언 가능하며, 위의 MyObject.generated.h 에 포함시킬 수도 있습니다.
UCLASS()- UCLASS 매크로는 UMyObject 를 언리얼 엔진5 에 보이도록 만듭니다. 이 매크로는 다양한 클래스 지정자 지원을 통해 클래스에 어떤 기능을 켜고 끌 지 결정할 수 있습니다.
class MYPROJECT_API UMyObject : public UObject- MyProject 가 UMyObject 클래스를 다른 모듈에 노출시키고자 한다면, MYPROJECT_API 지정이 필수입니다(= DLLEXPORT). 게임 프로젝트에 포함시킬 모듈이나 플러그인에 가장 좋으며, 의도적으로 다수의 프로젝트에 클래스를 노출시켜 기동성있는 자체 독립적인 함수성을 제공하고자 하는 경우에도 좋습니다.
GENERATED_BODY()- GENERATED_BODY 매크로는 인수를 받지 않으나, 클래스가 엔진에 필요한 인프라 스트럭처를 지원하도록 구성합니다. 모든 UCLASS 에 필수입니다. 참고로 GENERATED_BODY 매크로는 현재 엔진 버전에서 멤버 액세스 레벨을 언어 기본값인 private 으로 설정하기 보다는 public 으로 설정합니다.
5. 언리얼 오브젝트 리플렉션 시스템 Ⅰ
문서: https://www.unrealengine.com/ko/blog/unreal-property-system-reflection
리플렉션(Reflection)은 프로그램이 실행시간에 자기 자신을 조사하는 기능입니다. 이는 엄청나게 유용한 데다 언리얼 엔진 테크놀로지의 근간을 이루는 것으로, 에디터의 디테일 패널, 시리얼라이제이션, 가비지 콜렉션, 네트워크 리플리케이션, 블루프린트/C++ 커뮤니케이션 등 다수의 시스템에 탑재된 것입니다. 그러나 C++ 는 어떠한 형태의 리플렉션도 지원하지 않아, 언리얼에는 자체적으로 C++ 클래스, 구조체, 함수, 멤버 변수, 열거형 관련 정보를 수집, 질의, 조작하는 별도의 시스템이 구축되어 있습니다. 전형적으로 이러한 리플렉션은 '프로퍼티 시스템'이라고 부르는데, '리플렉션'은 그래픽 용어이기도 하기 때문입니다.
리플리케이션 시스템은 옵션입니다. 리플렉션 시스템에 보이도록 했으면 하는 유형이나 프로퍼티에 주석을 달아주면, Unreal Header Tool (UHT)가 그 프로젝트를 컴파일할 때 해당 정보를 수집합니다.
헤더에 리플렉션이 있는 유형으로 마킹을 하려면, 파일 상단에 특수한 include 를 추가해 줘야 합니다. 그러면 리플렉션이 있는 유형은 이 파일을 고려해야 함을, 시스템 구현에도 필요함을 UHT 에 알려줍니다 (자세한 정보는 '커튼 안쪽 살펴보기' 부분을 참고하세요).
#include "FileName.generated.h"#include "StrategyTypes.h"#include "StrategyChar.generated.h"UCLASS(Abstract)class AStrategyChar : public ACharacter, public IStrategyTeamInterface{ GENERATED_UCLASS_BODY() /** How many resources this pawn is worth when it dies. */ UPROPERTY(EditAnywhere, Category=Pawn) int32 ResourcesToGather; /** set attachment for weapon slot */ UFUNCTION(BlueprintCallable, Category=Attachment) void SetWeaponAttachment(class UStrategyAttachment* Weapon); UFUNCTION(BlueprintCallable, Category=Attachment) bool IsWeaponAttached();protected: /** melee anim */ UPROPERTY(EditDefaultsOnly, Category=Pawn) UAnimMontage* MeleeAnim; /** Armor attachment slot */ UPROPERTY() UStrategyAttachment* ArmorSlot; /** team number */ uint8 MyTeamNum; [이하 코드 생략]};
UPROPERTY가 붙으면 변수 nullptr 초기화 안해줘도 자동으로 해줌
한계
UHT 는 실제 C++ 파서가 아닙니다. 해당 언어의 상당 부분을 이해하고 실제로 할 수 있는 만큼 텍스트를 건너뛰기는 하지만, 리플렉션된 유형, 함수, 프로퍼티에만 주의를 기울입니다. 그러나 몇몇은 여전히 헛갈릴 수 있기에, 기존 헤더에 리플렉션된 유형을 추가할 때는 뭔가 단어를 바꾸거나 #if CPP / #endif 짝으로 둘러싸 줘야 합니다. 주석을 단 프로퍼티나 함수에는 (WITH_EDITOR 와 WITH_EDITORONLY_DATA 를 제외하고) #if/#ifdef 사용을 피해야 하는데, generated 코드가 그에 대해 레퍼런싱하여 그 정의가 참이지 않은 경우 환경설정에서 컴파일 에러가 나기 때문입니다.
대부분의 흔한 유형은 예상대로 작동하나, 프로퍼티 시스템은 가능한 C++ 유형 전부를 나타내지 못합니다 (특히 TArray 및 TSubclassOf 같은 몇몇 템플릿 유형만 지원되며, 그 템플릿 파라미터는 중첩 유형이 될 수 없습니다). UHT 는 실행시간에 나타낼 수 없는 유형을 붙이는 경우 오류 메시지가 뜹니다.
커튼 안쪽 살펴보기
프로퍼티 시스템을 그냥 사용만 하려는 경우 이 부분은 건너뛰어도 좋습니다만, 그 작동 원리를 알아두면 리플렉션된 유형이 포함되는 헤더의 몇 가지 한계와 몇 가지 결정사항에 도움이 됩니다.
Unreal Build Tool (UBT) 와 Unreal Header Tool (UHT) 가 함께하여 실행시간 리플렉션을 강화시키는 데 필요한 데이터를 생성합니다. UBT는 그 역할을 위해 헤더를 스캔한 다음 리플렉션된 유형이 최소 하나 있는 헤더가 들어있는 모듈을 기억합니다. 그 헤더 중 어떤 것이든 지난 번 컴파일 이후 변경되었다면, UHT를 실행하여 리플렉션 데이터를 수집하고 업데이트합니다. UHT는 헤더를 파싱하고, 리플렉션 데이터 세트를 빌드한 다음, (모듈별.generated.inl에 기여하는) 리플렉션 데이터가 들어있는 C++ 코드를 생성할 뿐만 아니라, (헤더별 .generated.h인) 다양한 헬퍼 및 thunk 함수도 생성합니다.
리플렉션 데이터를 C++ generated 코드로 저장하는 것의 한 가지 주요 장점은, 바이너리와의 동기화가 보장된다는 점입니다. 오래되거나 버전이 맞지 않는 리플렉션 데이터를 로드할 일은 없는데, 나머지 엔진 코드와 함께 컴파일되기 때문입니다. 그리고 특정 플랫폼/컴파일러/최적화 콤보의 패킹 작동방식을 역엔지니어링하려 하기 보다는, C++ 표현식을 사용해서 시작시 멤버 오프셋/등등을 계산하기 때문입니다. UHT 역시도 generated 헤더를 소모하지 않는 독립형 프로그램으로 만들어졌기에, UE3 의 스크립트 컴파일러에서 흔히 발생했던 닭이냐 계란이냐 문제가 생기지 않습니다.
generated 함수에는 StaticClass() / StaticStruct() 같은 것이 포함되어 있어, 유형에 대한 리플렉션 데이터를 구하는 것이 쉬워질 뿐만 아니라, 블루프린트나 네트워크 리플레키에션에서 C++ 함수를 호출하는 데 사용되는 thunk 를 구하는 것도 쉬워집니다. 이는 클래스나 구조체의 일부로 선언되어야 하며, GENERATED_UCLASS_BODY() 또는 GENERATED_USTRUCT_BODY() 매크로가 리플렉션된 유형에 포함되어야 하는지에 대한 이유가 됩니다. 이 매크로를 정의하는 #include 'TypeName.generated.h' 는 물론입니다.
6. 언리얼 오브젝트 리플렉션 시스템 Ⅱ
변수 초기화
// .hUCLASS()class A_API UPerson : public UObject{... UPROPERTY() FString Name = TEXT("홍길동"); // 1}// .cpp#include "Person.h"UPerson::UPerson() : Name(TEXT("홍길동")) // 2{ Name = TEXT("홍길동"); // 3}1번과 2번은 타이밍이 동일하다. 2번은 언리얼에서 지원하지 않는다. 3번은 1번 다음에 실행된다. 1~3번 모두 프로젝트 시작할 때 미리 초기화된다.
그래픽스 프로그래밍
게임에서의 머티리얼 - 정점마다 머티리얼이 늘어날수록 게임 성능이 낮아짐 왜? 드로우콜이 많아짐 → 그래픽카드 특성상 연산량이 늘어남 → 느려짐 드로우콜 줄이는 방법 중에:
- 고정돼있는 메시가 있으면
- 텍스쳐/셰이더 등이 같은 것들끼리 모아서 하나의 큰 정점 버퍼와 하나의 큰 텍스쳐를 만들고 한번에 그리게 처리함 CPU가 병목일 수 있고 GPU가 병목일 수 있음 CPU = 1회 처리량이 매우 적음 = 한 번의 처리량이 많을 떄 → 스레드로 나눠서 병렬 처리(최후의 방법) GPU = 처리횟수가 많을 떄 최적화 = 양쪽 하드웨어의 사용률을 최대한 끌어올리는 것 드로우콜 = CPU가 만들어주는 명령
컨셉아트 → 기획 → 모델링 → 텍스쳐(2D아트) + UV 매핑(모델러) → 포토샵/일러에서 색칠 → 머티리얼