강의 목표
언리얼 환경에서 알아두어야 할 기본 타입과 고려할 점
캐릭터 인코딩 시스템에 대한 이해
언리얼 C++이 제공하는 다양한 문자열 처리 방법과 내부 구성의 이해
정리
언리얼 C++ 기본 타입과 문자열 처리
- 언리얼이 C++ 타입 int를 사용하지 않는 이유
- 다양한 캐릭터 인코딩 시스템의 이해
- 언리얼의 문장 처리의 이해
- FString의 구조와 사용 방법
- FName의 구조와 사용 방법
1. 언리얼이 C++ 타입 int를 사용하지 않는 이유
-> int의 크기가 플래폼에 따라 32bit나 64bit로 해석될 수 있는데 캐시 크기가 데이터 크기에 맞게 정렬이 되어야 하는데 애매모호한 크기의 타입을 사용하면 문제를 일으킬 수 있다.
2. 다양한 캐릭터 인코딩 시스템 이해
->
언리얼은 왜 문자열을 따로 지정할까?
-> 컴퓨터의 발전 역사에 의해
싱글바이트(ANSI, ASCII) - 알파벳, 영미권
멀티바이트 문자열(EUC-KR, CP949) - 그 외 국가 문자, 윈도95시절~아직도 쓰임
유니코드(UTF-8, UTF-16) - 90년대 후반
이 세가지를 다 신경 써 줘야 한다. 하지만 그러다 보면 문제가 발생하니 TCHAR라는 문자열 고유 처리 방식을 제공하고 있으니 TCHAR만 생각하면 된다.
3. 언리얼의 문장 처리의 이해
->
캐릭터 인코딩 공식문서
캐릭터 인코딩
언리얼 엔진 4 에 사용되는 캐릭터 인코딩에 대한 개요입니다.
docs.unrealengine.com
4. FString 구조와 사용 방법
->
실습
새로운 UnrealString이란 이름의 언리얼 엔진 프로젝트를 생성하고
GameInstance클래스를 상속받은 MyGameInstance라는 C+ 클래스를 추가한다.
file→project setting→Maps&Modes으로 가서 기본맵들을 clear한다.
GameInstance class를 MyGameInstance로 지정한다.
헤더 파일을 변경할 것이기에 언리얼 에디터는 끈다.
public, private으로 나눠주고 재정의할 overried할 Init을 선언한다.
UCLASS()
class UNREALSTRING_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
virtual void Init() override;
private:
};
alt+enter를 눌러 구현부를 자동으로 생성한다.
override 받은 함수는 일단 Super로 시작을 해준다.
#include "MyGameInstance.h"
void UMyGameInstance::Init()
{
Super::Init();
TCHAR LogCharArray[] = TEXT("Hello Unreal");
UE_LOG(LogTemp, Log, TEXT("Message: %s"), LogCharArray);
}
이렇게 하고 빌드 후 ctrl+f5로 디버깅 없이 start한다.
언리얼에서 플레이 버튼을 누르면 메시지가 뜨는 걸 볼 수 있다.
void UMyGameInstance::Init()
{
Super::Init();
TCHAR LogCharArray[] = TEXT("Hello Unreal");
UE_LOG(LogTemp, Log, TEXT("Message: %s"), LogCharArray);
FString LogCharString = LogCharArray;
UE_LOG(LogTemp, Log, TEXT("%s"), *LogCharString); // 매크로에서는 세번째는 배열만 들어가기 떄문에 FString을 쓸 수는 없고, %s에 대응 될 때는 TCAHR의 포인터 array를 반환해줘야 하는데
// FString을 그대로 쓰면 TCHAR의 포인터가 반환이 되지 않는다. 그래서 *(포인터 연산자)를 지정해주면 된다. 그래야 FString이 감싸고 있는 실제 문자열 데이터를 가져올 수가 있게 된다.
}
매크로에서는 세번째는 배열만 들어가기 떄문에 FString을 쓸 수는 없고, %s에 대응 될 때는 TCAHR의 포인터 array를 반환해줘야 하는데 FString을 그대로 쓰면 TCHAR의 포인터가 반환이 되지 않는다. 그래서 *(포인터 연산자)를 지정해주면 된다. 그래야 FString이 감싸고 있는 실제 문자열 데이터를 가져올 수가 있게 된다. 라이브 코딩이니 코드 저장 후 언리얼에서 ctrl+alt+f11키를 누르고 실행 버튼을 누르면
이렇게 로그가 찍힌다.
유니코드를 사용해 문자열 처리를 통일
- 이 중에서 2byte로 사이즈가 균일한 UTF-16을 사용
- 유니코드를 위한 언리얼 표준 캐릭터 타입:TCHAR
문자열은 언제나 TEXT 매크로를 사용해 지정
- TEXT매크로로 감싼 문자열은 TCHAR배열로 지정됨
문자열을 다루는 클래스로 FString를 제공함
- FString은 TCHAR 배열을 포함하는 헬퍼 클래스
FString 공식 문서
FString
docs.unrealengine.com
TCHART를 FString에 넣으면 TArray라는 동적 배열 방식으로 보관이 된다.
그래서 TArray에서 다시 TCHAR 배열로 꺼내려면 * 연산자를 써야 한다.
FString이 제공하는 함수들은 내부적으로는 FCStirng이라는 클래스에 있다. 이 클래스는 C라이브러리에서 제공하는 기본적인 string 함수들을 포함하고 있는 래퍼 클래스라고 보면 된다. FString에서 보관하지만 실제 문자열을 자르거나 찾거나 하는 처리들은 FCString을 통해서 진행을 한다고 이해를 하면 된다.
몇가지 유용한 함수들
- 다른 타입에서 FString으로 변환
FString::Printf
FString::SanitizeFloat
FString::FromInt
- C런타임 수준에서 문자열을 처리하는 클래스 FCString
문자열 찾는 strstr
- FString에서 다른 타입으로의 변환(안전하지 않으므로 주의)
FCString::Atoi
FCString::Atof
예제
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameInstance.h"
void UMyGameInstance::Init()
{
Super::Init();
TCHAR LogCharArray[] = TEXT("Hello Unreal");
UE_LOG(LogTemp, Log, TEXT("Message: %s"), LogCharArray);
FString LogCharString = LogCharArray;
UE_LOG(LogTemp, Log, TEXT("%s"), *LogCharString);
// 매크로에서는 세번째는 배열만 들어가기 떄문에 FString을 쓸 수는 없고, %s에 대응 될 때는 TCAHR의 포인터 array를 반환해줘야 하는데
// FString을 그대로 쓰면 TCHAR의 포인터가 반환이 되지 않는다. 그래서 *(포인터 연산자)를 지정해주면 된다. 그래야 FString이 감싸고 있는 실제 문자열 데이터를 가져올 수가 있게 된다.
// FString에 집어 넣은 걸 끄집어 내고 싶다면
const TCHAR* LongCharPtr = *LogCharString;
// 직접 포인터를 받아서 고치고 싶다면
TCHAR* LogCharDataPtr = LogCharString.GetCharArray().GetData();
// 다시 배열로 가지고 오고 싶다면
TCHAR LogCharArrayWithSize[100];
FCString::Strcpy(LogCharArrayWithSize, LogCharString.Len(), *LogCharString);
// FString 유용한 함수들
if (LogCharString.Contains(TEXT("unreal"), ESearchCase::IgnoreCase)) // 대소문자 구분할지 지정할 수 있다.
{
int32 Index = LogCharString.Find(TEXT("unreal"), ESearchCase::IgnoreCase);
FString EndString = LogCharString.Mid(Index);
UE_LOG(LogTemp, Log, TEXT("Find Test:%s"), *EndString);
}
}
이렇게 빌드해서 잘 나오는지 보자.
뒤에 있는 Unreal만 출력됐어.
FString Left, Right;
if (LogCharString.Split(TEXT(" "), &Left, &Right))
{
UE_LOG(LogTemp, Log, TEXT("Split Test : %s 와 %s"), *Left, *Right);
}
이 코드를 추가해서 실행을 해보면
와’가 이상하게 출력되어 있는 것을 볼 수 있다.
이유는 코드를 작성할 때 윈도우에서 한글을 썼기 떄문에 CP949 멀티바이트 형태로 저장이 되어서, UTF16을 쓰는 언리얼과 호환이 안되는 거다.
이 때는 비쥬얼 스튜디오에서 File→Save As를 눌러서 Save 버튼 옆의 인코딩 옵션으로 가서
이것을 UTF-8로 바꿔주면 된다.
with signature라고 하는 건 3바이트 해더 BOM 정보가 들어가 있는 UTF-8 형식을 얘기 하고,
이렇게 저장을 해주고 언리얼에서 컴파일을 해주고 실행을 하면
이제 와가 잘 출력이 되는 것을 볼 수 있다.
인코딩에 대한 기본 지식을 갖췄다면
미세한 문제점들에 대해 스스로 해결할 수 있는 능력을 갖추게 될 거야.
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:%s"), *FloatString, *IntString);
}
이렇게 코드를 추가하고 언리얼에서 컴파일 후 플레이 해보면
이렇게 나온다.
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);
이 코드 추가을 하고 다시 컴파일 후 실행을 해보면
FString을 TCHAR 배열로 뺄 때 왜 포인터 연산을 사용하는지 FString 구조는 어떻게 되었는지 내부 구조를 이해해서 사용하면 좋다.
5. FName의 구조와 사용 방법
FName : 에셋 관리를 위해 사용되는 문자열 체계
- 대소문자 구분 없음(주의, 수정하려고 다시 FStirng으로 변환하면 대소문자가 구분 없어서 깨질 수 있다. )
- 한번 선언되면 바꿀 수 없음
- 가볍고 빠름
- 문자를 표현하는 용도가 아닌 애셋 키를 지정하는 용도로 사용. 빌드시 해시값으로 변환됨. (수정을 못함)
- 빠르게 원하는 에셋을 찾을 수 있게 제공되는 문자열
FText: 다국어 지원을 위한 문자열 관리 체계(UI에서)
- 일종의 키로 작용함
- 별도의 문자열 테이블 정보가 추가로 요구됨
- 게임 빌드 시 자동으로 다양한 국가별 언어로 변환됨
FString을 사용해서 문자열 관리하는데 이것들을 FName이나 FText로 변환해서 다양한 용도로 사용할 수 있다.
FName 클래스 공식 문서
https://docs.unrealengine.com/5.0/ko/fname-in-unreal-engine/
FName
docs.unrealengine.com
FName의 구조와 활용
언리얼은 FName과 관련된 글로벌 Pool 자료구조를 가지고 있음.
FName과 글로벌 Pool
- 문자열이 들어오면 해시 값을 추출해 키를 생성해 FName에서 보관
- FName 값에 저장된 값을 사용해 전역 Pool에서 원하는 자료를 검색해 반환
- 문자 정보는 대소문자를 구분하지 않고 저장함. (Ignore Case)
FName의 형성
- 생성자에 문자열 정보를 넣으면 풀을 조사해 적당한 키로 변환하는 작업이 수반됨.
- Find or Add
에셋 보관에 유용. FName을 이용해 키값을 사용하는 문자열들을 관리하면 좋다.
예제
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 SearchInNamePool = FName(TEXT("pelvis")); // 이렇게 선언하면 생성자에 문자열을 넣으면 FName이 문자열을 키로 변환하고 그 키가 전역 풀에 있는지 조사하는 작업을 거치기 때문에 오버헤드가 발생 가능
const static FName StaticOnlyOnce(TEXT("pelvis")); // 이렇게 하는게 효과적
}
'Unreal engine > Unreal C++' 카테고리의 다른 글
1_5_언리얼 오브젝트의 이해_언리얼 오브젝트 리플렉션 시스템1 (0) | 2023.09.18 |
---|---|
1_4_언리얼 오브젝트의 이해_언리얼 오브젝트 기초 (0) | 2023.09.18 |
1_2_언리얼 오브젝트의 이해_언리얼 C++ 코딩규칙 (0) | 2023.09.14 |
1_1_언리얼 오브젝트의 이해_헬로 언리얼 (0) | 2023.09.14 |
0_1. 강의소개: 언리얼 프로그래밍 공부법 (0) | 2023.09.11 |
댓글