본문 바로가기
Unreal/[서적] 언리얼5 이득우님 인프런1

Ch 03. 언리얼 C++ 기본 타입과 문자열

by GameStudy 2023. 5. 9.

3.1 언리얼 C++ 기본 타입

3.1-1 기본 타입 재정의의 필요성

  - C++는 C언어를 발전시킨 언어임.

    C언어는 임베디드 전용 언어로, 극단적인 환경에서도 동작할 수 있게끔 개발됨.

    극단적인 환경이라 함은 256MB보다 더 적은 렘에서도 돌아감.

    그래서 자료형의 크기가 아주 중요함. C언어는 자료형의 크기를 컴파일러에게 자율로 맡김

    그래서 본인이 C 컴파일러를 만든다면, int 자료형의 크기를 200MB로 해도됨.

    이는 C++에도 영향을 줌. 최신 C++ 표준에서는 int 자료형의 크기를 "최소" 32bit로 규정.

  - 게임 개발 후에는 여러 플랫폼(Windows, IOS, Android, Console, ...)에서 동작시켜야함.

    게다가 자료형도 여러 개임(bool, int, float, double, ...)

    근데 이 자료형마다 그 크기가 플랫폼 별로 마음대로 정해져 있음.

    이를 플랫폼 파편화라고 함.

  - 게임 제작에서는 이런 모호함들이 유지보수에 큰 영향을 끼침.

    그래서 C++을 사용하는 언리얼에서는 기본 타입의 재정의를 진행함.

 

3.1-2 언리얼 C++ 기본 타입

  - [여기]에서 확인 가능함.

 

3.1-3 bool 타입의 선언

  - C/C++에는 bool 자료형에 대한 표준 자체가 없음.

    그래서 그 크기가 명확하지 않음.

  - 언리얼에서의 사용 방법

    header 파일에서는 bool 대신 uint8 + Bit field 연산자를 사용. 참/거짓을 굳이 8bit로 할 필요 없음.

    cpp 파일에서는 bool 자료형 사용.

 

3.2 문자열 인코딩 시스템

3.2-1 언리얼 문자열 재정의의 필요성

  - 1990년대 후반이 되어서야 동아시아(한국, 일본, 중국)의 문자열이 표준에 등재됨.

    하지만 그 전부터 컴퓨터는 사용 되었음.

  - 문자열 인코딩 방식

    Single-Byte(ANSI, ASCII)

    Multi-Byte(EUC-KR, CP949, ...)

    Unicode(UTF-8, UTF-16, UTF-32)

  - 문자열 인코딩 방식도 플랫폼마다 다르고 나라마다 다름.

    이또한 게임 개발의 유지보수에 큰 영향을 끼침.

 

3.2-2 언리얼의 문자열 인코딩 

  - 언리얼은 내부적으로 UTF-16 방식을 채택함.

  - 언리얼이 파일(npc 대사, ...)을 로드할 때도 UTF-16으로 채택함.

  - 소스코드의 경우에는 컴파일 에러(Windows, Linux, ...)가 날 수 있으므로

    가급적 영문으로 작성하는걸 권장.

    동아시아 문자열이 꼭 필요한 경우에는 UTF-8로 변환 가능.

 

3.2-3 FString의 구조와 활용
  - 다른 타입에서 FString으로의 변환
    FString::Printf()
    FString::SanitizeFloat()
    FString::FromInt()

 

  - C 런타임 수준에서 문자열을 처리하는 클래스 FCString
    ex) 문자열을 찾는 strstr() 함수 활용 가능.

 

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

TEXT() 매크로는 TCHAR 배열을 반환. FString은 내부적으로 TCHAR 동적 배열을 품고 있음.

 

3.2-5 TCHAR와 FString 실습

  - UnrealString 프로젝트 생성

  - New C++ Class > GameInstance 부모 클래스 > MyGameInstance 클래스 생성

<hide/>

// MyGameInstance.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"

/**
 * 
 */
UCLASS()
class UNREALSTRING_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:
	virtual void Init() override;
	
};
<hide/>

// MyGameInstance.cpp

// 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"); // UTF-16을 위한 언리얼 표준 캐릭터 타입 TCHAR. TCHAR 자료형 변수에 문자열을 저장하기 위해서는 항상 TEXT() 매크로 사용.
	UE_LOG(LogTemp, Log, LogCharArray);

	FString LogCharString = LogCharArray; // 문자열을 자유롭게 조작하고 싶다면, TCHAR 배열 대신 FString을 사용해야 함. FString은 TCHAR 배열을 포함하는 헬퍼 클래스.
	  // FString LogCharString = FString(TEXT("Hello Unreal")); 같은 코드.
	UE_LOG(LogTemp, Log, TEXT("%s"), *LogCharString); // FString을 그대로 사용하면 TCHAR 배열을 반환되지 않음. 포인터 연산자를 붙혀줘야 실제 문자열이 반환됨.
	  // 결국 FString은 TArray<TCHAR> 멤버를 갖고 있다는 뜻.
	
	const TCHAR* LongCharPtr = *LogCharString;
	TCHAR* LogCharDataPtr = LogCharString.GetCharArray().GetData();

	TCHAR LogCharArrayWithSize[100];
	FCString::Strcpy(LogCharArrayWithSize, LogCharString.Len(), *LogCharString); // C 문자열 라이브러리에서 제공하는 문자열 처리 함수(strstr(), ...)를 제공하는 클래스 FCString. 다만 사용이 안전하다는건 보장받지 못함.

	if (LogCharString.Contains(TEXT("unreal"), ESearchCase::IgnoreCase)) // IgnoreCase는 대소문자 구분없이 진행.
	{
		int32 Index = LogCharString.Find(TEXT("unreal"), ESearchCase::IgnoreCase);
		FString EndString = LogCharString.Mid(Index); // "unreal" 문자열이 시작되는 곳에서부터 마지막까지 자름.
		UE_LOG(LogTemp, Log, TEXT("Find Test: %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을 사용하는 언리얼과 호환 문제 발생.
		  // File > Save as ... > Save 버튼 우측 역삼각형 클릭 > Save with Encoding > Encoding에 UTF-8로 지정 해줘야 Output log에 한글이 제대로 출력됨.
	}

	int32 IntValue = 32;
	float FloatValue = 3.141592;

	FString FloatIntString = FString::Printf(TEXT("Int:%d Float:%f"), IntValue, FloatValue);
	FString FloatString = FString::SanitizeFloat(FloatValue); // float 자료형의 표준은 상당히 복잡함. 이를 정돈해서 문자열로 바꿔줌. 
	FString IntString = FString::FromInt(IntValue);

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

	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);

}

 

3.2-4 FName과 FText

  - FString은 FName과 FText로 변환해서 사용 가능함.

  - FName: 애셋 관리를 위한 문자열 클래스

    애셋을 빠르게 찾고 싶을 때, 문자열로 지정해주는게 사람에겐 편함.

    그러나 문자열을 그대로 이용하면 연산량이 늘어남. 내부적으로는 hash value로 변환하는게 FName 클래스.

    대소문자 구분이 없음. 한번 선언되면 int와 같은 정수로 변환됨. 즉 문자열 변경이 불가능.

    바꾸고자 하면 다시 문자열로 만들어야 하는데, 대소문자 구분이 없어서 원본문자열로 돌아간다는 보장이 없음.

  - FText: 다국어 지원을 위한 문자열 클래스. UI에서 자주 사용.

    일종의 키로 작용. 별도의 문자열 테이블 정보가 추가로 요구됨.

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

 

3.2-5 FName 구조와 활용

  - 언리얼은 FName과 관련된 전역 Pool 자료구조를 가지고 있음.

  - FName과 전역 Pool

    문자열이 들어오면 해시 값을 추출 후 키를 생성해서 FName에 보관

    FName 값에 저장된 키를 사용해서 전역 Pool에서 원하는 자료를 검색해 보관

    문자 정보는 대소문자를 구분하기 않고 저장함.(IgnoreCase)

  - FName의 형성

    생성자에 문자열 정보를 넣으면 전역 Pool을 조사해서 적당한 키로 변환하는 작업이 수반됨.

    즉, FindOrAdd 작업이 진행됨.

    결국 FName은 Key와 Value 쌍이고, 전역 Pool에는 기존의 FName들이 들어있음.

 

3.2-6 FName 실습 예제

<hide/>

// MyGameInstance.cpp

...

void UMyGameInstance::Init()
{
	...

	FName key1(TEXT("PELVIS"));
	FName key2(TEXT("pelvis"));
	UE_LOG(LogTemp, Log, TEXT("FName 비교 결과 : %s"), key1 == key2 ? TEXT("같음") : TEXT("다름")); // IgnoreCase

	for (int i = 0; i < 10000; ++i)
	{
		FName SearchInNamePool = FName(TEXT("pelvis"));
		const static FName StaticOnlyOnce(TEXT("pelvis")); // FName은 결국 전역 Pool을 조사해보는 작업이 수반됨. 그래서 static 키워드를 통해서 재조사가 이뤄지지 않게끔 함.
	}

}

 

 

댓글