개발자로/Unreal

[Unreal4] 언리얼 아키텍처 노트

ReasonyB 2022. 8. 10. 15:18

Project는

 - Primary Game Module

 - Additional Game Module

로 C++ 클래스를 구성.

 

클래스: 새 Actor 혹은 Object에 대한 템플릿 정의.

- 헤더: 클래스, 클래스 함수, 프로퍼티

- 구조체도 포함 가능. -> 연관성이 있는 프로퍼티를 체계적으로 정지 조작하는 데 도움 되는 구조체. 자체적 정의 가능

인터페이스: 다른 클래스에서 추가적인 게임플레이 작동방식을 구현할 수 있다.

 

문법

표준 C++의 문법으로 정의. 그러나 UCLASS(), UFUNCTION(), UPROPERTY() 메크로 사용하여 엔진에 클래스, 함수, 변수 인식시킬 수 있다. 

 - UPROPERTY() 메크로 선행된 선언부의 변수는 엔진 가비지 컬렉션 대상이 됨. 언리얼 에디터 내에서 표시 및 수정 가능

 - 기타 UINTERFACE()와 USTRUCT() 매크로도 존재

 - 블루프린트에 C++ 코드를 노출시키는 UPARAM() 매크로 존재

 

게임 플레이 클래스

 모든 게임 플레이 클래스에는 클래스 헤더.h와 클래스 소스.cpp 파일 존재.

 

접두사

A- Spawnable한 게임 플레이 오브젝트의 베이스 클래스에서 확장. Actor로 월드에 바로 스폰 가능

U- 모든 게임 플레이 오브젝트의 베이스 클래스에서 확장. 월드에 바로 인스턴싱 불가. 보통 Components와 같은 오브젝트

 

클래스 헤더 파일

파일 이름은 보통 그 안에 정의되는 클래스 이름에서 접두사 A 또는 U를 뺀 것과 일치해야 함.

여기에 클래스, 변수, 함수 선언 과정을 단축하기 위해 전용 매크로도 같이 사용됩니다.

ClassName.h 상단에 #include "ClassName.generated.h" 줄이 기입되어야 함.

 

클래스 선언 문법

UCLASS([specifier, specifier, ...], [meta(key=value, key=value, ...)])
class ClassName : public ParentName
{
    GENERATED_BODY()
}

클래스 지정자(specifier)나 메타데이터(meta)같은 지시어(descriptor)가 UCLASS 매크로에 전달됨. 이 매크로는 선언 중인 클래스에 대한 Uclass를 생성하는 데 사용. GENERATED_BODY() 매크로는 클래스 본문 제일 처음에 와야 함

 

클래스 지정자(class specifier)

Abstract 추상 베이스 클래스로 선언. 사용자가 이 클래스 액터를 월드에 추가 하지 못함. 그 자체로는 의미 없는 클래스에 쓰기 좋음.
AdvancedClassDisplay 이 클래스의 모든 프로퍼티가 디테일 패널의 고급 섹션에만 표시 되도록 함. 개별 프로퍼티에서 이 설정을 무시하고 싶다면 SimpleDisplay 지정자를 사용하면 됌
AutoCollapseCategories = (Category1, ...) 지정된 나열된 카테고리에 대해 부모 클래스 상의 AutoExpandCategories 지정자 효과 무효화시킨다.
AutoExpandCategories = (Category1, ...) 이 클래스의 오브젝트에 대해서 언리얼 에디터 프로퍼티 창에 자동 확장되도록할 카테고리를 하나 이상 지정. 
Blueprintable 이 클래스를 블루프린트로 생성이 가능한 베이스 클래스로 노출. 기본값은 다른 식으로 상송되지 않는 한 NotBlueprintable
BlueprintType 이 클래스를 블루프린트에서 변수로 사용할 수 있는 Type으로 노출
ClassGroup = GroupName 언리얼 에디터의 엑티브 브라우저에서 그룹 뷰 옵션을 켰을 때, 지정된 GroupName내에서 이 클래스와 그 서브 클래스를 포함시킴
CollapseCategories 이 클래스의 프로퍼티들은 언리얼 에디터에서 프로퍼티 창의 카테고리에서 그룹으로 묶이지 않아야함. 자식 클래스에서 DontCollapseCategories 지정자 사용하여 덮어쓰기 가능
Config = ConfigName 이 클래스는 환경설정(.ini) 파일에 데이터를 저장할 수 있음을 나타냄. config/globalconfig 지정자로 선언된 클래스 프로퍼티가 있는 경우, 이 지정자는 그 프로퍼티를 해당 이름의 환경설 정파일에 저장하도록 해야 함. 무효화는 불가능하나, 저장되는 환경설정 파일을 바꿀 수는 있음.
Const 이 클래스의 모든 프로퍼티와 함수는 const며 const로 노출. 상속됨
ConversionRoot 루트 변환 서브클래스가 계층상의 첫 번째 루트 클래스의 자손 클래스로만 변환할 수 있도록 제한
CustomContstructor 생성자 선언의 자동생성을 방지.
DefaultToInstanced 이 클래스의 모든 인스턴스는 Instanced 로 간주. 인스턴스드 클래스는 생성시 복제됩니다.
DependsOn = (ClassName1, ClassName2, ...) 나열된 모든 클래스는 이 클래스에 앞서 컴파일됩니다. ClassName은 같은 패키니의 클래스를 지정해야합니다. 다중 종속 클래스 지정은 (표기법을 따로 찾아봐야함). 컴파일러는 이미 컴파일된 클래스에 있는 것에 대해서만 알고 있기에 다른 클래스에서 선언된 구조체 또는 enum을 사용사는 클래스의 경우 중요.
Deprecated 이 클래스는 폐기 되어서 이 클래스의 오브젝트는 시리얼라이즈에 저장되지 않음. 상속성
DontAutoCollapseCategories = (Category, Category, ...) 부모 클래스에서 상속된 나열 카테고리에 대해 AutoCollapseCategories 지정자를 무효화
EditInlineNew 이 클래스의 오브젝트는 기존에셋에서 참조되는 것과 반대로 언리얼 에디터 프로퍼티 창에서 생성할 수 있음. 작동방식: 기존 오브젝트로 레퍼런스만 프로퍼티 창을 통해 할당할 수 있음. 이 지정자는 모든 자식 클래스에 전파 됨. 상속성. NotEditInlineNew로 덮어쓰기 가능. 
HideCategories = (Category1,...)  이 클래스의 오브젝트에 대해 언리얼 에디터의 프로퍼티 창에서 숨겨야하는 카테고리를 하나이상 나열. 카테고리 없이 선언된 프로퍼티가 대상이라면, 변수 선언하는 것과 같은 클래스 이름 사용. 상속성
HideDropdown 이 클래스가 언리얼 에디터 프로퍼티 창에 콤보박스로 나타나지 않도록
HideFunctions = (Category1, ..) 프로퍼티 뷰에서 해당 카테고리 함수를 숨김
HideFunctions = FunctionName 프로퍼티 뷰에서 해당 이름 함수 숨김
Intrinsic C++에서 바로 전언하여 언리얼 헤더 툴이 생성하는 상용구가 없음을 의미. 새 클래스에 사용하지 말 것 (이해하지 못함)
MinimalAPI 클래스의 형 정보만 다른 모듈이 사용할 수 있도록 노출. 이 클래스는 형 변환 가능. 함수는 호출할 수 없음. 다른 모듈에서 접근할 수 없는 함수 전부가 필요하지 않은 클래스에 대해 모든 것을 익스포트 하지 않아 컴파일 시간 향상
NoExport 이 클래스의 선언은 헤더 생성기의 자동 생성 C++헤더 파일에 포함시키지 말아야함을 의미. C++ 클래스 선언은 헤더파일에 수동 정의 Native클래스에만 유효. 새 클래스에선 사용X
NonTransient Transient 지정자 무효화
NotBlueprintable 블루프린트 생성을 위한 베이스 클래스로 사용할 수 없음을 나타냄. 디폴트 값. 서브클래스 상속
NotPlaceable Placeable 지정자 무효화. 에디터의 레벨, UI, 블루프린트에서 배치할 수 없음
PerObjectConfig 이 클래스의 환경설정 정보는 오브젝트 별로 저장. 각 오브젝트에 ini 파일 섹션이 있고 이름은 [ObjectName ClassName]으로 포맷. 상속성
Placeable 이 클래스는 에디터에서 생성할 수 있고, 유형에 따라 레벨, UI 씬, 블루프린트에 배치할 수 있음.
ShowCategories = (Category1,... ) 나열된 카테고리에 대해  HideCategories 지정자 무효화
ShowFunctions = (Category1, ...) 프로퍼티 뷰어에서 나열된 카테고리 내 모든 함수 표기
ShowFunctions = FunctionName 프로퍼티 뷰어에서 나열된 함수 표시
Transient 이 클래스에 속하는 오브젝트는 디스크에 저장되지 않음. 플레이어나 창처럼 속성상 지속적이지 않은 네이티브 클래스 같은 것과 함께 쓸 때 좋습니다. 상속성, NonTransient 지정자로 덮어 쓰기 가능
Within = OuterClassName 이 클래스의 오브젝트는 OuterClassName 오브젝트의 인스턴스 외부에 존재할 수 없음. 즉 이 클래스의 오브젝트 생성하는 것은 OuterClassName 인스 턴스를 Outer 오브젝트로 제공해야한다는 의미.

메타데이터 지정자

BlueprintSpawnableComponent 존재하면, 블루프린트가 컴포넌트 클래스를 스폰시킬 수 있음
BlueprintTreadSafe 블루프린트 함수 라이브러리에서만 유효. 이 클래스의 함수를 애니메이션 블루프린트의 비게임 스레드에서 호출가능한 것으로 마킹.
ChildCannotTick 액터 및 컴포넌트 클래스에서 사용. 네이티브 클래스가 틱할 수 없으면, 이 액터 또는 컴포넌트를 기반한 블루 프린트 생성 클래스 도, 심지어 bCanBlueprintTickByDefault가 true여도 절대 틱 되지 않음.
ChildCanTick 액터 및 컴포넌트 클래스에 사용. 이 메타 데이터를 사용하면, bCanBlueprintsTickByDefault가 false일때,  bCanEverTick 옵션을 덮어쓰도록 할 수 있음.
DeprecatedNode Behaviour Tree 노드에 대해, 클래스가 폐기 되어 컴파일 시 경고가 표시됨을 나타냄.
DeprecationMessage = "Message Text" 클래스가 폐기되면, 그것을 사용하는 블루 프린트를 컴파일하려 할 때 표준 폐기 경고에 이 메세지 추가.
DisplayName = "Blueprint Node Name" 블루프린트의 노드이름은 코드 생성 이름이 아닌 여기 제공된 값으로 대체.
DontUseGenericSpawnObject 블루프린트의  Generic Create Object 노드를 사용하여 클래스의 오브젝트를 스폰하지 않음. 이 지정자는 Actor도 ActorComponent도 아닌 BlueprintType 클래스에만 의미가 있음.
ExposedAsyncProxy Async Task노드에  이 클래스의 프록시 오브젝트를 노출
IgnoreCategoryKeywordsInSubclasses 클래스의 첫 서브 클래스가 생성되면, 상속된 ShowCategories/HideCategories 지정자를 전부 무시하도록
IsBlueprintBase = "true/false" 이 클래스가 블루프린트 생성을 위한 베이스 클래스로 적합한지 아닌지를 나타냅니다. UCLASS 지정자 Blueprintable 또는 NotBlueprintable과 유사
KismetHideOverrides = "Event1, Event2, ...." 덮어쓰기를 허용하지 않을 이벤트 목록
ProhibitedInterfaces = "Interface1, ....." 클래스와 호환되지 않는 인터페이스 목록
RestrictedToClasses = "Class1, Class2, ..." 블루프린트 함수 라이브러리 클래스에서 목록의 클래스 사용을 제한시킴
ShortToolTip = "Short tooltip" 부모 클래스 선택기 대화창 처럼 전체 툴팁표시가 버거운 상황에서 사용할 짧은 툴팁
ShowWorldContextPin 이 클래스 소유 그래프가 배치된 블루프린트 노드는 월드 컨덱스트 핀을 반드시 표시해야함. 이 클래스 오브젝트를 월드 컨텍스트로 사용할 수 없어서 보통 숨겨진 경우에도 마찬가지이다.
UsesHierarchy 클래스가 계층형 데이터를 사용함을 표시. 디테일 패널의 계층형 편집 기능 인스턴싱에 사용.
ToolTip = "Hard-written tooltip" 코드 코멘트에서 자동 생성되는 툴팁을 덮어씁니다.

클래스 구현

모든 게임 플레이 클래스는  GENERATED_BODY 매크로를 사용해야 함.

클래스 파일들은 수동 선언도 가능함. AActor 클래스에 대한 C++선언은 EngineClasses.h 헤더 파일에 자동 생성. 즉 Cpp 파일은 반드시 EngineClasses.h를 포함하거나, 다른 파일의 인클루드에 포함해야 함. 일반적으로는 게임 프로젝트에 대한 헤더 파일만 포함하면 게임 프로젝트의 게임 플레이 클래스에 대한 헤더가 포함 됨. AActor와 EngineClasses.h의 경우, EnginePrivate.h헤더가 포함되어 있는 데, 여기는 Engine 프로젝트에 대한 헤더파일이  Engine.h 가 포함 되어있으며, EngineClasses.h 헤더 파일이 포함된다.

#include "EnginePrivate.h"

 

클래스 생성자

 UObject는 생성자를 사용하여 프로퍼티나 기타 변수에 대한 기본값, 초기화 작업. 클래스 생성자는 보통 클래스 구현 파일 안에 위치. 그러나 생성자가 클래스 소스 파일에 있는 경우, UClass에 CustomConstructor 지정자를 선언해 줘야 자동 코드 생성기가 헤더에 생성자 선언부를 만들지 않음.

 

생성자 포맷

UMyObject::UMyObject(){}

이 생성자는 클래스의 디폴트 오브젝트(Class Default Object,CDO)를 초기화 시키는데, 클래스 인스턴스의 기반이되는 마스터 사본. 특수한 프로퍼티 변경 구조체를 지원하는 부가 생성자도 존재

생성자에 작성된 초기화 코드는 CDO에 적용될 것이고, 이어서 CreateNewObject 또는 SpawnActor와 같은 엔진 내 적절히 생성된 새로운 오브젝트 인스턴스에 복사될 것

 

생성자 스태틱 및 헬퍼

좀 더 복잡한 데이터 유형, 특히나 클래스 레퍼런스, 이름 , 에셋 레퍼런스 값을 설정하는 데는, 생성자의 ConstructorStatics 구조체가 필요한 여러가지 프로퍼티 값을 담을 수 있도록 정의 하고 인스턴싱 하는 것이 필요. 이 ConstructorStatics 구조체는 생성자가 처음 실행될 때만 만들어짐. 이후 실행시 그냥 포인터만 복사함므로 매우 빠름. ConstructorStatics 구조체 생성시 구조체 멤버에 값이 할당되어, 나중에 생성자에서 실제 프로퍼티 자체에 값을 할당할 때 접근할 수 있다. ConstructorHelpers는 ObjectBase.h에 정의 되는 특수한 네임스페이스로, 생성자 전용 흔한 동작을 수행하는 데 사용하는 헬퍼 템플릿이 들어있다. 

에셋 레퍼런스

이상적으로 클래스의 애셋 레퍼런스는 존재하지 않음. 하드코딩된 에셋 레퍼런스는 취약해서, 에셋 프로퍼티의 환경설정에는 블루프린트를 사용하는 쪽이 선호되는 방식. 그러나 하드 코딩된 레퍼런스는 여전히 완벽 지원. 오브젝트를 생성할 때마다 에셋을 검색할 수 없으니 검색은 한번만. 이는 에셋 검색을 한 번만 하도록 보장해주는 정적인 구조체를 통해 이루어짐.

ConstructorHelper::FObjectFinder는 StaticLoadObject를 사용해서 지정된 UObject로의 레퍼런스를 찾는다. 이늘 보통 콘텐츠 패키지에 저장된 에셋을 참조하는 데 사용. 오브젝트를 찾지 못하면 실패했다고 보고 됌.

ATimelineTestActor::ATimelineTestActor()
{
    // 일회성 초기화 저장을 위한 구조체입니다.
    struct FConstructorStatics
    {
        ConstructorHelpers::FObjectFinder<UStaticMesh> Object0;
        FConstructorStatics()
        : Object0(TEXT("StaticMesh'/Game/UT3/Pickups/Pickups/Health_Large/Mesh/S_Pickups_Base_Health_Large.S_Pickups_Base_Health_Large'"))
        {
        }
    };
    static FConstructorStatics ConstructorStatics;

    // 프로퍼티 초기화

    StaticMesh = ConstructorStatics.Object0.Object;
}

 

클래스 레퍼런스

ConstructorHelpers::FClassFinder는 지정된 UClass 에 대한 레퍼런스를 찾는다. 찾지 못하면 실패를 보고.

APylon::APylon(const class FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
    // 일회성 초기화 저장을 위한 구조체입니다.
    static FClassFinder<UNavigationMeshBase> ClassFinder(TEXT("class'Engine.NavigationMeshBase'"));
    if (ClassFinder.Succeeded())
    {
        NavMeshClass = ClassFinder.Class;
    }
    else
    {
        NavMeshClass = nullptr;
    }
}

그냥 USomeClass::StaticClass()를 사용해 복잡한 ClassFinder는 한꺼번에 건너 뛰기 가능. 아래와 같이.

NavMeshClass = UNavigationMeshBase::StaticClass();

크로스 모듈 레퍼런스의 경우, 아마 ClassFinder 메서드를 사용하는 편이 나음.

 

컴포넌트와 서브오브젝트

컴포넌트 서브오브젝트를 만들고 액터의 계층구조에 붙이는 것은 생성자 안에서도 가능. 액터 스폰 시, 그 컴포넌트가 CDO에서 복제. 컴포넌트가 항상 제대로 생성, 소멸, 가비지 콜랙팅의 과정을 거치도록 하기 위해서는 생성자에서 생성된 모든 컴포넌트로의 포인터를 소유 클래스의 UPROPERTY()에 저장해야 함.

부모 클래스에 속하는 컴포넌트 변경은 일반적으로 필요치 않는 작업, 하지만 붙은 모든 컴포넌트 현재 목록은 부모 클래스가 생성한 컴포넌트를 포함해서 가능. 루트 컴포넌트를 포함해 아무 USceneComponent에서 GetAttachParent, GetParanetComponents, GetNumChildrenComponents, GetChildrenComponents, GetChildComponent를 호출해보길.