본 개발노트는 혼자서 언리얼 게임개발 독학하는 과정에서
공부한 내용들을 기록&공유하는 게시글이며
부족한점이 있을 수 있어 참고해서 봐주시면 감사하겠습니다
👉 이득우의 언리얼 프로그래밍 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));
}
}