언리얼 C++의 기본 타입과 문자열 다루는 방법 소개

  • 언리얼 환경에서 알아두어야 할 기본 타입과 고려할 점
  • 캐릭터 인코딩 시스템에 대한 이해
  • 언리얼 C++이 제공하는 다양한 문자열 처리 방법과 내부 구성의 이해

 

인프런 이득우님의 '언리얼 프로그래밍 Part1 - 언리얼 C++의 이해' 강의를 참고하였습니다. 
😎 [이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해] 강의 들으러 가기!

 

 

 

 


 

 

언리얼 C++ 기본 타입


 

언리얼은 C++의 기본 타입

 

1970년대에 개발된 C++언어는 기기들마다 C++ 언어를 구현하는 방법이 달라서 C++ 주요 함수가 특정 플랫폼에서 다르게 작동하는 플랫폼 파편화 (Platform Fragmentation) 문제가 발생했다.

 

게임 제작에서는

  • 데이터 정보가 명확해야 한다.
  • 단일 컴퓨터에서 최대 퍼포먼스를 뽑아내야 한다.
  • 네트웍 상에서 데이터 통신이 효율적이고 안정적이어야 한다.

그렇기 때문에 언리얼 엔진은 데이터 타입의 모호함을 없애기 위해 언리얼 자체 기본 타입을 만들었다.

 

 

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


 

bool 타입의 선언

 

  • 데이터 전송을 고려한 참, 거짓 데이터의 지정
  • `.h` 헤더에서는 가급적 `bool` 대신 `uint8` 타입을 사용하되 Bit Field 오퍼레이터를 사용해서 데이터 크기를 제한해 최소화시킬 수 있다.
  • 일반 `uint8` 과의 구분을 위해, `b` 접두사를 사용하도록 코딩 표준에 명시되어 있다. 
  • .cpp 로직에서는 자유롭게 bool을 사용해도 무방하다.

UPROPERTY()
uint8 bNetTemporary:1;
uint8 bNetStartup:1;
UPROPERTY(Category=Replication, EditDefaultOnly, BlueprintReadOnly)
uint8 bOnlyRelevantToOwner:1;
UPROPERTY(Category=Replication, EditDefaultOnly, BlueprintReadWrite)
uint8 bAlwaysRelevant:1;

 

 

언리얼의 문자열


 

 

언리얼이 문자열을 따로 지정하는 이유는?

 

  • 1990년대 후반에 Unicode라는 새로운 표준을 통해 모든 국가의 문자를 표현할 수 있게 되었다.
  • 하지만, 컴퓨터는 그 전에 보급되었다.
  • 문자열 처리의 종류
    • Single byte
    • Multibyte
    • Unicode

 

  • Unicode보다 먼저 등장한 Multibyte 표현방법이 아직도 사용이 되고 있다.
    • C++ STL은 ASCII, UTF-8, UTF-16만 지원함

 

이 상황을 해결하기 위해서 언리얼에서는 TCHAR을 이용한다.
언리얼은 내부적으로 UTF-16을 사용한다.


 

 

복잡한 문자열 처리를 하나로

 

  • 유니코드(Unicode)를 사용해 문자열 처리를 통일
    • 이 중에서 2byte로 사이즈가 균일한 UTF-16을 사용
    • 유니코드를 위한 언리얼 표준 캐릭터 타입: TCHAR

 

  • 문자열은 언제나 TEXT 매크로를 사용해 지정
    • TEXT 매크로로 감싼 문자열은 TCHAR 배열로 지정됨

 

  • 문자열을 다루는 클래스로 FString을 제공함
    • FStringTCHAR 배열을 포함하는 헬퍼 클래스

 

#include "MyGameInstance.h"
void UMyGameInstance::Init()
{
Super::Init();
TCHAR LogCharArray[] = TEXT("Hello Unreal!");
UE_LOG(LogTemp, Log, TEXT("%s"), LogCharArray);
FString LogCharString = LogCharArray;
UE_LOG(LogTemp, Log, TEXT("%s"), *LogCharString); // *연산자를 붙여줘야 FString이 감싸고 있는 실제 문자열 데이터를 가져올 수 있다.
}

 

 

FString의 구조와 활용

 

  • 다른 타입에서 FString으로의 변환
    • FString::Printf
    • FString::SanitizeFloat
    • FString::FromInt

 

  • FCString 래퍼 클래스로 C 런타임 수준에서 문자열을 처리하는 함수를 사용할 수 있다.
    • ex. 문자열을 찾는 strstr, 문자열 복사 strcpy, strcmp, 등등

 

  • FString에서 다른 타입으로의 변환 (안전하지 않으므로 주의)
    • FCString::Atoi
    • FCString::Atof

 

 

 

※ 참고사항:  직렬화(Serialization) 후 변환되는 

  • 직렬화(Serialization)을 거친 모든 오브젝트와 변수들은 '0, 1로 이루워진 String(= 즉, bit array)'으로 변경된다.
    • 직렬화된 정보들은 나열된 정보로 저장된다.
    • CoreNet.h 내의 UPackageMap을 사용해서 직렬화된 정보를 사용

 

 

 

 

TCHAR와 FString 이용한 문자열 처리

 

#include "MyGameInstance.h"
void UMyGameInstance::Init()
{
Super::Init();
const TCHAR* LogCharPtr = *LogCharString;
// std::string::data() 같은 느낌인듯 하다.
TCHAR* LogCharDataPtr = LogCharString.GetCharArray().GetData();
// 대소문자 구분 없이 "Unreal"이란 문자열을 찾음. 리턴 값은 bool형
if (LogCharString.Contains(TEXT("Unreal"), ESearchCase::IgnoreCase))
{
// 대소문자 구분 없이 "Unreal"이란 문자열이 시작하는 위치를 찾음.
int32 Index = LogCharString.Find(TEXT("Unreal"), ESearchCase::IgnoreCase);
// Index 위치부터 끝까지의 부분문자열을 리턴. substr
FString EndString = LogCharString.Mid(Index);
UE_LOG(LogTemp, Log, TEXT("Find Test: %s"), *EndString);
}
}

 


 

 

문자열 코드 출력하기 코드

 

#include "MyGameInstance.h"
void UMyGameInstance::Init()
{
Super::Init();
FString LeftString, RightString;
if (LogCharString.Split(" ", &LeftString, &RightString)) // " "(공백)을 기준으로 문자열을 두개의 부분 문자열로 분리
{
// 한글을 사용하기 위해선 UTF-8(Unicode 65001)로 인코딩해야 함.
UE_LOG(LogTemp, Log, TEXT("Split Test: %s 와 %s"), *LeftString, *RightString);
}
int32 IntValue = 32;
float FloatValue = 3.141592;
// FString::SanitizeFloat, FString::FromInt를 이용해 정수형, 실수형 변수를 문자열로 전환
// FString::Printf의 경우, 출력하는 함수가 아닌 FString을 반환하는 함수다
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: %s"), *IntString, *FloatString);
}

 

 


 

 

 

FName의 활용

 


 

 

언리얼이 제공하는 다양한 문자열 처리

 

   
 FName  애셋 관리를 위해 사용되는 문자열 체계
  • 대소문자 구분 없음
  • 한번 선언되면 바꿀 수 없음 (선언되면 key로 만들어지는 것이기 때문에 int로 변환된다.)
  • 가볍고 빠름
  • 문자를 표현하는 용도가 아닌 애셋 키를 지정하는 용도로 사용. 빌드시 해시값으로 변환됨.
 FText  다국어 지원을 위한 문자열 관리 체계
  • 일종의 로 작용함
  • 별도의 문자열 테이블 정보가 추가로 요구됨
  • 게임 빌드 시 자동으로 다양한 국가별 언어로 변환

 


 

 

FName 구조와 활용

 

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

문자가 들어왔을때 문자열 정보가 저장되는 것이 아니라 Key값이 저장됨. 문자열은 다른곳에 따로 저장됨.


 

 

FName 예시 코드

 

#include "MyGameInstance.h"
void UMyGameInstance::Init()
{
Super::Init();
// 문자열이 해시테이블을 통해 key값으로 변환
FName key1(TEXT("PELVIS"));
FName key2(TEXT("pelvis"));
UE_LOG(LogTemp, Log, TEXT("FName 비교 결과: %s"), key1 == key2 ? TEXT("같음") : TEXT("다름"));
// 아래와 같이 매번 실행하게 하면, 오버헤드가 발생한다.
// Tick과 같이 자주 실행되는 곳에 FName을 사용하는 경우, 오버헤드가 발생할 수 있다.
for (int i = 0; i < 10000; i++)
{
FName SearchInNamePool = FName(TEXT("pelvis")); // 생성자에 문자열을 넣음. pelvis
}
// StationOnlyOnce로 처음 초기화할 때 저장한 뒤에는 그 후에는 찾을 일이 없다.
for (int i = 0; i < 10000; i++)
{
FName SearchInNamePool = FName(TEXT("pelvis")); // 생성자에 문자열을 넣음. pelvis
const static FName StaticOnlyOnce(TEXT("pelvis"));
}
}