Unreal engine/Unreal C++

1_3_언리얼 오브젝트의 이해_언리얼C++ 기본타입과 문자열

devRiripong 2023. 9. 15.
반응형

강의 목표

언리얼 환경에서 알아두어야 할 기본 타입과 고려할 점

캐릭터 인코딩 시스템에 대한 이해

언리얼 C++이 제공하는 다양한 문자열 처리 방법과 내부 구성의 이해

 

정리

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

  1. 언리얼이 C++ 타입 int를 사용하지 않는 이유
  2. 다양한 캐릭터 인코딩 시스템의 이해
  3. 언리얼의 문장 처리의 이해
  4. FString의 구조와 사용 방법
  5. FName의 구조와 사용 방법

 

1. 언리얼이 C++ 타입 int를 사용하지 않는 이유

-> int의 크기가 플래폼에 따라 32bit나 64bit로 해석될 수 있는데 캐시 크기가 데이터 크기에 맞게 정렬이 되어야 하는데 애매모호한 크기의 타입을 사용하면 문제를 일으킬 수 있다. 

 

2. 다양한 캐릭터 인코딩 시스템 이해 

->

 

언리얼은 왜 문자열을 따로 지정할까?

-> 컴퓨터의 발전 역사에 의해 

싱글바이트(ANSI, ASCII) - 알파벳, 영미권

멀티바이트 문자열(EUC-KR, CP949) - 그 외 국가 문자, 윈도95시절~아직도 쓰임 

유니코드(UTF-8, UTF-16) - 90년대 후반
이 세가지를 다 신경 써 줘야 한다. 하지만 그러다 보면 문제가 발생하니 TCHAR라는 문자열 고유 처리 방식을 제공하고 있으니 TCHAR만 생각하면 된다.

 

3. 언리얼의 문장 처리의 이해

->

 

캐릭터 인코딩 공식문서

https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/StringHandling/CharacterEncoding/

 

캐릭터 인코딩

언리얼 엔진 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 공식 문서

https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/StringHandling/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")); // 이렇게 하는게 효과적 
	}

 

반응형

댓글