게임 개발자를 향해

[ UE5 ] 언리얼 엔진 문자열 본문

언리얼 엔진/C++

[ UE5 ] 언리얼 엔진 문자열

뿌단이 2023. 12. 17. 22:45

캐릭터 인코딩과 TCHAR

컴퓨터는 영문권에서 처음 개발되었으며, 알파벳을 1byte로 표현했습니다. (ANSI, ASCII)
하지만 다른 나라는 알파벳처럼 singlebyte로 표현할 수가 없었서 새로운 규약이 필요했습니다.
90년대 후반에 Unicode라는 표준을 만들었고 이것을 지금까지 사용하고 있습니다.


Unicode가 만들어지기 이전에는 나라들만의 방법을 고안해서 우리는 multibyte 문자열을 만들어서 사용했습니다.
(EUC-KR -> CP949로 발전함)


이 multibyte가 MS의 Window를 통해 널리 보급되어 사용되고 있었음.

이후 유니코드가 만들어지면서 90년대에 완전히 정착이 됐습니다.
근데 multibyte 문자열 체계가 아직까지 윈도우에서 쓰이고 있는것이 문제입니다.

서로 호환이 되지 않아 데이터가 깨져버리는 경우입니다.

("쒧뛝?꿻" 이런 문자가 보인다면 문자 표기가 달라서 깨지는 경우임.)

이런 여러 종류의 문자를 전부 호환되게 개발하는것은 비효율 적이므로

언리얼은 "TCHAR" 라는 언리얼 자체 방식문자열을 만들었습니다.

 

언리얼 공식 문서 Character Incoding Link

 

캐릭터 인코딩

언리얼 엔진 4 에 사용되는 캐릭터 인코딩에 대한 개요입니다.

docs.unrealengine.com

 

 

 

 

 

1. TEXT 메크로

먼저 기본적으로 언리얼에서는 문자열을 TEXT() 메크로로 관리를 합니다.

이 메크로를 통해 2byte UTF-16 문자열로 반환해줍니다.
 이 TEXT 메크로로 감싼 문자열은 TCHAR 배열로 지정이됩니다.

TEXT("Hello Unreal!");

 

 

 

 

2. TCHAR

언리얼은 내부적으로 모두 한문자당 2byte를 가지고 있는 UTF-16 포맷을 사용한다고 명시되어 있습니다.

언리얼은 가급적 Unicode를 쓰기를 권고하고있고, 

UTF-16으로 맞추되 소스코드 같은경우는 많이 사용되는 UTF-8로 저장하기를 권고하고 있습니다.

언리얼은 String을 관리할때는 UTF-16을 사용하고
소스코드에 꼭 한글을 사용하면 UTF-8 방식으로 저장해달라고 명시되어 있습니다.

(TCHAR 배열을 만들고 TEXT를 집어넣을수 있음.)

TCHAR LogCharArray[] = TEXT("Hello Unreal!");
UE_LOG(_Category, _LogType, _format, LogCharArray);

 

 


3. FString

FString TCHAR 배열을 포함하는 헬퍼 클래스입니다.
아래와 같이 대입하면 자동으로 FString 데이터에 들어가집니다.

UE_LOGTCHAR[] 자료형을 넣어야 합니다.FString에서 *라는 operator가 있습니다. 이 연산자를 이용하면 FString에 저장된 문자열을 TCHAR[]로 반환해줍니다.

TCHAR LogCharArray[] = TEXT("Hello Unreal!");
FString LogCharString = LogCharArray;
UE_LOG(_Category, _LogType, _format, *LogCharString);

 

아래 공식문서를 보면 FString에서는 여러 함수들을 제공하고 이 함수들을 이용하여 

자료형들을 문자열(TCHAR[] : UTF-16) 로 변환할 수 있습니다.

 

언리얼 엔진 공식 문서 FString Link

 

FString

 

docs.unrealengine.com

 

 

아래는 FString 에서 제공하는 함수의 예제입니다.

// TCHAR[]
TCHAR LogCharArray[] = TEXT("Hello Unreal");
UE_LOG(LogTemp, Log, TEXT("LogCharArray: %s"), LogCharArray);


// FString
{
	FString FStringLog = TEXT("abcd1234");
	UE_LOG(LogTemp, Log, TEXT("FStringLog2: %s"), FStringLog.GetCharArray().GetData());
}


// TCHAR[] To FString
{
	FString FStringLog = LogCharArray;
	FStringLog += TEXT("2");
	UE_LOG(LogTemp, Log, TEXT("FStringLog1: %s"), *FStringLog);
}


// int to FString
{
	int32 IntValue = 10;
	FString FStringLog = FString::FromInt(IntValue);
	UE_LOG(LogTemp, Log, TEXT("IntString: %s"), *FStringLog);
}

// FString to int
{
	FString IntString = TEXT("10");
	int IntValue = FCString::Atoi(*IntString);
	UE_LOG(LogTemp, Log, TEXT("Int: %02d"), IntValue);
}


// float to FString
{
	float FloatValue = 5.0f;
	FString FStringLog = FString::SanitizeFloat(FloatValue);
	UE_LOG(LogTemp, Log, TEXT("FloatString: %s"), *FStringLog);
}


// FString to float
{
	FString floatString = TEXT("10");
	float FloatValue = FCString::Atof(*floatString);
	UE_LOG(LogTemp, Log, TEXT("Float: %02f"), FloatValue);
}


// FLinearColor to FString
{
	FLinearColor LinearColorVariable = { 0.0f, 0.0f, 0.0f, 1.0f };
	FString FStringLog = LinearColorVariable.ToString();
	UE_LOG(LogTemp, Log, TEXT("FLinearColor: %s"), *FStringLog);
}


// Printf
{
	int32 Hour = 3;
	int32 Minute = 49;
	FString Res = FString::Printf(TEXT("Time %02d:%02d"), Hour, Minute);
	UE_LOG(LogTemp, Log, TEXT("Printf: %s"), *Res);
}


// copy
{
	FString FStringLog = TEXT("Copy");
	// copy TCHAR* from FString1
	const TCHAR* TCHARPtr1 = *FStringLog;

	// copy TCHAR* from FString2
	TCHAR* TCHARPtr2 = FStringLog.GetCharArray().GetData();

	// copy TCHAR[] from FString
	TCHAR TCHARArray[100];
	FCString::Strcpy(TCHARArray, FStringLog.Len(), *FStringLog);
}


// find and edit String
{
	FString FStringLog = TEXT("Hi Unreal");

	// ESearchCase::CaseSensitive (A != a)
	// ESearchCase::IgnoreCase    (A == a)
	if (true == FStringLog.Contains(TEXT("unreal"), ESearchCase::IgnoreCase))
	{
		int32 Index = FStringLog.Find(TEXT("unreal"), ESearchCase::IgnoreCase);
		FString UEString = FStringLog.Mid(Index);
		UE_LOG(LogTemp, Log, TEXT("Find and Edit String: %s"), *UEString);
	}
}

// split
{
	FString FStringLog = TEXT("Hi Unreal");
	FString Left, Right;

	if (true == FStringLog.Split(TEXT(" "), &Left, &Right))
	{
		UE_LOG(LogTemp, Log, TEXT("Split: %s 와 %s"), *Left, *Right);
	}
}

 

 

 

아래는 예제의 결과입니다.

여기서 맨 마지막에 한글을 인식하지 못하는 것을 볼 수 있습니다.

이는 MultibyteUTF-16으로 인코딩되면서 생긴 문제입니다.

 

 

 

Visual Studio에서 Save As를 클릭하고

 

 

 

Save with Encoding을 클릭합니다.

 

 

 

Encoding 설정을 보면 CodePage949(Multibyte, CP949)로 설정이 되어있습니다.

 

 

 

Unicode(UTF-8 with signature)로 바꿔주고 확인을 클릭합니다.

 

 

 

이제 한글도 잘 나오는 것을 볼 수 있습니다.

 

 

 

 

4. FCString

FString이 제공하는 함수들은 FCString이라는class가 있는데

FString은 데이터를 보관하지만 문자열 변환 등은 FCString이 담당하고 있습니다.
일종의 C 라이브러리에서 제공하는 String 관련 함수들을 포함하고있는 Wrapper class 입니다.


(Atoi(), Atof(), strcpy(), etc..)


 

 

5. FName

에셋 관리를 위해 사용되는 문자열 클래스입니다.
FName은 에셋을 빠르게 찾기 위한 용도이고 문자열을 편집하기 위한 용도가 아닙니다.

String에서 FName으로 변환 시 해시 테이블을 사용해서 탐색이 빠름.

빌드 시 데이터테이블에 한 번만 저장되며 변경도 조작도 불가능하다.

FName은 대소문자 구분이 없습니다.
빌드 시 int32로 저장되며, 변형을 하려면 다시 FString으로 변환해야하는데
대소문자 구분이 안되기 때문에 데이터가 깨질수있습니다.
Name = Test(대소문자 포함);

Nameint변형->  key값
key값을 FString변형-> TEST(대문자)

FName대문자 Key값소문자 Key값을 비교해보면

 

 

같은 값으로 나옵니다.

 

 

※ 주의사항※

FName 생성자에 문자열(TCHAR[])을 넣게 되면 Key값FNamePool에서 검색을 합니다.

만약 Key값이 없으면 해당 문자열해시 테이블에 저장합니다.

만약 이 검색하는 작업이 Tick 함수나 반복이 잦은 함수, Loop에 있다면

반복적인 검사로 인해 오버헤드가 발생할 수 있으므로 주의해야 합니다.

 

죽어도 내부에 넣어야 겠다면 함수전역(함수 내부 static)을 선언하여 

다시 생성되지 않도록 해주어도 됩니다.

 

 

 

 

6. FNamePool

FNamePool 이라는 자료구조가 있는데 FName 데이터를 저장하고 관리하는 클래스입니다.

FNamePool싱글톤 클래스로 게임에서 단일 인스턴스로 존재합니다.


FNamePool 자료구조는 Key값과 Value를 한 쌍으로 구성되며,

실질적인 데이터는 key값만 있기 때문에 용량이 굉장히 작습니다.

FName에 문자열을 넣어주면 해시값을 추출해서 FNamePool key값으로 저장합니다.
 key값을 이용해서 FNamePool에 데이터 유무를 빠르게 검출을 할 수 있습니다.
(String으로 문자 하나하나를 비교하는것보다, int32로 한번만 비교하면 되서 빠름.)

FName으로는 키값을 찾거나 추가하는 일 밖에 할 수 없습니다.
FName을 찾는 코드는 아래와 같습니다.

FName SearchOnNamePool = FName(TEXT("test"));


생성자에 TEXT를 넣어주면 이 TEXT를 key로 변환하고
FNamePool에 들어온 키값이 있는지 검사하고
SearchOnNamePool에 결과를 저장하게 되는데.

이 생성을 loop나 Tick 등의 반복적인 곳에서 사용할 경우 

반복적인 검사로 오버헤드가 발생할 수 있으므로 주의

 

 

 

 

7. FText

다국어 지원을 위한 문자열 관리 클래스 일종의 키로 작용함. (UI에서 자주 사용함)
별도의 문자열 테이블 정보가 추가로 요구됩니다.

게임 빌드 시 자동으로 다양한 국가별 언어로 변환됨.