득이공간

[언리얼 C++의 이해] 1장. 언리얼 오브젝트의 이해 본문

GP/UE5

[언리얼 C++의 이해] 1장. 언리얼 오브젝트의 이해

쟁득 2024. 1. 29. 20:32
해당 게시물은 이득우 교수님의 '언리얼 C++의 이해' 강의를 수강하며
학습한 내용을 개인적으로 정리한 글입니다.

📌 목차 - 1장. 언리얼 오브젝트의 이해

1-1. 헬로 언리얼!
1-2. 언리얼 C++ 코딩규칙
1-3. 언리얼 C++ 기본타입과 문자열
1-4. 언리얼 오브젝트 기초
1-5. 언리얼 오브젝트 리플렉션 시스템 1
1-6. 언리얼 오브젝트 리플렉션 시스템 2


📌 1-1. 헬로 언리얼

1. 언리얼 엔진 설치, 프로그래밍 환경 구축
2. 언리얼 에디터에서의 클래스 추가
3. 언리얼 C++의 클래스 상속 및 오버라이딩 구현
4. 언리얼 엔진의 문자열 처리의 이해
5. 게임인스턴스 클래스의 적용과 카테고리를 활용한 로그 확인

 

* 에디터 언어 설정: 영어
Editor Preference
region & language
source code

* 확장도구: 매크로 뒤에 엔터시 자동 들여쓰기 삭제
visualcommander

* 언리얼 코드 컴파일 방법
헤더 파일 변경 발생: 에디터를 끄고 비주얼 스튜디오에서 컴파일 f7
소스 파일만 변경 발생: 라이브 코딩으로 컴파일 ctrl+alt+f11
에디터 시작: ctrl+f5

* 비주얼 스튜디오에서 클래스 추가 금지

* 프로젝트 세팅
Maps & Modes
gameInstance 설정
Default Map 설정


📌 1-2. 언리얼 C++ 코딩규칙

1. public에서 private으로 이어지는 클래스 체계(Organization)를 준수
2. 명명 규칙
- 파스칼 케이싱(Pascal Casing)을 사용한다.
- 소문자를 가급적 사용하지 않고 공백 및 언더스코어(_)가 없다.
- 모든 클래스와 구조체에는 고유한 접두사가 있다.
3. 코드의 명확성
- 파라미터에 가급적 In과 Out 접두사를 사용해서 명시
- const 지시자(directive)의 적극적인 활용
- 레퍼런스를 통한 복사 방지
- auto 키워드는 가급적 자제
4. Find In Files의 활용
5. 헤더 파일 및 include 구문은 의존성을 최소화시켜 주의 깊게 다룰 것

 

* 코딩 표준 (코딩 스타일)
- 언리얼 엔진 코딩 표준을 따라야 한다.

* 명명 규칙 종류
- 파스칼 케이싱 UnrealEngine (언리얼 표준)
- 카멜 케이싱 unrealEngine
- 스네이크 케이싱 unreal_engine

* 언리얼 명명 규칙
- U: 언리얼 오브젝트 상속
- A: 액터 상속
- S: UI widget 상속
- I: 추상 인터페이스 클래스
- E: enum
- b: bool
- F: 일반 C++ 클래스, 구조체 등
- IsFunction(): bool 반환 함수

* 포터블 C++ 코드
- uint8: 1byte
- int32: 4byte
- TCHAR: 문자

* 표준 라이브러리를 사용하지 않는다.

* const를 최대한 사용한다.

* 허용되는 auto 키워드 사용
- 변수에 람다를 바인딩해야 하는 경우
- 가독성에 악영향을 미치는 이터레이터 변수의 경우
- 템플릿 코드에서 표현식의 타입을 쉽게 식별할 수 없는 경우

* Enum
UENUM()
enum class EThing : uint8
{
Thing1,
Thing2
}

* Switch문
- break 미사용 시 //fall through 주석달기

* 헤더파일
- include 최소화 -> 전방선언 사용

* 캡슐화
- get, set 사용


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

1. 언리얼이 C++ 타입 int를 사용하지 않는 이유
2. 다양한 캐릭터 인코딩 시스템의 이해
3. 언리얼의 문자열 처리의 이해
4. FString의 구조와 사용 방법
5. FName의 구조와 사용 방법

 

* 플랫폼 파편화 문제

* 포터블 C++ 코드
- uint8: 1byte
- int32: 4byte
- TCHAR: 문자
- float: 4byte
- double: 8byte

* bool 타입의 선언
- 헤더에서는 uint8 bName:1; //1비트 크기로 사용

* 문자열 처리 종류
- Single byte(ANSI, ASCII): 컴퓨터 초창기
- Multibyte(EUC-KR, CP949): 컴퓨터 보급기
- Unicode(UTF-8, UTF-16): 국제 표준 정착기
- 언리얼 엔진: UTF-16(스트링 관리), 한 문자당 2byte를 사용한다.
- 우리가 작성하는 소스코드: UTF-8(한글 사용)로 저장해야 한다.
- FString: TCHAR[]을 담고있는 포인터, 사용시 * 붙이기

* FString의 구조와 활용
- FString은 TCHAR동적배열(TArray)를 가리키는 포인터다.
FString::Printf
FString::SanitizeFloat
FString::FromInt
- FCString 클래스가 포함되어있다.
FCString::Atoi
FCString::Atof

* FString -> FName: 애셋 관리
- 내부적으로 해시값으로 변환해서 애셋을 빠르게 찾는다.
- 대소문자 구분이 없다. 가볍다.
FNamePool: 글로벌 싱글톤 자료구조, FName의 Key값들을 보관한다.
FName 생성 시 항상 해당 키값이 풀에 존재하는지 확인하기 때문에 틱함수 같은 곳에서는 static으로 사용하는 것이 좋다.

* FString -> FText: UI 다국어 지원


📌 1-4. 언리얼 오브젝트 기초

1. 게임이 대형화되면서 성능과 유지보수 두 가지가 모두 중요해졌다.
2. 언리얼 엔진은 C++ 언어를 확장한 언리얼 오브젝트라는 객체 구조를 고안했다.
3. 지정된 매크로를 사용해 빌드를 수행하면, 추가 코드가 자동으로 만들어지는 구조를 가진다.
4. 언리얼 오브젝트를 사용해 대규모 게임 제작을 안정적으로 설계하고 구현할 수 있다.

 

* 게임 프로그래밍
- 사용자: 성능
c++
메모리 직접 제어
캐시 활용 극대화
저수준 api 직접 호출
복사 작업 최소화
- 개발자: 안정성(유지보수)
하이레벨언어
유지보수성 향상
크래시로부터 보호
자동 메모리 관리
고질적 실수 예방

* SOLID 원칙 & 새로운 기능
1. SRP (단일 책임 원칙): 하나의 클래스는 하나의 책임만 가져야 한다.
2. OCP (개방-폐쇄 원칙): 클래스 설계를 변경하지 않고 동작을 확장할 수 있어야 한다.
3. LSP (리스코프 치환 원칙): 자식 클래스는 부모 클래스를 대체 사용할 수 있어야 한다.
4. ISP (인터페이스 분리 원칙): 작고 명확한 인터페이스들로 분리해 관리해야 한다.
5. DIP (의존 역전 원칙): 구현을 배제시킨 상위 정책을 바라보며 설계해야 한다.
- 인터페이스: 객체 설계의 틀을 제공하는 추상 클래스
- 리플렉션: 런타임에서 객체의 구조를 파악하고 객체에 메타데이터를 부여
- 델리게이트: 프로그램에서 발생한 이벤트를 다수의 객체에 효과적으로 전달하는데 활용

* 언리얼 오브젝트: U - 게임 콘텐츠 개발에 사용
CDO(클래스 기본 객체): 클래스의 기본 값과 타입 정보 제공
리플렉션: 런타임에서 클래스 정보의 참조 기능
인터페이스
열거형
델리게이트
가비지 컬렉션: 자동 메모리 관리
구조체: 리플렉션이 가능한 구조체 지원
직렬화(Serialization): 객체 정보를 바이트 스트림으로 저장, 전송, 불러들이는 기능

* 일반 C++ 오브젝트: F - 빠른 처리에 사용

* 언리얼 헤더 툴 -> ".generated.h"를 생성 // intermediate 폴더
- GENERATED_BODY() 사용
1. 언리얼 헤더툴에 의해 소스코드를 자동 생성
2. 생성된 코드를 포함해서 최종 빌드


📌 1-5. 언리얼 오브젝트 리플렉션 시스템 1

1. 언리얼 오브젝트에는 항상 클래스 정보를 담은 UClass 객체가 매칭되어 있다.
2. UClass로부터 언리얼 오브젝트의 정보를 파악할 수 있다.
3. UClass에는 클래스 기본 오브젝트(CDO)가 연결되어 있어 이를 활용해 개발의 생산성을 향상시킬 수 있따.
4. 클래스 정보와 CDO는 엔진 초기화 과정에서 생성되므로 게임 개발에서 안전하게 사용 가능하다.
5. 헤더 정보를 변경하거나 생성자 정보를 변경하면 에디터를 끄고 컴파일하는것이 안정적이다.

 

* 언리얼 프로퍼티 시스템 = 리플렉션
- 프로그램이 실행시간에 자기 자신을 조사하는 기능이다.
- 언리얼 헤더 툴이 다음 매크로를 분석하고 소스코드를 생성한다.
UENUM()
UCLASS()
USTRUCT()
UFUNTION()
UPROPERTY()
- GENERATED_BODY()
- UPROPERTY 선언을 통해서만 언리얼이 메모리 관리를 해준다. (가비지 콜렉터)
- 계층
UObject
UField
UStruct
UClass
UScriptStruct
UFunction
UEnum
UProperty

* 언리얼 오브젝트 구성
- new() -> NewObject<TypeName>()
- 언리얼 C++ 객체 -> UClass -> CDO(ClassDefaultObject)
- GetClass() == UTypeName::StaticClass() - UClass 호출
- GetDefaultObject<UTypeName>() - CDO 호출

* check(bool) 함수
- false => assertion failed, 에디터가 꺼지도록 한다.
- 중요한 부분마다 check()문을 넣는 습관을 들이자.

* ensure(bool) 함수
- false => 에디터가 꺼지지 않고 에러로그가 뜬다.

* ensureMsgf(bool, TEXT(""))
- 에러로그에 추가 문자열이 뜨도록 한다.

* CDO
- CDO를 변경하는 코드는 헤더 파일 변경처럼 에디터가 꺼져있어야 반영이 된다.


📌 1-6. 언리얼 오브젝트 리플렉션 시스템 2

1. 리플렉션 시스템을 사용해 언리얼 오브젝트의 특정 속성과 함수를 이름으로 검색할 수 있다.
2. 리플렉션 시스템을 사용해 접근 지시자와 무관하게 값을 설정할 수 있다.
3. 리플렉션 시스템을 사용해 언리얼 오브젝트의 함수를 호출할 수 있다.

 

* 실습
- Person
Student
Teacher

* FProperty*, FindPropertyByName() 활용
FString CurrentTeacherName;
FString NewTeacherName(TEXT("이득우"));
FProperty* NameProp = UTeacher::StaticClass()->FindPropertyByName(TEXT("Name"));
if (NameProp)
{
NameProp->GetValue_InContainer(Teacher, &CurrentTeacherName);
UE_LOG(LogTemp, Log, TEXT("현재 선생님 이름 %s"), *CurrentTeacherName);

NameProp->SetValue_InContainer(Teacher, &NewTeacherName);
UE_LOG(LogTemp, Log, TEXT("새로운 선생님 이름 %s"), *NewTeacherName);
}

* UFunction*, FindFunctionByName() 활용
UFunction* DoLessonFunc = Teacher->GetClass()->FindFunctionByName(TEXT("DoLesson"));
if (DoLessonFunc)
{
Teacher->ProcessEvent(DoLessonFunc, nullptr);
}