언리얼 게임 프로젝트 실행 구조
- 코드 파이프라인: 소스 코드(.cpp, .h) 작성 → UnrealHeaderTool(UHT)이 실행되어 (.generated.h) 같은 리플렉션 코드 생성 → 컴파일러가 (.obj) 파일로 변환 → 링킹(여러 .obj 파일과 엔진과 외부의 라이브러리)하여, 게임 모듈(dll(dynamic link library, 실행 중 필요할 때 로드할 수 있는 코드 묶음)과 실행 파일(.exe)) 생성
- 에셋 파이프라인: 원본 에셋 import → 언리얼 포맷(.uasset)으로 변환해 content browser에서 관리 → 쿠킹(플랫폼에 맞춘다.) → 패킹(모든 언리얼 포맷 파일을 묶어 하나의 .pak으로 저장)
→ 최종적으로, 코드는 dll/exe, 에셋은 pak이 된다. 최종 게임 프로그램은 “dll가 로직을, pak이 데이터를 제공하며 게임이 돌아간다.”
+) .exe는 게임 실행 시작점, 즉, 런처 역할에 가깝다. 로직은 dll을 중심으로 존재한다고 볼 수 있다.
+) 작성한 C++ 코드에는 UPROPERTY와 같은 언리얼용 매크로가 섞여있는데, 일반 C++ 컴파일러는 이를 이해하지 못한다. 따라서, 빌드과정에서 UHT가 리플렉션 코드를 생성해주는데, 이를 통해서 컴파일러가 언리얼용 매크로도 이해할 수 있게 된다. 그렇게 만들어진 오브젝트 파일이 언리얼 실행 환경에서 동작할 수 있는 모듈로서 존재하게 된다.
+) PIE(Play In Editor): 패킹 없이, 에디터 내부에서 “에디터 전용 실행파일(UE5Editor.exe) + dll + uasset”을 합쳐 임시 실행해보는 것, 즉, 개발용, 에디터 안에서만 실행한다.
합쳐서 정리해보면?
VS와 같은 소스코드 에디터에서 C++ 코드 작성, UHT를 통해 리플렉션 코드 생성 → 2) 컴파일러가 코드를 .obj 파일로 변환, 링커가 엔진/외부 라이브러리 링킹해 dll/exe 생성 → 3) 에디터가 dll 로드, 그것과 content browser의 에셋(import 되며 uasset으로 관리 되고 있는)을 통해 PIE 구현 → 4) 에셋이 쿠킹과 패킹을 거쳐 pak 파일로 묶이고, .exe와 함께 배포되어 게임이 독립적으로 실행
+) 빌드란? 사람이 작성한 소스 코드와 에셋을 컴퓨터가 실행할 수 있는 결과물로 전환하는 과정이다. 즉, 실행 가능한 프로그램 패키지를 만든다고 보면 된다. 코드는 컴파일과 링킹을 거쳐 실행파일과 dll이 되고, 에셋은 쿠킹과 패킹을 거쳐 pak 파일이 된다. 최종적으로는 이 두 가지가 합쳐져 실제 게임이 실행된다.
+) 파생되는 생각들:
- 컴파일 vs 빌드? 컴파일은 코드를 .obj 파일로 변환하는 것, 빌드는 그보다 넓게 컴파일 + 링킹 + 에셋 쿠킹/패킹까지를 포함한다.
- 언리얼의 개발 중에서는 PIE로 uasset을 직접 실행하고, 최종 배포 시에는 패키징 빌드로 exe/dll + pak을 만들어 게임이 돌아가게 된다.
UPROPERTY
- UPROPERTY에 달린 지시어(specifier)로 “어디에서, 어떻게 보이고/수정 가능한지”를 정한다.
- 왜 UPROPERTY가 필요한가? 엔진이 변수를 에디터에서 “노출/수정, 저장/로드… 등”의 작업을 하려면 반드시 그 값이 리플렉션 코드(매크로에 표시된 부분 만 등록된다.)에 등록되어야 하기 때문이다.
+) 단순한 int32 MyInt; 같은 순수 C++ 코드는 엔진 에디터 안에서 변경하기는 어렵다.
라이브 코딩과 정식 빌드
- Live Coding = 메모리 핫스왑(temporary) → 에디터 켜져 있는 동안만 반영되고, 종료하면 사라진다.
- 정식 C++ 빌드(Development Editor) = 디스크 갱신(영구) → VS/UBT(언리얼 빌드 툴)로 빌드 후 에디터 재실행 시 새 DLL가 로드된다.
- 최종적으로 새 UPROPERTY 추가/삭제/타입 변경은 반드시 정식 빌드 + 재실행이 필요하다.
- Live Coding만을 쓰면 재실행 시 옛 DLL로 돌아가 값이 초기화되거나 사라진다.
→ 라이브코딩은 에디터에만 적용된 것으로, 그것을 제대로 적용하기 위해서는 에디터를 끄고, VS Code에서 기존 빌드 작업을 다시 실행한 뒤, 에디터를 켜서 디스크를 갱신한다.
→ 에디터가 켜져있으면 DLL(실행 중인 프로그램에 붙어 돌아가는 코드) lock 으로 인해 변경이 안된다. 그래서 위 작업을 진행해야 한다.
한 번 더 구체적으로 나눠보자면 아래 순서를 거쳐서, Live Coding으로 적용했던 변경 사항이 최종 DLL에 들어가게 되어 이제 에디터를 껐다 켜도 그대로 반영되도록 할 수 있다.
- 에디터 + Live Coding
- VSCode에서 코드 수정 → 저장
- 에디터에서 Live Coding 버튼 → 에디터 메모리에만 반영
- 여기서 테스트 가능 (디스크 DLL은 이전 버전)
- 에디터 종료
- 메모리에 있던 Live Coding 결과는 다 날아감 → 결국 디스크 DLL 기준으로 돌아갈 준비 상태
- VSCode 빌드 (Ctrl+Shift+B)
- 소스 전체를 다시 컴파일 + 링킹 → 디스크 DLL이 최신 코드로 교체됨
- 에디터 재실행
- 에디터는 실행 시 디스크 DLL을 다시 로드 → Live Coding에서 실험하던 것이 영구 반영된 상태
기본적인 코드 실행 흐름과 코드 작성
- BeginPlay: 액터가 월드에 스폰된 직후 한 번만 실행되는 초기화 구간이다.
- Tick: 매 프레이마다 게임이 업데이트되도록 작업해주는 함수
- 코드 구조 예시
[헤더(.h)에 UPROPERTY로 변수 선언 → cpp에서, h에서 선언했던 변수를 통해 계산/위치 회전 적용 로직 추가]
→ 위치 변경: SetActorLocation(FVector), 회전 변경: SetActorRotation(FRotator)
→ 블루프린트는 C++ 위에 얹히는 계층이므로, 코드 변경 시 BP도 리컴파일이 필요할 수 있다.
로컬 변수
void AMovingPlatform::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FVector LocalVector = MyVector; //지역 변수!!
LocalVector.Z = LocalVector.Z + 100;
MyVector.Y = MyVector.Y + 1;
SetActorLocation(LocalVector);
}
→ 매 틱마다 지역변수가 다시 초기화되니까 누적이동이 되는게 아니다. 즉, Z 값 자체는 전혀 업데이트 되지 않을 것이다.
Delta Time
언리얼에서 delta time을 이용하면,각 프레임이 실행되는 데 시간이 얼마나 걸리는지 알 수 있다.
어떤 값을 델타 타임으로 곱하면, 게임 프레임률이 독립적으로 변하게 된다. 즉, 빠른 컴퓨터와 느린 컴퓨터에서 게임이 동일하게 동작하게 됨.
ex) 게임 내에서 델타타임이랑 속도(20)을 곱한걸로 초당 모습이 업데이트되게 한다면?
120fps PC에서든, 30fps PC에서든 게임 내 1 프레임에 20만큼 이동한다. 즉, 어떤 성능을 가진 PC에서든 동일한 이동결과가 나타난다.
플레이어와 이동 오브젝트 충돌 보정
움직이는 물체에 가만히 있는, 플레이어가 닿으면 합리적으로 밀리지가 않는다.
→ “내”가 움직일 때는 움직이는 큐브와 닿으면 부드럽게 밀리게 되는데 캐릭터 코드는 움직이지 않는 한 Collision을 확인하지 않기 때문이다.
이 문제를 해결하기 위해서 만들어뒀던, BP_ThirdPersonCharacter로 이동해보자.
Event Tick과 이어지는 MoveUpdatedComopnent를 추가해 충돌을 강제로 적용할 수 있다.
→ [Event Tick → MoveUpdatedComponent(또는 AddActorWorldOffset)]를 호출해 캐릭터의 위치가 변하지 않아도 캐릭터를 이동 과정 중으로 넣어 충돌 검사를 강제로 돌릴 수 있다.
+) 그런데, 플레이어의 움직임에서 회전이 고정된다. 따라서,Get Actor Rotation을 통해 움직임에서 방향을 받아, 캐릭터가 움직이는 방향을 바라보도록 버그를 수정하자.

매 프레임마다(Tick) → 캐릭터가 미세하게 움직엿다가 돌아옴(충돌처리) → 동시에 현재 액터의 FRotator를 가져오고, 그 움직였다 돌아오는 움직임이 액터의 현재 회전을 유지하면서 이동하는 구조가 된다.
Game Mode
- 게임 모드는 게임 규칙과 흐름을 정의하는 클래스이다.
- 어떤 Pawn(플레이어 캐릭터)을 쓸지, 어떤 HUD/GameState를 쓸지를 결정한다.
- 규칙이 달라지거나 주인공이 바뀌면 새로운 GameMode 블루프린트를 만들어 적용한다.
UE_LOG
→ 기본적으로는 display, warning, error으로 자주 사용된다.
- 출력은 Output Log 창에 표시된다.
- 형식:
UE_LOG(LogTemp, Warning, TEXT("Hello %s!"), *Name);
→ 첫 번째 인자: 로그 카테고리 (LogTemp는 기본/임시)
→ 두 번째 인자: 로그 레벨 (Display, Warning, Error 등)
→ 세 번째 인자: 실제 메시지 (C++의 printf처럼 형식 지정 가능)
+) over shoot?

→ OverShoot은 float OverShoot = DistanceMoved - MoveDistance; 내 의도보다 조금 더 이동해버린 거리를 가리킨다.
→ 의도한 거리만큼 더 이동한 양으로, 이 값을 보정해 이동한 곳에서 돌아갈 StartLocation을 다시 잡고, PlatformVelocity를 반전시켜 플랫폼이 정확하게 왕복하도록 한다.
const 멤버 함수
→ const 멤버 함수는 클래스 상태(멤버 변수)를 수정하지 않는 함수이다.
- 따라서 함수 내부에서는 멤버 변수 변경이 불가하고, const로 선언된 다른 함수만 호출 가능하다.
- ex) GetActorLocation()은 const 함수라 호출 가능하지만, SetActorLocation()은 상태를 바꾸므로 const 함수 내에서 호출할 수 없다.
+) AddActorLocalRotation: 액터를 본인 로컬 좌표계 기준으로 회전시킨다.
→ 헤더에 FRotator RotationVelocity(0, 0, 90);을 두고 Tick마다 곱하면, Z축을 중심으로 1초에 90도 회전한다.
+) GetSafeNormal: 벡터의 방향만 구해주는 함수로, 길이를 1로 정규화(normalize)한다.
→ 예를 들어, (10, 0, 0) 벡터도 (1, 0, 0)으로 바뀌어 순수한 방향 정보만 남는다.
- 벡터의 길이가 0일 경우(즉, 크기가 없는 경우) 에러 대신 (0,0,0)을 반환한다.
- 이동 방향이나 속도를 계산할 때 방향 벡터 * 속력 형태로 쓰면 안정적이다.
'공부 > Unreal' 카테고리의 다른 글
| [UE5] Crypt Raider (0) | 2025.10.07 |
|---|---|
| [UE5] Warehouse Wreckage (0) | 2025.07.09 |