게임 개발/언리얼 C++

언리얼C++ - 기본타입과 문자열

싹난 감자 2024. 11. 10. 17:32

왜 언리얼은 기본 타입을 따로 지정하는가?

게임 개발은 단일 컴퓨터에서 최대 퍼포먼스를 뽑아내야함. 또한 네트워크 상에서 데이터 통신이 효율적이고 안정적이여야함.

캐시 히트율 등을 신경써서 개발해야하는데 플랫폼 파편화로 인해 같은 int형이어도 최소 32비트를 보장하더라도 크기를 확신할 수 없음

데이터 타입의 모호함은 개발 시 문제를 일으킬 수 있음

후발 언어인 C#의 경우 int타입이 4바이트, 32비트로 명확히 정의되어 있음

위의 이유들로 인해 언리얼은 int가 아닌 int32를 사용


언리얼 엔진의 기본 데이터 타입과 크기

bool- boolean값 (bool크기 추정 금지)

TCHAR - charactor (TCHAR크기 추정 금지)

uint8 - unsigned byte (1바이트)

int8 - signed bytet (1바이트)

uint16 - unsigned “short” (2바이트)

int16 - signed “short” (2바이트)

uint32 - unsigned int (4바이트)

int32 - signed int (4바이트)

uint64 - unsigned “quad word” (8바이트)

int64 - signed “quad word” (8바이트)

float - single precision floating point (4바이트)

double - double precision floating point (4바이트)

PTRINT - 포인터를 가질 수 있는 integer (PTRINT크기 추정 금지)


bool 타입의 선언

bool 타입은 크기가 명확하지 않음.

데이터를 저장하는데 있어서의 고려사항.

헤더에는 가급적 bool 대신 uint8타입을 사용하되 Bit Field 오퍼레이터를 사용하여 크기 제한.

일반 uint8과 구분을 위해 b접두사 사용

UPROPERTY()
uint8 bNetTemporary:1;

헤더가 아닌 cpp 로직에서는 자유롭게 bool 사용

텍스트 파일 인코딩

문자열 처리의 종류는 다양하나 언리얼에서는 TCHAR로 모든 문자열을 처리

ASCII, UTF-8, UTF-16 등의 타입에 신경쓸 필요없이 TCHAR를 사용

언리얼은 유니코드로 문자열 처리를 통일하며 그 중 2byte로 사이즈가 균일한 UTF-16을 사용

TCHAR 는 유니코드를 위한 언리얼 표준 Charactor타입.

소스 코드의 경우 UTF-8 사용을 권장


문자열 출력

TCHAR LogCharArray[] = TEXT("Hello Unreal");
UE_Log(LogTemp, Log, LogCharArray);

문자열은 언제나 TEXT매크로를 사용하여 지정.

TEXT 매크로로 감싼 문자열은 TCHAR 배열이다.

문자열에 대한 다양한 조작이 필요한 경우 TCHAR 배열이 아닌 FString을 사용.

FSring LogCharString = LogCharArray; //FString 안에 문자열 데이터가 포함됨
UE_Log(LogTemp, Log, TEXT("%s"), *LogCharString); //UE_Log의 세번째 구문에는 배열만 들어감

%s에 대응될 때에는 포인터 어레이를 반환해야하는데 FString을 그대로 쓰면 TCHAR 포인터가 반환되지 않기 때문에 포인터 연산자를 지정해줘야함

항상 FString 앞에 포인터 연산자를 붙여줘야 FString이 감싸고 있는 실제 문자열 데이터를 가져올 수 있음


FString의 구조와 활용

TEXT(”Hello”) →

  • TCHAR배열[] → TCHAR 동적배열(TArray) ↔ FCString
  • FCString ↔ TCHAR 동적배열(TArray) → TCHAR 포인터
    • TEXT매크로로 문자열을 선언하면 TCHAR배열로 만들어짐
    • TCHAR배열을 FString에 넣는 순간 TArray라는 언리얼의 동적 배열 클래스 방식으로 문자열이 보관
    • 이 실제 데이터를 꺼낼 때는 동적 배열이 속하고 있는 내부 자료에 대한 포인터를 가져옴(dereferencing)
    • 그래서 포인터 연산자를 FStirng 앞에 써주면 FString에 포함하고 있는 동적 배열 TArray에, TArray가 포함하고 있는 첫 번째 인자의 포인터를 반환해줌

FString은 보관, FCString은 문자열 처리.

다른 타입에서 FString으로 변환

  • FString::Printf
  • FString::SanitizeFloat
  • FString::FromInt

C런타임 수준에서 문자열을 처리하는 래퍼 클래스 FCStrirng

  • 문자열을 찾는 strstr 사용
  • 이 외의 문자열 슬라이싱 등 문자열에 대한 처리

FString에서 다른 타입으로 변환 (안전하진 않음)

  • FCString::Atoi
  • FCString::Atof

FStirng 예제

포인터를 끄집어내어 문자열 복사

TCHAR LogCharArray[] = TEXT("Hello Unreal");
FSring LogCharString = LogCharArray; //FString 안에 문자열 데이터가 포함됨

const TCHAR* LogCharPtr = *LogCharString; //const로 문자열복사
TCAHR* LogCharDataPtr = LogCharString.GetCharArray().GetData(); //CharArray를 직접 가져와 GetData
//FString에 직접 접근하여 수정할 경우
//TArray의 첫번째 인자인 포인터를 가져오기

//다시 배열로 가져올 경우
TCHAR LogCharArrayWithSize[100]; //배열 선언 후
FCString::StrCpy(LogCharArrayWithSize, LogCharString.Len(), *LogCharString) //저수준 스트링 복사

//텍스트 존재여부 검사
if(LogCharString.Contains(TEXT("unreal"), ESearchCase::IgnoreCase)) //대소문자 구분 없이 "unreal" 단어 검색
{
	int32 index = LogCharString.Find(TEXT("unreal"), ESearchCase::IgnoreCase); //"unreal"단어의 위치 검색
	FString EndString = LogCahrString.Mid(index); //"unreal"이 시작되는 위치부터 끝까지 자르기
	UE_Log(LogTemp, Log, TEXT("Find Text: %s"), *EndString);
}

//문자열 나누기
FString Left, Right;
if (LogCharString.Split(TEXT(" "), &Left, &Right)) //공백을 기준으로 문자열 나누기
{
	UE_Log(LogTemp, Log, TEXT("Split Test: %s 와 %s"), *Left, *Right); //출력결과 한글 깨짐
	//윈도우에서 한글을 사용하면 CP949형태의 멀티 바이트 스트링으로 저장됨
	//언리얼은 UTF-16을 사용하기 때문에 한글이 깨짐. 인코딩 옵션 변경해줘야함
}

int32 IntValue = 32;
float FloatValue = 3.141592;

//문자열로 변환
FString FloatIntString = FString::Printf(TEXT("Int: %d, Float:%f"), IntValue, FloatValue);
FString FloatString = FString::SanitizeFloat(FloatValue);
FString IntString = FString::FromInt(IntValue);

UE_Log(LogTemp, Log, TEXT("%s"), *FloatIntString);
UE_Log(LogTemp, Log, TEXT("Int: %s, Float:%f"), *IntString, *FloatIntString);

//문자열에서 변환
int32 IntValueFromString = FCString::Atoi(*IntString);
float FloatValueFromString = FCString::Atof(*FloatString);
FString FloatIntString2 = FString::Printf(TEXT("Int: %d, Float:%f"), IntValueFromString, FloatValueFromString);
UE_Log(LogTemp, Log, TEXT("%s"), *FloatIntString2);

다른 이름으로 저장, 인코딩 옵션을 UTF-8로 변경하면 한글이 정상적으로 출력됨


FName와 FText

FName: 에셋 관리를 위해 사용되는 문자열 체계

문자열을 사용하지만 Key,Value로 저장되어 빠르게 원하는 에셋을 찾을 수 있게 함

  • 대소문자 구분 없음
  • Key로 저장되어 한번 선언되면 바꿀 수 없음. int로 변환됨
  • 가볍고 빠름
  • 문자를 표현하는 용도가 아닌 에셋 키를 지정하는 용도로 사용, 빌드 시 해시값으로 변환됨

FText: 다국어 지원을 위한 문자열 관리 체계 (UI등)

  • 일종의 키로 작용
  • 별도의 문자열 테이블 정보가 추가로 요구됨
  • 게임 빌드 시 자동으로 다양한 국가별 언어로 변환됨

FString을 사용해 문자열을 관리하지만 FName이나 FText로 변환해 다양한 용도로 사용 가능하다.

FName의 구조와 활용

  • 언리얼은 FName과 관련된 글로벌 Pool 자료구조를 가지고 있음
  • FName과 글로벌 Pool
    • 문자열이 들어오면 해시 값을 추출해 키를 생성하고 FName에서 보관
    • FName 값에 저장된 값을 사용해 전역 Pool(싱글톤)에서 원하는 자료를 검색해 반환
    • 문자 정보는 대소문자를 구분하지 않고 저장
  • FName의 형성
    • 생성자에 문자열 정보를 넣으면 Pool을 조사하여 적당한 키로 변환하는 작업이 수반됨
    • Only Find or Add

“HELLO” → FName → FNamePool(Key, Value)

FName Key1(TEXT("PELVIS"));
FName Key2(TEXT("pelvis"));
UE_Log(LogTemp, Log, TEXT("FName 비교 결과: %s"), Key1 == Key2 ? TEXT("같음") : TEXT("다름"));
///FName은 대소문자를 구분하지 않으므로 같음

for(int i = 0; i<10000; ++i)
{
	FName SearchInFNameFool = FName(TEXT("pelvis"));
	//FName은 문자열을 Key로 변환한 뒤 해당 Key가 전역Pool에 있는지 조사하는 작업을 거치기 때문에
	//빈번하게 작업이 발생할 경우(tick) 오버헤드가 발생할 수 있음
	//조사해서 FName에 관련 Key값만 저장되면 되기 때문에 가급적 그 전에 한번 선언하거나
	const static FName StaticOnlyOnce(TEXT"pelvis");
	//local static으로 선언하여 처음 초기화할 때 데이터를 저장하고 한번만 처리하게 해주는 것이 좋다
}

왜 언리얼은 기본 타입을 따로 지정하는가?

게임 개발은 단일 컴퓨터에서 최대 퍼포먼스를 뽑아내야함. 또한 네트워크 상에서 데이터 통신이 효율적이고 안정적이여야함.

캐시 히트율 등을 신경써서 개발해야하는데 플랫폼 파편화로 인해 같은 int형이어도 최소 32비트를 보장하더라도 크기를 확신할 수 없음

데이터 타입의 모호함은 개발 시 문제를 일으킬 수 있음

후발 언어인 C#의 경우 int타입이 4바이트, 32비트로 명확히 정의되어 있음

위의 이유들로 인해 언리얼은 int가 아닌 int32를 사용


언리얼 엔진의 기본 데이터 타입과 크기

bool- boolean값 (bool크기 추정 금지)

TCHAR - charactor (TCHAR크기 추정 금지)

uint8 - unsigned byte (1바이트)

int8 - signed bytet (1바이트)

uint16 - unsigned “short” (2바이트)

int16 - signed “short” (2바이트)

uint32 - unsigned int (4바이트)

int32 - signed int (4바이트)

uint64 - unsigned “quad word” (8바이트)

int64 - signed “quad word” (8바이트)

float - single precision floating point (4바이트)

double - double precision floating point (4바이트)

PTRINT - 포인터를 가질 수 있는 integer (PTRINT크기 추정 금지)


bool 타입의 선언

bool 타입은 크기가 명확하지 않음.

데이터를 저장하는데 있어서의 고려사항.

헤더에는 가급적 bool 대신 uint8타입을 사용하되 Bit Field 오퍼레이터를 사용하여 크기 제한.

일반 uint8과 구분을 위해 b접두사 사용

UPROPERTY()
uint8 bNetTemporary:1;

헤더가 아닌 cpp 로직에서는 자유롭게 bool 사용

텍스트 파일 인코딩

문자열 처리의 종류는 다양하나 언리얼에서는 TCHAR로 모든 문자열을 처리

ASCII, UTF-8, UTF-16 등의 타입에 신경쓸 필요없이 TCHAR를 사용

언리얼은 유니코드로 문자열 처리를 통일하며 그 중 2byte로 사이즈가 균일한 UTF-16을 사용

TCHAR 는 유니코드를 위한 언리얼 표준 Charactor타입.

소스 코드의 경우 UTF-8 사용을 권장


문자열 출력

TCHAR LogCharArray[] = TEXT("Hello Unreal");
UE_Log(LogTemp, Log, LogCharArray);

문자열은 언제나 TEXT매크로를 사용하여 지정.

TEXT 매크로로 감싼 문자열은 TCHAR 배열이다.

문자열에 대한 다양한 조작이 필요한 경우 TCHAR 배열이 아닌 FString을 사용.

FSring LogCharString = LogCharArray; //FString 안에 문자열 데이터가 포함됨
UE_Log(LogTemp, Log, TEXT("%s"), *LogCharString); //UE_Log의 세번째 구문에는 배열만 들어감

%s에 대응될 때에는 포인터 어레이를 반환해야하는데 FString을 그대로 쓰면 TCHAR 포인터가 반환되지 않기 때문에 포인터 연산자를 지정해줘야함

항상 FString 앞에 포인터 연산자를 붙여줘야 FString이 감싸고 있는 실제 문자열 데이터를 가져올 수 있음


FString의 구조와 활용

TEXT(”Hello”) →

  • TCHAR배열[] → TCHAR 동적배열(TArray) ↔ FCString
  • FCString ↔ TCHAR 동적배열(TArray) → TCHAR 포인터
    • TEXT매크로로 문자열을 선언하면 TCHAR배열로 만들어짐
    • TCHAR배열을 FString에 넣는 순간 TArray라는 언리얼의 동적 배열 클래스 방식으로 문자열이 보관
    • 이 실제 데이터를 꺼낼 때는 동적 배열이 속하고 있는 내부 자료에 대한 포인터를 가져옴(dereferencing)
    • 그래서 포인터 연산자를 FStirng 앞에 써주면 FString에 포함하고 있는 동적 배열 TArray에, TArray가 포함하고 있는 첫 번째 인자의 포인터를 반환해줌

FString은 보관, FCString은 문자열 처리.

다른 타입에서 FString으로 변환

  • FString::Printf
  • FString::SanitizeFloat
  • FString::FromInt

C런타임 수준에서 문자열을 처리하는 래퍼 클래스 FCStrirng

  • 문자열을 찾는 strstr 사용
  • 이 외의 문자열 슬라이싱 등 문자열에 대한 처리

FString에서 다른 타입으로 변환 (안전하진 않음)

  • FCString::Atoi
  • FCString::Atof

FStirng 예제

포인터를 끄집어내어 문자열 복사

TCHAR LogCharArray[] = TEXT("Hello Unreal");
FSring LogCharString = LogCharArray; //FString 안에 문자열 데이터가 포함됨

const TCHAR* LogCharPtr = *LogCharString; //const로 문자열복사
TCAHR* LogCharDataPtr = LogCharString.GetCharArray().GetData(); //CharArray를 직접 가져와 GetData
//FString에 직접 접근하여 수정할 경우
//TArray의 첫번째 인자인 포인터를 가져오기

//다시 배열로 가져올 경우
TCHAR LogCharArrayWithSize[100]; //배열 선언 후
FCString::StrCpy(LogCharArrayWithSize, LogCharString.Len(), *LogCharString) //저수준 스트링 복사

//텍스트 존재여부 검사
if(LogCharString.Contains(TEXT("unreal"), ESearchCase::IgnoreCase)) //대소문자 구분 없이 "unreal" 단어 검색
{
	int32 index = LogCharString.Find(TEXT("unreal"), ESearchCase::IgnoreCase); //"unreal"단어의 위치 검색
	FString EndString = LogCahrString.Mid(index); //"unreal"이 시작되는 위치부터 끝까지 자르기
	UE_Log(LogTemp, Log, TEXT("Find Text: %s"), *EndString);
}

//문자열 나누기
FString Left, Right;
if (LogCharString.Split(TEXT(" "), &Left, &Right)) //공백을 기준으로 문자열 나누기
{
	UE_Log(LogTemp, Log, TEXT("Split Test: %s 와 %s"), *Left, *Right); //출력결과 한글 깨짐
	//윈도우에서 한글을 사용하면 CP949형태의 멀티 바이트 스트링으로 저장됨
	//언리얼은 UTF-16을 사용하기 때문에 한글이 깨짐. 인코딩 옵션 변경해줘야함
}

int32 IntValue = 32;
float FloatValue = 3.141592;

//문자열로 변환
FString FloatIntString = FString::Printf(TEXT("Int: %d, Float:%f"), IntValue, FloatValue);
FString FloatString = FString::SanitizeFloat(FloatValue);
FString IntString = FString::FromInt(IntValue);

UE_Log(LogTemp, Log, TEXT("%s"), *FloatIntString);
UE_Log(LogTemp, Log, TEXT("Int: %s, Float:%f"), *IntString, *FloatIntString);

//문자열에서 변환
int32 IntValueFromString = FCString::Atoi(*IntString);
float FloatValueFromString = FCString::Atof(*FloatString);
FString FloatIntString2 = FString::Printf(TEXT("Int: %d, Float:%f"), IntValueFromString, FloatValueFromString);
UE_Log(LogTemp, Log, TEXT("%s"), *FloatIntString2);

 

다른 이름으로 저장, 인코딩 옵션을 UTF-8로 변경하면 한글이 정상적으로 출력됨


FName와 FText

FName: 에셋 관리를 위해 사용되는 문자열 체계

문자열을 사용하지만 Key,Value로 저장되어 빠르게 원하는 에셋을 찾을 수 있게 함

  • 대소문자 구분 없음
  • Key로 저장되어 한번 선언되면 바꿀 수 없음. int로 변환됨
  • 가볍고 빠름
  • 문자를 표현하는 용도가 아닌 에셋 키를 지정하는 용도로 사용, 빌드 시 해시값으로 변환됨

FText: 다국어 지원을 위한 문자열 관리 체계 (UI등)

  • 일종의 키로 작용
  • 별도의 문자열 테이블 정보가 추가로 요구됨
  • 게임 빌드 시 자동으로 다양한 국가별 언어로 변환됨

FString을 사용해 문자열을 관리하지만 FName이나 FText로 변환해 다양한 용도로 사용 가능하다.

FName의 구조와 활용

  • 언리얼은 FName과 관련된 글로벌 Pool 자료구조를 가지고 있음
  • FName과 글로벌 Pool
    • 문자열이 들어오면 해시 값을 추출해 키를 생성하고 FName에서 보관
    • FName 값에 저장된 값을 사용해 전역 Pool(싱글톤)에서 원하는 자료를 검색해 반환
    • 문자 정보는 대소문자를 구분하지 않고 저장
  • FName의 형성
    • 생성자에 문자열 정보를 넣으면 Pool을 조사하여 적당한 키로 변환하는 작업이 수반됨
    • Only Find or Add

“HELLO” → FName → FNamePool(Key, Value)

FName Key1(TEXT("PELVIS"));
FName Key2(TEXT("pelvis"));
UE_Log(LogTemp, Log, TEXT("FName 비교 결과: %s"), Key1 == Key2 ? TEXT("같음") : TEXT("다름"));
///FName은 대소문자를 구분하지 않으므로 같음

for(int i = 0; i<10000; ++i)
{
	FName SearchInFNameFool = FName(TEXT("pelvis"));
	//FName은 문자열을 Key로 변환한 뒤 해당 Key가 전역Pool에 있는지 조사하는 작업을 거치기 때문에
	//빈번하게 작업이 발생할 경우(tick) 오버헤드가 발생할 수 있음
	//조사해서 FName에 관련 Key값만 저장되면 되기 때문에 가급적 그 전에 한번 선언하거나
	const static FName StaticOnlyOnce(TEXT"pelvis");
	//local static으로 선언하여 처음 초기화할 때 데이터를 저장하고 한번만 처리하게 해주는 것이 좋다
}

 

 


이득우의 언리얼 프로그래밍 Part1 | 인프런

https://www.inflearn.com/course/%EC%9D%B4%EB%93%9D%EC%9A%B0-%EC%96%B8%EB%A6%AC%EC%96%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-part-1/dashboard

 

이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해 강의 | 이득우 - 인프런

이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 엔진 프로그래머라면 게임 개발전에 반드시 알아야 하는 언리얼 C++ 기초에 대해 알려드립니다., [사진] 언

www.inflearn.com