C++를 사용한 언리얼 엔진을 학습하던 중, 오브젝트 생성 시 UObject::CreateDefaultObject, New Object<T>, UWorld::SpawnActor<T> 와같은 각기 다른 호출방식이 있었습니다. 이에 대해 어떠한 상황에서 해당 함수들을 호출하는지 학습하고 기록하고자 이번 포스팅을 하게 되었습니다. 또한, 생성자 호출 시 추가적으로 어떠한 과정이 이루어지는지에 대해 알아보던중 CDO를 알게되어 추가적으로 작성하게 되었습니다. 

 

CDO(Class Default Object)

CDO는 엔진이 실행되며 클래스가 로드 될 때 초기화, 생성되는 인스턴스화 되지 않은 기본값을 가지고 있는 객체입니다.

  • C++이 제공하지 않는 언리얼만의 추가 기능들을 제공한다.
  • UClass 클래스에서 추가기능(메타정보)들을 보관한다.
  • 메모리에 한번만 존재하며, 클래스가 로드될 때 자동으로 생성된다.
  • 언리얼 오브젝트를 생성할 때 매번 초기화 하지않고 CDO를 복제해서 사용한다.
    • 객체 생성시 초기값에 대한 일관성 유지 및 재사용으로 인한 생성 비용 감소

 

CDO 생성 및 기본 프로퍼티 값 확인 예시 코드

// AMyCharacter 클래스의 CDO에 접근
AMyCharacter* DefaultCharacter = AMyCharacter::StaticClass()->GetDefaultObject<AMyCharacter>();

// 기본 프로퍼티 값 확인
float DefaultHealth = DefaultCharacter->Health;

 

 

 

CreateDefaultSubObject

  • 클래스의 서브 객체(Ex : Compoenet)들을 생성하고 초기화 하는데 사용한다.
  • 클래스 생성자에서 호출된다.
    • BeginPlay()와 같은 생성자가 아닌 곳에서 호출 시 동적할당으로 생성하게되어 에러가 발생한다.
  • 언리얼 엔진에서 메모리를 관리한다.
  • 생성된 객체는 에디터 단계에서 조작할 수 있다.
// Camera Component와 SpringArm Component를 캐릭터의 컴포넌트로 추가
FPSCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
FPSSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));

 

 

 

 

NewObject

  • 동적으로 객체를 생성할 경우 사용된다.
  • 런타임 단계에서 객체를 생성하고 싶을시에 사용한다.
  • 생성자 단계에서 생성하지 않는 객체들의 생성을 위해 일반적으로 사용된다.
// 런타임 환경에서 객체를 생성하고 싶을 때
// 생성자 이후 객체를 생성 시
UDynamicCreateObject* DCObject = NewObject<UDynamicCreateObject>(this, UDynamicCreateObject::StaticClass());

 

 

 

SpawnActor

  • 액터 객체를 월드에서 동적으로 생성하는 함수
    • 생성자 단계 이후에서 생성해야한다.
  • Transform 정보를 가지는 액터를 스폰하기 위해 사용된다.
  • 액터 라이프사이플에 따른 관리를 받는다.
// 런타임 시 월드에서 액터 스폰
FVector Location(0, 0, 100);
FRotator Rotation(0, 0, 0);
AMonsterActor* SpawnedActor = GetWorld()->SpawnActor<AMonsterActor>(AMonsterActor::StaticClass(), Location, Rotation);

 

 

 

정리

함수 역할 사용 예 주 용도
CreateDefaultSubobject 클래스의 서브 객체(컴포넌트 등) 생성 CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); 주로 클래스 내부에서 컴포넌트를 생성하고 기본값 설정
NewObject 동적 객체 생성 NewObject<UMyCustomObject>(this, UMyCustomObject::StaticClass()); 런타임에서 새로운 UObject 객체 생성, 주로 데이터 객체 생성
SpawnActor 월드에서 액터 객체 동적 생성 SpawnActor<AMyActor>(AMyActor::StaticClass(), Location, Rotation); 월드에 실제로 존재하는 액터 객체 생성, 게임에서 상호작용 가능

힙(Heap)

완전 이진트리의 자료구조로 값의 최대 힙과 최소 힙의 유형을 가지고 있습니다.

삽입, 삭제의 시간복잡도는 O(log N)을 가지고 있습니다.

 

힙 정렬

주어진 데이터를 힙(Heap)을 사용하여 정렬하는 알고리즘입니다.

  • 최대 힙(Max Heap) : 내림차순 정렬
  • 최소 힙(Min Heap) : 오름차순 정렬

힙정렬 과정 - https://taaewoo.tistory.com/6

 

 

힙 정렬 과정

1. 리스트의 원소들을 모두 힙에 넣는다.

2. 힙의 루트 원소를 뽑아 정렬시킬 배열에 놓고 힙에서는 제거한다.

3. 2번의 과정을 재귀적으로 오름차순 또는 내림차순으로 정렬될때까지 반복한다.

 

특징 및 장단점

  • 제자리 정렬 (In-Place Sorting) : 추가적인 메모리 공간을 필요로 하지 않으며, 주어진 배열에서 바로 정렬을 수행한다.
  • 데이터를 비교하며 정렬을 수행한다.
  • 최선,평균,최악의 시간 복잡도는 O(log n)으로 일정한 시간복잡도를 보장한다.
  • 정렬하는 원소가 동일한 값일 경우, 순서를 보장하지 않는다.

 

힙정렬 구현

// Heap (완전 이진 트리 형태)
// 정렬할 배열 , 배열의 크기, 루트가 될 원소
void heapify(int arr[], int n, int i)
{
	int largest = i;
	int left = 2 * i + 1;
	int right = 2 * i + 2;

	// (right < n && arr[left] < arr[smallest]) -> 최소 힙
	if (left < n && arr[left] > arr[largest])
	{
		largest = left;
	}

	// (right < n && arr[right] < arr[smallest] -> 최소 힙
	if (right < n && arr[right] > arr[largest])
	{
		largest = right;
	}

	if (largest != i) {
		std::swap(arr[i], arr[largest]);
		heapify(arr, n, largest);
	}
}

// 힙정렬
void heapSort(int arr[], int n)
{
	for (int i = n/2 - 1; i >= 0; i--)
	{
		heapify(arr, n, i);
	}

	for (int i = n - 1; i > 0; i--)
	{
		std::swap(arr[0], arr[i]);
		heapify(arr, i, 0);
	}
}

 

HeapSort - 정렬 결과

 

 


 

병합 정렬 (Merge Sort)

  • 리스트의 원소들을 재귀적으로 반으로 나누고, 각 부분들을 정렬한 후 병합(Merge) 과정을 통해 다시 합치는 방식
    • 분할정복 알고리즘
  • 두 개의 정렬된 리스트를 합쳐서 하나의 정렬된 리스트로 만드는 과정

 

병합정렬 분할 / 정복 과정- https://anweh.tistory.com/85

 

 

특징 및 장단점

  • 분할정복 알고리즘
  • 동일한 값에대한 순서를 보장한다.
  • 리스트를 분할하고 병합하는 단계에서 병렬 처리에 유용하다.
  • 최선,최악,평균 시간복잡도 O(n lon n)을 유지한다.
  • 정렬시 추가적인 메모리 공간이 O(n)만큼 필요하다.

 

병합정렬 구현

// 병합
void merge(std::vector<int>& arr, int left, int right)
{
	if (left >= right) return;

	int mid = left + (right - left) / 2;

	merge(arr, left, mid);
	merge(arr, mid + 1, right);

	// 병합 단계
	int n1 = mid - left + 1;	// 왼쪽 크기
	int n2 = right - mid;		// 오른쪽 크기

	// 임시배열. 공간복잡도 O(n)이 추가적으로 필요한 이유
	std::vector<int> leftArr(n1), rightArr(n2);

	// 값 복사
	for (int i = 0; i < n1; i++)
	{
		leftArr[i] = arr[left + i];
	}

	for (int i = 0; i < n2; i++)
	{
		rightArr[i] = arr[mid + 1 + i];
	}

	// 두 배열 병합
	int i = 0, j = 0, k = left;
	while (i < n1 && j < n2)
	{
		if (leftArr[i] <= rightArr[j])
		{
			arr[k] = leftArr[i];
			i++;
		}
		else
		{
			arr[k] = rightArr[j];
			j++;
		}
		k++;
	}

	// 왼쪽 배열의 나머지 항목 복사
	while (i < n1) 
	{
		arr[k] = leftArr[i];
		i++;
		k++;
	}

	// 오른쪽 배열의 나머지 항목 복사
	while (j < n2)
	{
		arr[k] = rightArr[j];
		j++;
		k++;
	}
}

// 병합정렬 (분할)
void mergeSort(std::vector<int>& arr, int left, int right) {
	if (left < right)
	{
		int mid = left + (right - left) / 2;	// 중간값 계산

		mergeSort(arr, left, mid);				// 왼쪽 절반
		mergeSort(arr, mid + 1, right);			// 나머지 오른쪽 절반

		merge(arr, left, right);				// 마지막 병합
	}
}

MergeSort - 정렬 결과

 

Matrix

Vector로 이루어진 직사각형 배열로, 행,m(row), 열,n(column)의 수를 나타냅니다.

3D그래픽스 환경에서 4x4행렬을 사용하여 객체의 Transform관련 변환을 하는데 사용합니다.

2D그래픽스 환경에서는 3x3행렬을 사용하여 객체의 Transform 관련 변환을 하는데 사용합니다.

이번 포스팅에서는 행렬 연산에 대해 알아보겠습니다.

 

1. 행렬 곱셈

  • 두 개의 행렬을 결합하여 새로운 행렬을 생성하는 연산
  • 행렬 곱셈 규칙 : A * B
    • B*A와 값이 다르다. (교환 법칙이 성립이 안된다.)
  • (앞 행렬의 행의 수) * (뒤 행렬의 열의 수)

 

2. 대각행렬 (Diagonal Matrix)

  • 대각선 이외의 값들을 0으로 설정한다.
  • 크기 조절(Scaling)을 할 시 사용한다.
	FMatrix ScaleMatrix = FMatrix(
		FPlane(2.0f, 0.0f, 0.0f, 0.0f),	// X축 Scale
		FPlane(0.0f, 2.0f, 0.0f, 0.0f),	// Y축
		FPlane(0.0f, 0.0f, 2.0f, 0.0f),	// Z축
		FPlane(0.0f, 0.0f, 0.0f, 1.0f)	// Scale 설정, Default = 1.f
	);

	FVector BeforeVector(1.0f, 1.0f, 1.0f);
	FVector ScaledVector = ScaleMatrix.TransformPosition(BeforeVector);

	// 대각 행렬 Matrix 연산 전
	UE_LOG(LogTemp, Log, TEXT("Before Vector : %s"), *BeforeVector.ToString());
	
	// 대각 행렬 Matrix 연산 후
	UE_LOG(LogTemp, Log, TEXT("Scaled Vector : %s"), *ScaledVector.ToString());

 

대각행렬 연산 로그

 

3. 단위행렬 (Identity Matrix)

  • 변환을 수행하지 않는 기본행렬
  • 초기화 시에 사용한다.

단위 행렬 (Identity Matrix)

	FMatrix IdentityMatrix = FMatrix::Identity;
	
	UE_LOG(LogTemp, Log, TEXT("Identity Matrix : %s"), *IdentityMatrix.ToString());

단위행렬

 

 

4. 역행렬 (Inverse Matrix)

  • 변환했던 행렬을 반대로 취소하는 데 사용한다.
    • Ex) 특정 위치에서 이동 후 다시 원래 위치로 돌아갈 때

역행렬 공식

	FMatrix TranslationMatrix = FMatrix::Identity;
	TranslationMatrix = FTranslationMatrix(FVector(10.0f, 0.0f, 0.0f));

	FVector InverseBeforeVector(1.0f, 2.0f, 3.0f);	// 1.0f, 2.0f, 3.0f
	FVector TranslationVector = TranslationMatrix.TransformPosition(InverseBeforeVector); // 11.0f, 2.0f, 3.0f

	// 역행렬
	FMatrix InverseMatrix = TranslationMatrix.Inverse();	// TranslationMatrix의 역행렬
	FVector InverseTranslationVector = InverseMatrix.TransformPosition(TranslationVector);	// 1.0f, 2.0f, 3.0f

	UE_LOG(LogTemp, Log, TEXT("InverseBefore Vector : %s"), *InverseBeforeVector.ToString());
	UE_LOG(LogTemp, Log, TEXT("Translation Vector : %s"), *TranslationVector.ToString());
	UE_LOG(LogTemp, Log, TEXT("InverseTranslation Vector : %s"), *InverseTranslationVector.ToString());

역행렬 연산 로그

 

 

5. 전치행렬 (Transpose Matrix), 직교행렬(Orthogonal Matrix)

  • 행과 열을 대각선 축을 기준으로 서로 위치를 바꾼 행렬
  • mxn의 전치행렬 : nxm
  • 직교행렬 x 전치행렬 = 단위행렬 (전치행렬의 역행렬)
	// 전치행렬
	FMatrix NormalMatrix = FMatrix(
		FPlane(1.0f, 0.0f, 0.0f, 0.0f),
		FPlane(0.0f, 2.0f, 0.0f, 0.0f),
		FPlane(0.0f, 0.0f, 3.0f, 0.0f),
		FPlane(0.0f, 0.0f, 0.0f, 4.0f)
	);

	FMatrix TransposedMatrix = NormalMatrix.GetTransposed();

	FVector NormalVector(1.0f, 2.0f, 3.0f);
	FVector TransposedVector = TransposedMatrix.TransformPosition(NormalVector);

	UE_LOG(LogTemp, Log, TEXT("Normal Vector: %s"), *NormalVector.ToString());
	UE_LOG(LogTemp, Log, TEXT("Transposed Vector: %s"), *TransposedVector.ToString());

전치행렬 연산 로그

 

 

6 변환 행렬 (Transformation Matrix)

  • 위치, 회전, 크기 조정을 한번에 처리하는 복합  변환을 구현할 때 사용한다.
  • Scaling, Rotation, Translation, Relection Matrix
  • 각각 x,y,z축을 세타 만큼 회전한다.

 

	FMatrix TranslationMatrix = FTranslationMatrix(FVector(10.0f, 0.0f, 0.0f));  // X축으로 10만큼 이동
	FRotator Rotation(0.0f, 90.0f, 0.0f);  // Y축으로 90도 회전
	FMatrix RotationMatrix = FRotationMatrix(Rotation);
	FMatrix ScaleMatrix = FMatrix(
		FPlane(2.0f, 0.0f, 0.0f, 0.0f),
		FPlane(0.0f, 2.0f, 0.0f, 0.0f),
		FPlane(0.0f, 0.0f, 2.0f, 0.0f),
		FPlane(0.0f, 0.0f, 0.0f, 1.0f) 
	);

	FMatrix TransformMatrix = TranslationMatrix * RotationMatrix * ScaleMatrix;  // 변환 행렬 결합

	FVector TransBeforeVector(1.0f, 1.0f, 1.0f);
	FVector TransformedVector = TransformMatrix.TransformPosition(TransBeforeVector);

	UE_LOG(LogTemp, Log, TEXT("Transform Before Vector: %s"), *TransBeforeVector.ToString());
	UE_LOG(LogTemp, Log, TEXT("Transformed Vector: %s"), *TransformedVector.ToString());

변환행렬 연산 로그

'게임수학' 카테고리의 다른 글

게임수학 - 삼각함수  (1) 2025.01.21
게임수학 - 왼손좌표계, 오른손좌표계  (1) 2025.01.02
게임수학 - Vector(기하)  (0) 2024.12.31

+ Recent posts