오늘은 오랜만에 다른 주제의 글이다.
프론트엔드에 대한 글을 네 번 연속 쓰니 조금 질리기도 하고, 어느정도 궤도에 오른 느낌이 없지 않아 있어,
최근에 공부하고 싶었던 언리얼 엔진의 리플렉션 시스템에 대해 공부를 시작했다.
아마 프론트엔드 관련 글은 강의 수강 중에 벽에 가로막힌 느낌이 들거나 혹은,
깊게 공부해야할 내용이 나타나면 또 다시 작성할 예정이다. (당연히 계속 수강할 예정이다.)
1. 언리얼 에디터
우리가 흔히 이야기하는 언리얼 엔진은 주로 에디터를 이야기한다.
실제로 언리얼 엔진을 실행하면 선택한 버전의 언리얼 에디터가 실행된다.
언리얼 에디터라는 거대한 C++ 프로그램이 먼저 실행되고,
우리가 만든 게임 프로젝트 코드를 플러그인처럼 로드하는 순서로 진행된다.
하지만 순수 C++ 환경에서, 이미 컴파일되고 난 이후인 런타임 환경에서는 클래스의 맴버들을 확인 할 수 없다.
런타임일 때는 코드들을 전부 기계어로 번역한 뒤 프로그램을 실행할 준비를 마친거지,
함수/변수들을 관리하는 단계는 아니라서, 그 코드들의 구조를 이해할 수 없도록 되어있다.


하지만 언리얼 에디터는 런타임 상황에서 코드를 읽고, 블루프린트나 디테일 패널에서 클래스의 맴버에 접근해야만 한다.
이때 필요한 것이 바로 리플렉션 시스템이다.
리플렉션은 비단 언리얼 엔진에서만 쓰는 단어가 아니라, 프로그램이 실행 중에 자기 자신을 검사하는 것을 말한다.
표준 C++ 환경에서는 리플렉션 시스템이 따로 존재하지 않아서, 언리얼 엔진 자체의 기능으로 지원한다.
언리얼에는 UHT(Unreal Header Tool)이라는 자체 도구가 C++ 코드를 컴파일 하기 전에 한번 검사를 하게 되어있다.
런타임 중에서도 코드를 검사할 수 있도록, 미리 표시해둔 매크로들을 전부 파악하고,
일종의 메타데이터(데이터 자체가 아닌 데이터의 구조, 속성을 담은 데이터)를 저장한 파일인 generated.h 파일을 만들게 된다.
(유니티도 보니까 컴파일하면 .meta라는 파일이 만들어지는데 아마 같은 역할을 하지 않을까?)
예를 들어 Weapon.h 과 그 소스코드를 빌드해보면, 언리얼은 항상 Weapon.generated.h 파일을 자동 생성하게 된다.
이 안에는 런타임에서 읽어야 할 클래스들의 정보를 담고있는 것이다.

백문불여일견.
내 프로젝트의 Weapon 클래스의 예시와 함께 에디터가 어떻게 내가 작성한 클래스들을
런타임에서도 불러올 수 있는지 살펴보자.
UCLASS() 라는 매크로가 등장했다.
이제부터 이 클래스는 UHT의 영역 안에 들어온 것이다.
즉, 컴파일하기도 전에 에디터를 위한 메타데이터를 만들게 될것이다.
UPROPERTY()라는 매크로를 또 살펴보자.
Weapon이라는 클래스에 TrajectorySphere(총구가 가르키는 방향의 붉은 구)와
WeaponType이라는 맴버변수가 있다.
이 맴버변수 또한 UPROPERTY()라는 매크로로 인해 UHT의 영역으로 들어오게 된 것이다.

여기서 WeaponType은
EditAnywhere(블루프린트에서 보이고 수정 또한 가능) 이라는 속성을 가지고,
Weapon Properties라는 카테고리라는 UI에 생성된다.
(TrajectorySphere는 포인터 변수이기에 디테일 패널에 따로 나타나지 않는다.)
이렇게 리플렉션 시스템에 의해 우리가 작성한 코드들이 에디터 UI에 나타나게 될 수 있는 것이다.
리플렉션 시스템이 관여하는 중요한 것이 또 있는데, 바로 가비지 컬렉션(GC)이다.
2. 가비지 컬렉션(GC)
메모리 관리 방법 중 하나로, 프로그래머가 동적으로 할당한 메모리 영역 중 더 이상 쓰이지 않는 영역을 자동으로 찾아내어 해제하는 기능이다. 존 매카시가 1959년에 LISP의 메모리 관리를 위해 처음 만들었다고 알려져 있다.
(출처: 나무위키)
언리얼 엔진에서는 모든 객체의 부모라고 할 수 있는 UObject가 있다.
그래서 존재하는 모든 객체는 결국 UObject라고도 볼 수 있는데,
언리얼의 GC는 사용 되지않는, 즉 쓸데없는 UObject들을 메모리에서 해제시켜버리는 것을 목표로 한다.
이때 GC가 UObject를 인식할 때 리플렉션 시스템을 이용한다.
위와 같이 UPROPERTY() 매크로(필수임. 없으면 GC가 인식 못함)가 붙은 객체가
GC에게 UObject들의 포인터들을 알려주게 되면서 UObject 레퍼런스 트리를 쭉 검색을 하게된다.

트리의 시작은 Root Set이라고 하는데, 게임에서 사라지지 않고 유지되어야 하는 객체들을 기준으로 잡는다.


이때 객체가 한 번이라도 참조된다면 Marking을 하고, 전부 순회했을 때 Mark가 없는 객체들은 Sweep한다 해서
해당 방식을 Mark & Sweep 기법이라고 한다.
이를 공부하면서 궁금했던 것이 생겼는데,
"캐릭터가 총을 들고있다가, 그 총을 버리면서 총의 맴버변수인 Owner가 nullptr가 되면,
GC의 Sweep 대상이 되는 거 아닌가?"
만약 이것이 사실이면, GC는 잘못된 시스템이 되어버린다.
어디까지나 메모리 관리 시스템인데, 게임 로직에 관여하는 대참사가 나기 때문이다.
(총을 실수로 버렸다가 눈앞에서 사라지는 광경을 목격하게 될 것이다.)
하지만 이런 일은 일어나지 않는다.
총이 레벨에 존재한 순간, 총이라는 객체는 UWorld에 의해 스폰되었기에, 여전히 UWorld에 의해 참조되고있다.
(UWorld는 레벨 그 자체라고 보면 되기에, Root Set에 적합하다.)
단지 캐릭터와의 참조가 끊겼다고 Sweep 대상이되지 않는 것이다.
그렇기에 Root Set 설정이 굉장히 중요해 보인다.
해당 GC는 보통 60초에 한번씩 실행되는데, 그 간격 동안 살아남지 못할 가능성이 있는 객체를 Root Set으로 설정하면
게임 로직에 영향이 갈 수 있다는 것이다.
마치며
리플렉션 시스템은 위 두가지 말고도 여러 역할을 담당한다고 한다.
하지만 이번엔 메모리 관리 방법인 GC에 대해 자세히 알고 싶었던 마음이 커, 여기까지 공부하기로 한다.

사실 게임을 만들면서 메모리를 관리해본 경험이 없는데,
과거에는 메모리나 게임 용량 관리없이 개발은 상상도 못했던 일이라고 한다.
다행히 현재는 무거운 게임 하나 정도는 너끈히 버틸 수 있는 메모리의 상용화와
오늘 배운 GC와 같은 자동 메모리 관리 시스템 덕분에 개발 진입장벽이 훨씬 낮아졌다고 볼 수 있다.
이들을 만들어낸 똑똑한 사람들에게 감사를 전하며 글을 마친다.
도움 받은 글
https://hyo-ue4study.tistory.com/272
https://ciel45.tistory.com/126
https://ciel45.tistory.com/127
'UE5 > Studylog' 카테고리의 다른 글
| [UE5] Studylog 4 - Frontend UI/Menu using CommonUI [ 3 ] (0) | 2025.10.26 |
|---|---|
| [UE5] Studylog 3 - Frontend UI/Menu using CommonUI [ 2 ] (0) | 2025.10.11 |
| [UE5] Studylog 2 - NDC 2025 "저 메시의 목을 쳐라!" 리뷰 (0) | 2025.09.27 |
| [UE5] Studylog 1 - Frontend UI/Menu using CommonUI [ 1 ] (2) | 2025.09.24 |