언리얼 엔진 본캠프 6주차-3 언리얼 엔진 C++ : Object와 Actor

정재훈·2025년 1월 22일
0

unreal engine

목록 보기
15/45

<UObject와 AActor>

💡UObject

UObject는 언리얼 엔진에서 모든 클래스의 부모 클래스로 언리얼 클래스들은 UObject에서 파생, 리플렉션 시스템(Reflection System)과 가비지 컬렉션(Garbage Collection)과 같은 엔진의 핵심 기능을 제공한다

역할

  • 게임 로직과 데이터를 저장하고 관리하는 데 사용
  • 주로 데이터 테이블(능력치), AI 로직 관리 클래스, UI, 세이브 데이터 관리 등 화면에 보이지 않는 추상적, 비게임플레이 요소에 사용

특징

  • 트랜스폼 정보가 없음 => world에 배치 불가능 => 화면에 보이지 않는 기능에 사용
  • 렌더링 불가 : 트랜스폼 정보가 없는 것에 더불어 화면에 나타날 수 없음

기능

  • 가비지 컬렉션(Garbage Collection) : 가비지 컬렉션 시스템을 통해 객체들이 자동으로 메모리에서 해제. 이를 위해 스마트 포인터 사용 권장

    • 엔진에서는 레퍼런스 그래프를 만들어 어느 오브젝트가 아직 사용중이고 어느 것이 해제가 되었는지를 알아낸다
      1. 루트 세트 설정
        • 루트 세트는 가비지 컬렉션 시스템에서 "참조의 출발점"이 되는 객체들의 집합
        • AddToRoot() 메서드로 객체를 루트 세트에 추가
      2. 참조 트리 탐색(Mark Phase)
        • 루트 세트부터 시작하여, 엔진은 참조 트리를 따라가며 참조된 객체들을 마킹(Marking)
      3. 고아 객체 확인(Sweep Phase)
        • 마킹된 객체들은 사용하지 않더라도 메모리 해제가 되지 않음 => 메모리 누수이긴 하지만 그럴만한 이유가 있어서 루트 세트에 포함되어 있는 것 => 다 사용했으면 개발자가 수동으로 RemoveFromRoot()를 호출하여 루트 세트에서 해당 객체를 제거해야함
        • 고아 객체(참조되지 않은, 마킹되지 않은 객체)들은 UObjectMarkPendingKill() 메서드가 호출되어 삭제 대기 상태로 설정
      4. 가비지 컬렉션 후처리(Cleanup)
        • 가비지 컬렉션이 완료된 후, 엔진은 UObject::Destroy() 등의 메서드를 호출하여 고아 객체들을 메모리에서 삭제
      5. 추가적인 최적화 및 후속 작업
        • 시스템의 성능 최적화를 위해 메모리를 점검
    • 즉, 가비지 컬렉션이 발생하면 엔진이 모든 메모리를 하나 하나 마킹하는 게 아니고 루트 세트부터 참조된 메모리들만 마킹하고 마킹이 끝나면 그 외 나머지 메모리를 전부 해제
  • 직렬화(Serialization) : 객체의 상태를 바이트 스트림 형태로 변환하여 디스크에 저장하거나 네트워크로 전송할 수 있도록 하는 과정. 캐릭터의 체력을 100 → 200으로 변경하고 저장하면 다음에 불러올 때 캐릭터의 체력을 200으로 불러옴

    • 저장 및 로드 : 게임의 진행 상태를 저장하고, 나중에 불러올 수 있게 하기 위해 객체 데이터를 직렬화
    • 네트워크 통신 : 네트워크를 통해 객체 데이터를 전송할 때, 객체를 직렬화하여 데이터를 패킷 형태로 전송하고, 수신 측에서 역직렬화하여 객체를 복원
    • 게임 월드와 상태 관리 : 게임의 월드나 씬에 존재하는 객체들은 직렬화되어 디스크에 저장되고, 이후 불러오는 방식으로 관리
  • 리플렉션 시스템(Reflection System) : Reflection은 런타임에 인스턴스의 데이터 정보를 확인할 수 있는 기능

    • 언리얼에는 크게 두가지 C++ 오브젝트, 언리얼 Object 이렇게 두가지로 나뉘고 C++ 오브젝트를 언리얼 오브젝트로 만들기 위해 엔진 및 에디터 함수 기능을 제공하는 다양한 매크로로 C++ 클래스를 캡슐화하면 이 리플렉션 시스템을 사용할 수 있게 된다
    • UObject 리플렉션 시스템은 객체의 속성, 메서드, 클래스를 런타임에 확인하고 조작할 수 있게 한다
      • UCLASS() : 언리얼 엔진에서 클래스가 리플렉션 시스템에 등록될 수 있도록 하는 매크로
        • 리플렉션 시스템 활성화 : 클래스의 속성(UProperty)과 메서드(UFunction) 등을 런타임에서 동적으로 탐지하고 조작할 수 있음
        • 객체 생성 및 관리 : UCLASS() 매크로가 붙은 클래스는 UObject를 상속받으므로, 언리얼 엔진의 가비지 컬렉션, 메모리 관리 등 시스템에서 관리
        • 직렬화 및 네트워크 통신 : UCLASS()UPROPERTY()를 특수한 클래스와 함께 사용하면 객체의 변수를 직렬화할 수 있고 Replicated와 같은 옵션(지정자)를 사용하면 네트워크 통신도 가능
        • 에디터 및 블루프린트와의 통합 : UCLASS가 정의된 클래스는 언리얼의 에디터와 블루프린트에서 사용될 수 있도록 메타데이터가 생성
      • UPROPERTY() : UObject 기반 클래스의 속성(변수)의 메타데이터를 리플렉션 시스템에 등록
        • EditAnywhere - can mod default value in the BP details panel, but not at runtime.
        • ReadWrite - can get/mod dynamically in the Event Graph.
        • ReadOnly - can get in the Event Graph.
      • UFUNCTION() : UObject 기반 클래스의 함수를 리플렉션 시스템에 등록
      • UENUM() : 열거형을 리플렉션 시스템에 등록
      • USTRUCT() : 해당 구조체를 리플렉션 시스템에 등록
      • 나머지 매크로들(UCLASS()가 아닌)도 리플렉션 시스템 활성화나 가비지 컬렉션에 의한 메모리 관리를 할 수 있게 해주고, 지정자에 따라 에디터 및 블루 프린트 통합, 네트워크 시스템 통합 등을 할 수 있게 해준다
    • UClass, UProperty, UFunction, UEnum, UDelegate 등은 메타데이터를 관리하고 리플렉션 시스템을 활용하는 데 사용되는 클래스로 매크로와 생김새가 비슷하지만 헷갈리지 않도록 유의
  • 동적 할당 : NewObject<T>() 함수를 호출하여 객체를 동적으로 생성할 수 있다

    • 언리얼 오브젝트는 가비지 컬렉터가 관리하기 때문에 new 키워드를 사용하면 안된다
      • C언어의 malloc()과 C++의 new가 다르듯이 언리얼에서도 new와 NewObject<T>() 다르다고 생각하면 됨
  • 상속 및 다형성 : UObejct를 상속한 클래스는 UObject의 모든 기능을 사용할 수 있다


🚶‍AActor

AActor는 UObject를 상속한 클래스로, 언리얼 엔진의 world에 존재할 수 있는 모든 게임 오브젝트의 기본 클래스

역할

  • 트랜스폼 정보를 가지고 있어 world에 실제로 배치되어 다른 객체들과 상호작용한다
  • 주로 캐릭터, 무기, 환경 요소, 조명, 이펙트 등 실제 화면에 보이는 요소에 사용

특징

  • 트랜스폼 정보를 포함 => world에 배치 가능 => 화면에 보일 수 있음
  • 컴포넌트 기반 구조 : 여러 컴포넌트(메시, 파티클, 사운드 등)를 추가로 붙여 기능을 확장할 수 있음 => 메시가 추가되면 렌더링 가능 => 화면에 나타남
    • Root Component : 액터의 기본 트랜스폼을 정의하는 컴포넌트
    • 트랜스폼 : USceneComponent와 그 하위 클래스
    • 렌더링 : UStaticMeshComponent, USkeletalMeshComponent 등.
    • 트리거 및 충돌 : UBoxComponent, USphereComponent 등.
    • 카메라 및 이동 : UCameraComponent, USpringArmComponent, UMovementComponent
    • 이런 컴포넌트들은 UObject를 상속
  • 이벤트 처리 가능 : BeginPlay(), Tick(), Destroy()등 라이프 사이클 이벤트를 활용
    • BeginPlay() : 액터가 처음 활성화될 때 한 번 호출되는 함수
      • 컴포넌트 초기화, 타이머 설정, 이벤트 바인딩 등에 사용
    • Tick() : 매 프레임 호출되는 함수
      • 위치 이동, 애니메이션, 상태 변화와 같이 지속적인 업데이트가 필요한 작업을 처리
    • Destroy() : 액터와 관련된 모든 리소스를 해제하고 액터를 파괴
      • 액터가 파괴되기 직전에 EndPlay() 호출
    • EndPlay(const EEndPlayReason::Type EndPlayReason) : 액터가 파괴되거나 월드에서 제거될 때 호출
      • 타이머 해제, 이벤트 해제, 네트워크 관련 리소스 정리, 복제 리소스 해제 등 관계를 정리하는 작업을 함(메모리 해제가 되는건 아님)
      • EndPlayReason의 값
        • EEndPlayReason::Destroyed : 액터가 명시적으로 파괴되었을 때
        • EEndPlayReason::LevelTransition : 레벨 전환 시 액터가 제거되었을 때
        • EEndPlayReason::EndPlayInEditor : 에디터에서 게임이 종료되었을 때
        • EEndPlayReason::RemovedFromWorld : 월드에서 제거되었을 때
        • EEndPlayReason::Quit : 게임이 종료되었을 때
  • 네트워크 동기화 : 서버와 클라이언트 간에 게임의 상태를 일관성 있게 유지하기 위한 프로세스
    • 리플렉션 시스템을 통해 이루어짐
      • 여기서 하나 알게 된 것은 리플렉션 시스템이 클라스마다 지원하는 방식이 조금 다르다는 것이다.
      • 네트워크 복제와 직렬화는 전혀 다른 동작을 하는데, Chat-GPT의 직렬화 예제에서는 USaveGame 클래스를 사용하였고 네트워크 복제 예제에서는 AActor 클래스를 사용했다
      • 그럼 AActor는 직렬화를 못하나?, UsaveGame은 네트워크 복제를 못하나? 이 부분에 대해 직접 실험해보진 않았지만(나중에 추가) 대답은 그렇다
      • 클래스마다 리플렉션 시스템 기능을 사용하기 위한 추가적인 코드와 메커니즘이 포함되어 있음 => 용도에 맞는 클래스를 선택해서 사용해야 함

기능

  • AActorUObject를 상속받기 때문에 UObject에서 제공하는 여러 기능을 사용할 수 있을 뿐만 아니라 추가적인 기능을 제공
    • 트랜스폼 : 위치, 회전, 크기와 같은 트랜스폼 정보를 가지고 있어 world에 배치 가능
    • 상호작용 : 충돌 감지, 물리적 상호작용, 이벤트 등을 처리할 수 있음
    • 렌더링 : 렌더링 컴포넌트를 추가로 붙여 시각적으로 표시될 수 있음

UObject와의 차이

  • AActorUObject와 다르게 UWorld::SpawnActor<T>() 함수를 사용해야 world에 배치할 수 있고 다른 객체들과 상호작용할 수 있다
  • AActorworld와 강한 연계가 되어 있어 일반적으로 가비지 컬렉션의 대상이 아님
    • 액터를 제거하고 싶으면 Destroy()를 호출하여 명시적으로 제거해야 함
      • Destroy()는 내부적으로 Destroyed()EndPlay()를 호출하고 액터를 삭제 준비 상태("Pending Kill")로 만든다
      • Destroyed() 함수는 Unreal Engine에서 AActor 클래스에 정의된 가상 함수. 액터 삭제에 대한 후처리 작업의 시작 지점으로, EndPlay()가 그 과정의 일부로 호출된다.
        • 특정 작업을 수행하려면 사용자가 이를 오버라이드해야 함
      • EndPlay()는 주변 정리를 함
        • 이때 액터는 월드에서 비활성화(월드와의 연결이 끊어짐)되고 액터와 관련된 컴포넌트들의 참조도 끊어짐
      • "Pending Kill" 상태가 된 액터는 가비지 컬렉터의 처리 대상이 됨
        • 하지만 코드 상에서는 여전히 액터를 참조할 수 있음(댕글링 포인터)
        • IsPendingKill() 함수를 사용하여 유효성 검사를 하는 것이 안전
      • 다음 가비지 컬렉션 주기에 액터와 컴포넌트들의 메모리가 해제됨
    • Destroy()나 EndPlay()가 호출되면 해당 Actor는 유효하지 않음 이후 Garbage Collection에 의해 메모리가 회수되는 것
  • 실제 언리얼 내부 코드를 보면 Destroy 호출 순서가 Destroy() -> DestroyActor() -> Destroyed() -> RouteEndPlay() -> BeginPlay() 했으면 EndPlay() 호출 -> Component들의 EndPlay() -> RouteEndPlay() 중간 부분에서 World->RemoveNetworkActor(this) + 끝 부분에서 UninitializeComponents() -> DestroyActor() 끝 부분에서 "<Pending Kill>"






참조 사이트
1. https://dev.epicgames.com/documentation/ko-kr/unreal-engine/actors-in-unreal-engine
2. https://dev.epicgames.com/documentation/ko-kr/unreal-engine/unreal-object-handling-in-unreal-engine#%EC%9E%90%EB%8F%99%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0%EC%B4%88%EA%B8%B0%ED%99%94
3. https://www.unrealengine.com/ko/blog/unreal-property-system-reflection
4. http://heukkellsoft.net/HeukKellLab/Page/UnrealEngine/Archietecture/UObject/Concept/ObjectSystem/Page.html
5. https://minusi.tistory.com/entry/%EC%96%B8%EB%A6%AC%EC%96%BC-UPROPERTY-Unreal-UPROPERTY

profile
드가자

0개의 댓글