언리얼 C++ 프로그래밍

[언리얼 C++] 언리얼 오브젝트 처리 (1)

언린이 2021. 7. 17. 18:38

언리얼 엔진에는 내부적으로 게임 오브젝트를 처리하는 견고한 시스템이 존재합니다.

이러한 오브젝트 처리 시스템의 도움을 받아 더욱 쉽고 간편하게 오브젝트를 관리할 수 있습니다.

제작한 클래스에서 오브젝트 처리 시스템의 기능들을 사용할려면, 시스템이 해당 클래스를 인식하게 해서 접근할 수 있도록 만들어줘야 합니다.

클래스는 UCLASS() 매크로, 프로퍼티는 UPROPERTY() 매크로, 함수는 UFUNCTION() 매크로를 사용하여 선언을 해주면 오브젝트 처리 시스템에서 인식하여 접근할 수 있게 됩니다.

이제 오브젝트 처리 시스템이 오브젝트에 접근하여 수행하는 여러가지 기능들에 대해 설명하도록 하겠습니다.

 

 

1. 자동 프로퍼티 초기화

UObject는 생성자 호출 전 초기화시 자동으로 0으로 채워집니다. 클래스, UProperty, 네이티브 멤버 모두 전체적으로 동일하게 처리합니다. 그 이후 멤버는 클래스 생성자의 커스텀 값으로 초기화가 가능합니다.

 

 

2. 레퍼런스 자동 업데이트

AActor 또는 UActorComponent가 소멸되거나 다른 식으로 플레이에서 제거되면, 리플렉션 시스템에 보이고 있는 그에 대한 모든 레퍼런스는 자동으로 null이 됩니다. 이는 허상 참조를 예방하여 문제의 소지를 줄인다는 장점이 있지만, 다른 코드 부분에서 AActor와 UActorComponent 포인터를 소멸시키는 경우 null이 된다는 것을 뜻하기도 합니다. 여기서 최고의 장점은 null 검사 안전성이 높다는 것인데, 일반적인 null 포인터의 경우와 null이 아닌 포인터가 삭제된 메모리를 가리키는 경우 둘 다 감지해내기 때문입니다.

 

여기서 한가지 중요한 점은, 이 기능은 UPROPERTY로 마킹되어 있거나 언리얼 엔진 컨테이너 클래스에 저장된 AActor 또는 UActorComponent 레퍼런스에만 적용된다는 점입니다. raw 포인터에 저장된 오브젝트 레퍼런스는 언리얼 엔진이 알지 못하기 때문에, 자동으로 null이 되거나, 가비지 컬렉션이 방지되지 않습니다. 그렇다고 모든 UObject* 변수가 UProperty가 되어야 한다는 뜻은 아닙니다. UProperty가 아닌 오브젝트 포인터가 필요한 경우, TWeakObjectPtr 사용을 고려해 보시기 바랍니다. 이는 약 포인터로, 가비지 컬렉션을 방지하지는 않지만, 접근 전 질의를 통해 유효성 검사가 가능하며, 거기서 가리키는 오브젝트가 소멸된 경우 null 설정도 가능합니다.

 

참조된 UObject UProperty가 자동으로 null이 되는 또 한가지 경우는, 에디터에서 애셋을 강제 삭제한 경우입니다. 그에 따라, 애셋인 UObject에 대한 모든 코드 작업시 이 포인터가 null이 되도록 처리해야 합니다.

 

 

3. Serialization

UObject가 Serialize될때, 모든 UProperty 값은 명시적으로 휘발성 마킹 또는 생성자 이후 기본값에서 미변경 상태가 아닌 이상 자동으로 읽고 쓰기가 가능합니다. 예를 들어 레벨에 AEnemy 인스턴스를 배치하고서, 그 Health를 500으로 설정하고 저장을 하면, UClass 정의 이외의 코드를 한줄도 작성하지 않고도 다시 로드할 수 있습니다.

UProperty가 추가 또는 제거될때, 기존 컨텐츠 로드는 매끄럽게 처리됩니다. 새 프로퍼티는 새 CDO에서 기본값을 복사해 옵니다. 제거된 프로퍼티는 말없이 무시됩니다.

커스텀 작동 방식이 필요한 경우, UObject::Serialize 함수를 덮어쓰면 됩니다. 데이터 오류, 버전 번호 검사, 데이터 포맷 변경시 자동 변환 또는 업데이트 수행 등에 유용하게 쓰일 수 있습니다.

 

 

4. 프로퍼티 값 업데이트하기

UClass의 클래스 디폴트 오브젝트(CDO)가 변경되면, 엔진은 그 클래스의 모든 인스턴스 로드시 알아서 변경사항을 적용합니다. 주어진 오브젝트 인스턴스에 대해, 업데이트된 변수값이 이전 CDO 값과 일치한다면, 새로운 CDO에 저장된 CDO 값으로 업데이트됩니다. 변수값이 다른 경우, 그 변수가 의도적으로 설정되었다 가정하여 그 변경사항을 보존합니다.

 

예를 들어 AEnemy 오브젝트를 여럿 배치했고, AEnemy 생성자의 Health 기본값을 100으로 설정한 레벨을 저장했다 칩시다. 여기서 또, Enemy_3이 특별히 쎈 놈이라 Health 값을 500으로 설정했다 가정합시다. 여기서 마음이 바뀌어 Health 기본값을 150으로 올렸다고 가정합니다. 이 상황에서, 다음 레벨을 로드할때 언리얼은 CDO가 변경되었다는 것을 알아채고 이전 기본 Health 값(100)인 AEnemy 모든 인스턴스의 Health 값을 150으로 업데이트합니다. Enemy_3의 Health 값은 500 그대로 남아있는데, 그 이유는 이전 CDO에 저장된 기본값이 아니기 때문입니다.