언리얼 엔진 공부하기 #11 [UE5]

신지한·2024년 6월 26일
0

개발노트

목록 보기
17/17
post-thumbnail

📢 개발노트에 앞서서

본 개발노트는 혼자서 언리얼 게임개발 독학하는 과정에서
공부한 내용들을 기록&공유하는 게시글이며
부족한점이 있을 수 있어 참고해서 봐주시면 감사하겠습니다

👉 이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해

❗ 본 게시글은 위 강의의 내용을 정리 및 실습한 게시글로 모든 내용은 대부분의 내용은 책이 출처입니다


📚 스터디 내용

10강에서는 언리얼 컨테이너 라이브러리중 Array와 Set 에 대해서 학습했습니다. 아래는 필기를 한 강의 노트입니다

📖 10강 필기 노트

언리얼 컨테이너 라이브러리

  • 언리얼 엔진이 자체 제작해 제공하는 자료구조 라이브러리
  • 안정적으로 지원하며, 다수 오브젝트 처리에 유용하게 사용됨
  • 다양한 자료구조 라이브러리를 직접 만들어 제공하고 있음

기존 C++ STL과 UCL의 차이점

  • C++ STL은 범용적, 호환성이 높음, 많은 기능이 엮여있어 컴파일 시간이 오래 걸림
  • UCL은 언리얼 엔진에 특화되어 있고, 언리얼 오브젝트를 안정적으로 지원, 가볍고 게임 제작에 최적화되어 있음

UCL 주요 컨테이너 라이브러리

  • TArray: 오브젝트를 순서대로 담아 효율적으로 관리하는 용도
  • TSet: 중복되지 않는 요소로 구성된 집합을 만드는 용도
  • TMap: 키, 벨류 조합의 레코르를 관리하는 용도로 사용

TArray 개요

  • TArray는 가변 배열 자료구조
  • vector와 동작 원리가 유사함
  • 게임 제작에서는 가변 배열 자료구조를 효과적을 활용하는 것이 좋음
    • 데이터가 순차적으로 모여있기 때문에 메모리를 효과적으로 사용할 수 있고 캐시 효율이 높다
    • 컴퓨터 사양이 좋아지면서, 캐시 지역성으로 인한 성능 향상은 굉장히 중요해짐
    • 임의 데이터의 접근이 빠르고, 고속으로 요소를 순회하는 것이 가능
  • 단점
    • 맨 끝에 데이터를 추가하는 것은 가볍지만, 중간에 요소를 추가하거나 삭제하는 작업은 비용이 큼
  • 데이터가 많아질 수록 검색, 삭제, 수정 작업이 느려지기 때문에, 많은 수의 데이터에서 검색 작업이 빈번하게 일어난다면 TArray대신 TSet을 사용하는 것이 좋음

TSet의 특징

  • STL set의 특징
    • 이진 트리로 구성되어 있어 정렬을 지원
    • 메모리 구성이 효율적이지 않음
    • 요소가 삭제될 때 균형을 위한 재구축
    • 모든 자료를 순회하는데 적합하지 않음
  • 언리얼 TSet 특징
    • 해시테이블 형태로 키 데이터가 구축되어 있어 빠른 검색 가능
    • 동적 배열의 형태로 데이터가 모여있음
    • 데이터는 빠르게 순회할 수 있음
    • 데이터는 삭제해도 재구축이 일어나지 않음
    • 자료에는 비어있는 데이터가 있을 수 있음
  • SET set과 언리얼 TSet의 활용 방법은 서로 다르기 때문에 주의
  • STL의 unordered_set과 유사하게 동작하지만 동일하지 않음
  • TSet은 중복 없는 데이터 집합을 구축하는데 유용하게 사용할 수 있음

11강에서는 언리얼 컨테이너 라이브러리인 구조체와 Map에 대해서 학습했습니다. 아래는 필기를 한 강의 노트입니다

📖 8강 필기 노트

언리얼 구조체 UStrcut

  • 데이터 저장/전송에 특화된 가벼운 객체
  • 대부분 GENERATED_BODY 매크로를 선언해준다
    • 리플렉션, 직렬화와 같은 유용한 기능을 지원함
    • GENERATED_BODY를 선언한 구조체는 UScriptStruct 클래스로 구현됨
    • 이 경우 제한적으로 리플렉션을 지원함, 속성 UPROPERTY만 선언할 수 있고 함수 UFUNCTION은 선언할 수 없음
  • 언리얼 엔진의 구조체 이름은 F로 시작함
    • 대부분 힙 메모리 할당(포인터 연산) 없이 스택 내 데이터로 사용됨
    • NewObject API 사용할 수 없음

TMap의 특징

  • STL map의 특징
    • 이진 트리로 구성되어 있음
    • 정렬은 지원하지만, 메모리 구성이 효율적이지 않으며, 데이터 삭제시 재구축이 일어날 수 있음
    • 모든 자료를 순회하는데 적합하진 않음
  • 언리얼 TMap의 특징
    • 키, 밸류 구성의 튜플 데이터의 TSet 구조로 구현되어 있음
    • 해시테이블 형태로 구축되어 있어 빠른 검색이 가능함
    • 동적 배열의 형태로 데이터가 모여있음
    • 데이터는 빠르게 순회할 수 있음
    • 데이터는 삭제해도 재구축이 일어나지 않음
    • 비어있는 데이터가 있을 수 있음
    • TMultiMap을 사용하면 중복 데이터를 관리할 수 있음
  • 동작 원리는 STL unordered_map과 유사함
  • 키, 밸류 쌍이 필요한 자료구조에 광범위하게 사용됨

👉 실습코드

// 구조체 선언
USTRUCT()
struct FStudentData
{
	GENERATED_BODY()
	
	FStudentData()
	{
		Name = TEXT("홍길동");
		Order = -1;
	}

	FStudentData(FString InName, int32 InOrder) : Name(InName), Order(InOrder) {}

	bool operator==(const FStudentData& InOther) const
	{
		return Order == InOther.Order;
	}

	friend FORCEINLINE uint32 GetTypeHash(const FStudentData& InStudentData)
	{
		return GetTypeHash(InStudentData.Order);
	}

	UPROPERTY()
	FString Name;

	UPROPERTY()
	int32 Order;
};

// TArray, TMap 선언
UCLASS()
class UNREALCONTAINER_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
	
public:
	virtual void Init() override;

private:
	TArray<FStudentData> StudentsData;

	UPROPERTY()
	TArray<TObjectPtr<class UStudent>> Students;

	TMap<int32, FString> StudentsMap;
};

// MyGameInstance에서 활용한 코드
void UMyGameInstance::Init()
{
	Super::Init();

	const int32 ArrayNum = 10;
	TArray<int32> Int32Array;

	for (int32 ix = 1; ix <= ArrayNum; ++ix)
	{
		Int32Array.Add(ix);
	}

	Int32Array.RemoveAll(
		[](int32 Val)
		{
			return Val % 2 == 0;
		}
	);

	Int32Array += {2, 4, 6, 8, 10};

	TArray<int32> Int32ArrayCompare;
	int32 CArray[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 10 };
	Int32ArrayCompare.AddUninitialized(ArrayNum);
	FMemory::Memcpy(Int32ArrayCompare.GetData(), CArray, sizeof(int32) * ArrayNum);

	ensure(Int32Array == Int32ArrayCompare);

	int32 Sum = 0;
	for (const int32& Int32Elem : Int32Array)
	{
		Sum += Int32Elem;
	}

	int32 SumByAlgo = Algo::Accumulate(Int32Array, 0);
	ensure(Sum == SumByAlgo);

	TSet<int32> Int32Set;
	for (int32 ix = 1; ix <= ArrayNum; ++ix) 
	{
		Int32Set.Add(ix);
	}

	Int32Set.Remove(2);
	Int32Set.Remove(4);
	Int32Set.Remove(6);
	Int32Set.Remove(8);
	Int32Set.Remove(10);
	Int32Set.Add(2);
	Int32Set.Add(4);
	Int32Set.Add(6);
	Int32Set.Add(8);
	Int32Set.Add(10);

	const int32 StudentNum = 300;
	for (int32 ix = 1 ; ix <= StudentNum; ++ix)
	{
		StudentsData.Emplace(FStudentData(MakeRandomName(), ix));
	}

	TArray<FString> AllStudentsNames;
	Algo::Transform(StudentsData, AllStudentsNames,
		[](const FStudentData& Val)
		{
			return Val.Name;
		}
	);

	UE_LOG(LogTemp, Log, TEXT("모든 학생 이름의 수 : %d"), AllStudentsNames.Num());

	TSet<FString> AllUniqueNames;
	Algo::Transform(StudentsData, AllUniqueNames,
		[](const FStudentData& Val)
		{
			return Val.Name;
		}
	);

	UE_LOG(LogTemp, Log, TEXT("중복 없는 학생 이름의 수 : %d"), AllUniqueNames.Num());

	Algo::Transform(StudentsData, StudentsMap,
		[](const FStudentData& Val)
		{
			return TPair<int32, FString>(Val.Order, Val.Name);
		}
	);

	UE_LOG(LogTemp, Log, TEXT("순번에 따른 학생 맵의 레코드 수 : %d"), StudentsMap.Num());

	TMap<FString, int32> StudentsMapByUniqueName;
	Algo::Transform(StudentsData, StudentsMapByUniqueName,
		[](const FStudentData& Val)
		{
			return TPair<FString, int32>(Val.Name, Val.Order);
		}
	);

	UE_LOG(LogTemp, Log, TEXT("이름에 따른 학생 맵의 레코드 수 : %d"), StudentsMapByUniqueName.Num());


	TMultiMap<FString, int32> StudentMapByName;
	Algo::Transform(StudentsData, StudentMapByName,
		[](const FStudentData& Val)
		{
			return TPair<FString, int32>(Val.Name, Val.Order);
		}
	);

	UE_LOG(LogTemp, Log, TEXT("이름에 따른 학생 멀티맵의 레코드 수 : %d"), StudentMapByName.Num());
	

	const FString TargetName(TEXT("이혜은"));
	TArray<int32> AllOrders;
	StudentMapByName.MultiFind(TargetName, AllOrders);

	UE_LOG(LogTemp, Log, TEXT("이름에 %s인 학생 수 : %d"), *TargetName, AllOrders.Num());

	TSet<FStudentData> StudentsSet;
	for (int32 ix = 1; ix <= StudentNum; ++ix)
	{
		StudentsSet.Emplace(FStudentData(MakeRandomName(), ix));
	}
}

😅 강의 들으면서 필기한거라 급하게 작성해서 내용이 중구난방일 수 있습니다..!

profile
게임 개발자

0개의 댓글