언리얼에서 개발을 하다 보면 헷갈리는 문자열 타입 3가지가 있다.
FName, FText, FString
셋 다 문자열을 처리하는데 어디에, 어떤 식으로 사용되는 것일까?
1. FName
콘텐츠 브라우저에서 새 에셋 이름을 지을 때, 다이내믹 머티리얼 인스턴스의 파라미터를 변경할 때, 스켈레탈 메시에서 본에 접근할 때, 모두 FName을 사용합니다. FName 은 문자열 사용에 있어서 초경량 시스템을 제공하는데, 주어진 문자열이 재사용된다 해도 데이터 테이블에 한 번만 저장되는 것입니다. FName 은 대소문자를 구분하지 않습니다. 변경도 불가능하여, 조작할 수 없습니다. 이처럼 FName의 정적인 속성과 저장 시스템 덕에 찾기나 키로 FName에 접근하는 속도가 빠릅니다. FName 서브시스템의 또 다른 특징은 스트링에서 FName 변환이 해시 테이블을 사용해서 빠르다는 점입니다.
즉, 기존 C++의 문자열 방식보다 더 빠르고 가볍다는 장점이 있으며 언리얼 에디터에서 주로 사용되는 타입이다.
1-1. 변환
FName -> FString / FText로 가능하며, FString -> FName만 가능하다.
// From FName
// FName -> FString
TestHUDString = TestHUDName.ToString();
// FName -> FText
// 이 경우 FText의 현지화 데이터 손실이 존재한다.
TestHUDText = FText::FromName(TestHUDName);
// To FName
// FString -> FName
// FName은 대소문자 구분이 없으므로 주의해야한다.
TestHUDName = FName(*TestHUDString);
1-2. 비교
FName비교는 1) == 연산자와 2) FName::Compare을 사용해 비교할 수 있다.
1) == 연산자
실제 문자열 비교가 아닌, Index 안의 값을 비교하여 CPU 값을 크게 절약할 수 있다.
NameTypes.h에 작성된 연산자 오버로딩을 보면 아래처럼 나와있다.
// in NameTypes.h
// ...
class FName
{
// ...
FORCEINLINE bool operator==(FName Other) const
{
return ToUnstableInt() == Other.ToUnstableInt();
}
FORCEINLINE bool operator!=(FName Other) const
{
return !(*this == Other);
}
// ...
#if UE_FNAME_OUTLINE_NUMBER
FORCEINLINE uint64 ToUnstableInt() const
{
return ComparisonIndex.ToUnstableInt();
}
#else
FORCEINLINE uint64 ToUnstableInt() const
{
static_assert(STRUCT_OFFSET(FName, ComparisonIndex) == 0);
static_assert(STRUCT_OFFSET(FName, Number) == 4);
static_assert((STRUCT_OFFSET(FName, Number) + sizeof(Number)) == sizeof(uint64));
uint64 Out = 0;
FMemory::Memcpy(&Out, this, sizeof(uint64));
return Out;
}
#endif
//...
private:
/** Index into the Names array (used to find String portion of the string/number pair used for comparison) */
FNameEntryId ComparisonIndex;
#if !UE_FNAME_OUTLINE_NUMBER
/** Number portion of the string/number pair (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) */
uint32 Number;
#endif// ! //UE_FNAME_OUTLINE_NUMBER
#if WITH_CASE_PRESERVING_NAME
/** Index into the Names array (used to find String portion of the string/number pair used for display) */
FNameEntryId DisplayIndex;
#endif // WITH_CASE_PRESERVING_NAME
//...
}
assert 부분은 3가지로 되어있는데
1) FName의 ComparisonIndex의 offset이 0인지 체크
2) FName의 Number offset이 4인지 체크
3) ComparisonIndex + Number가 uint64 크기(8 바이트) 만큼을 차지하는지 확인
즉, FName은 8 byte가 ComparisionIndex(4 byte) + Number (4 byte)로 구성되어 있다는 뜻이다.
== 연산에서 FName을 uint64로 변환 후 비교하면 하위 4 byte의 ComparisonIndex로 비교하는 원리인 것으로 보인다.
2. FName::Compare()
CompareFloat = TestFName.Compare(OtherFName);
// in UnrealNames.cpp
int32 FName::Compare( const FName& Other ) const
{
// ...
// Names match, check whether numbers match.
if (GetComparisonIndex() == Other.GetComparisonIndex())
{
return GetNumber() - Other.GetNumber();
}
// Names don't match. This means we don't even need to check numbers.
return CompareDifferentIdsAlphabetically(GetComparisonIndex(), Other.GetComparisonIndex());
//...
}
static int32 CompareDifferentIdsAlphabetically(FNameEntryId AId, FNameEntryId BId)
{
checkSlow(AId != BId);
FNamePool& Pool = GetNamePool();
FNameBuffer ABuffer, BBuffer;
FNameStringView AView = Pool.Resolve(AId).MakeView(ABuffer);
FNameStringView BView = Pool.Resolve(BId).MakeView(BBuffer);
// If only one view is wide, convert the ansi view to wide as well
if (AView.bIsWide != BView.bIsWide)
{
FNameStringView& AnsiView = AView.bIsWide ? BView : AView;
FNameBuffer& AnsiBuffer = AView.bIsWide ? BBuffer : ABuffer;
#ifndef WITH_CUSTOM_NAME_ENCODING
FPlatformMemory::Memcpy(AnsiBuffer.AnsiName, AnsiView.Ansi, AnsiView.Len * sizeof(ANSICHAR));
AnsiView.Ansi = AnsiBuffer.AnsiName;
#endif
ConvertInPlace<ANSICHAR, WIDECHAR>(AnsiBuffer.AnsiName, AnsiView.Len);
AnsiView.bIsWide = true;
}
int32 MinLen = FMath::Min(AView.Len, BView.Len);
if (int32 StrDiff = AView.bIsWide ? FCStringWide::Strnicmp(AView.Wide, BView.Wide, MinLen) :
FCStringAnsi::Strnicmp(AView.Ansi, BView.Ansi, MinLen))
{
return StrDiff;
}
return AView.Len - BView.Len;
}
Compare은 비교 값이 작으면 음수, 같으면 0, 크면 양수를 반환한다.
이때 ComparisonIndex로 같은 Names인지 체크하고 나머지 Number 비교를 한다.
만약 Names가 다르다면 알파벳 순서 비교를 통해 차이를 반환하는 것 같다.
1-3. 네임 테이블 검색
FName이 네임 테이블에 존재하는지 알고 싶지만, 테이블에 추가하기 싫은 경우에는 다음처럼 사용하면 된다.
if(FName(TEXT("pelvis"), FNAME_Find) != NAME_None )
{
// Do something
}
2. FText
언리얼 엔진(UE)에서 텍스트 현지화를 위한 주요 컴포넌트는 FText 클래스입니다. 이 클래스는 다음 기능을 제공하여 텍스트 현지화를 지원하므로 모든 사용자 대상 텍스트는 이 클래스를 사용해야 합니다.
- 현지화된 텍스트 리터럴 생성
- (자리표시자 패턴에서 텍스트를 생성하기 위한) 텍스트 포매팅
- 숫자에서 텍스트 생성
- 날짜 및 시간에서 텍스트 생성
- 대소문자 표기와 같은 파생 텍스트 생성
FText 에는 현지화되지 않거나 '컬처 무관' 텍스트를 생성하는 AsCultureInvariant 함수(또는 INVTEXT 매크로)도 있습니다. 이는 외부 API의 플레이어 이름을 사용자 인터페이스에 표시할 수 있는 이름으로 변환하는 것과 같은 작업에 유용합니다. FText::GetEmpty()를 사용하거나 FText()만 사용하여 빈 FText를 만들 수 있습니다.
UI와 Localization에 주로 사용되는 스트링 클래스인 것으로 보인다.
2-1. 변환
FText <-> FString 변환이 가능하다.
그러나 FText에는 현지화 데이터와 연결된 문자열이 포함되어 있기 때문에, FString으로 변환할 때 데이터 손실을 주의해야 한다.
// FString -> FText (Culture Invariant)
// 현지화되지 않은 Culture Invariant한 FText 인스턴스 생성
TestHUDText = FText::AsCultureInvariant(*TestHUDString);
// FString -> FText
// 현지화되지 않은 FText 인스턴스 생성. 추후 FText 프로퍼티에 할당하면 현지화 가능
TestHUDText = FText::FromString(*TestHUDString);
// FName -> FText ( == FName -> FString -> FText)
TestHUDText = FText::FromName(TestHUDName);
// FText -> FString
TestHUDString = TestText.ToString();
2-2. 비교
FText 데이터는 단순한 문자열보다 더 복잡하기 때문에 == 연산자 등의 비교를 지원하지 않는다.
대신, 다른 비교 함수들로 비교가 가능하다.
2-2-1. EqualTo
FText는 FTextComparison::ETextComparisoinLevel 값을 사용해 비교 규칙에 따라 비교를 진행한다.
내부적으로는 FText -> FString으로 변환 후 비교한다.
// in TextComparison.h
namespace ETextComparisonLevel
{
enum Type
{
Default, // Locale-specific Default
Primary, // Base
Secondary, // Accent
Tertiary, // Case
Quaternary, // Punctuation
Quinary // Identical
};
}
// in Text.h
bool FText::EqualTo(const FText& Other, const ETextComparisonLevel::Type ComparisonLevel) const
{
return FTextComparison::EqualTo(ToString(), Other.ToString(), ComparisonLevel);
}
FTextComparison의 EqualTo는 FTextComparsion::CompareTo로 비교하며, 이는 다시 ICUText.cpp / LegacyText.cpp로 구현이 나뉘어 있다.
ICUText.cpp의 구현은 아래와 같은데, 궁금한 사람은 세부 구현 내용을 찾아보면 될 것 같다.
int32 FTextComparison::CompareTo( const FString& A, const FString& B, const ETextComparisonLevel::Type ComparisonLevel )
{
const TSharedRef<const icu::Collator, ESPMode::ThreadSafe> Collator( FInternationalization::Get().GetCurrentLanguage()->Implementation->GetCollator(ComparisonLevel) );
// Create an iterator for 'A' so that we can interface with ICU
FStringView AView = A;
UCharIterator ADisplayStringICUIterator;
FICUTextCharacterIterator ADisplayStringIterator(AView);
uiter_setCharacterIterator(&ADisplayStringICUIterator, &ADisplayStringIterator);
// Create an iterator for 'B' so that we can interface with ICU
FStringView BView = B;
UCharIterator BDisplayStringICUIterator;
FICUTextCharacterIterator BDisplayStringIterator(BView);
uiter_setCharacterIterator(&BDisplayStringICUIterator, &BDisplayStringIterator);
UErrorCode ICUStatus = U_ZERO_ERROR;
const UCollationResult Result = Collator->compare(ADisplayStringICUIterator, BDisplayStringICUIterator, ICUStatus);
return Result;
}
2-2-2. EqualToCaseIgnored
위의 EqualTo에서 ETextComparisonLevel 값을 Secondary로 지정하여 호출한다.
bool FText::EqualToCaseIgnored(const FText& Other) const
{
return FTextComparison::EqualToCaseIgnored(ToString(), Other.ToString());
}
2-2-3. CompareTo
EqualTo와 마찬가지로 ETextComparisonLevel 값을 사용해 비교한다.
비교 후 알파벳 순 정렬 기준 더 작으면 음수, 동일하면 0, 더 크면 양수를 반환한다.
int32 FText::CompareTo(const FText& Other, const ETextComparisonLevel::Type ComparisonLevel) const
{
return FTextComparison::CompareTo(ToString(), Other.ToString(), ComparisonLevel);
}
2-2-4. CompareToCaseIgnored
위의 CompareTo 에서 ETextComparisonLevel 값을 Secondary로 지정하여 호출한다.
int32 FText::CompareToCaseIgnored(const FText& Other) const
{
return FTextComparison::CompareToCaseIgnored(ToString(), Other.ToString());
}
2-3. UI에서 사용
Slate 및 UMG에서의 텍스트 표시는 FText 사용을 권장하고 있다.
사용 예시는 아래와 같다.
// 텍스트를 포함할 새 FCanvasTextItem 인스턴스를 만듭니다.
FCanvasTextItem TextItem(FVector2D::ZeroVector, TestHUDText, BigFont, FLinearColor::Black);
// FCanvasTextItem에 텍스트를 추가합니다.
TextItem.Text = FText::Format(LOCTEXT("ExampleFText", "You currently have {0} health left."), CurrentHealth);
// FCanvas::DrawItem을 사용하여 화면에 텍스트를 그립니다.
Canvas->DrawItem(TextItem, 10.0f, 10.0f);
3. FString
FName이나 FText 와는 달리, FString 은 조작이 가능한 유일한 스트링 클래스입니다. 대소문자 변환, 부분문자열 발췌, 역순 등 사용가능한 메서드는 많습니다. FString 은 검색, 변경에 다른 스트링과의 비교도 가능합니다. 그러나 바로 그것이 FString 이 다른 불변의 스트링 클래스보다 비싸지는 이유입니다.
유일하게 조작이 가능하지만, 그만큼 비용을 잘 고려해서 구현이 필요하다. 주로 디버깅 테스트 출력 등의 용도로 쓰이는 것 같다.
3-1. 변환
FString <-> FName, FText가 가능하다.
마찬가지로 FText는 현지화 데이터 손실, FName은 대소문자 구분을 주의해야한다.
// From FString
// FString -> FName
// FName은 대소문자 구분이 없어 주의해야 한다.
TestHUDName = FName(*TestHUDString);
// FString -> FText
// 현지화 데이터 손실을 주의해야한다.
TestHUDText = FText::FromString(TestHUDString);
// To FString
// FName -> FString
// FName은 대소문자 구분이 없어 주의해야 한다.
TestHUDName = TestHUDText.ToString();
// FText -> FString
// 현지화 데이터 손실을 주의해야한다.
TestHUDString = TestHUDText.ToString();
추가로 숫자 및 기타 변수 <-> FString으로 변환이 가능하다.
| 변수 유형 | 스트링에서 변환 | 스트링 포맷 |
| float | FString::SanitizeFloat(FloatVariable); | |
| int | FString::FromInt(IntVariable); | |
| bool | InBool ? TEXT("true") : TEXT("false"); | 'true' 또는 'false' |
| FVector | VectorVariable.ToString(); | 'X= Y= Z=' |
| FVector2D | Vector2DVariable.ToString(); | 'X= Y=' |
| FRotator | RotatorVariable.ToString(); | 'P= Y= R=' |
| FLinearColor | LinearColorVariable.ToString(); | '(R=,G=,B=,A=)' |
| UObject | (InObj != NULL) ? InObj->GetName() : FString(TEXT("None")); | UObject FName |
| 변수 유형 | 스트링에서 변환 | 참고 |
| bool | TestHUDString.ToBool(); | |
| int | FCString::Atoi(*TestHUDString); | |
| float | FCString::Atof(*TestHUDString); |
3-2. 비교
== 연산자를 사용해서 FString // FString 비교나 FString // TCHAR*s 배열 비교가 가능하다.
내부적으로는 FString::Equals를 사용하며 ESearchCase enum을 통해 대소문자 비교를 할 건지 정할 수 있다.
// in UnrealString.h
// class: FString
UE_NODISCARD FORCEINLINE bool operator==(const FString& Rhs) const
{
return Equals(Rhs, ESearchCase::IgnoreCase);
}
// in CString.h
/** Determines case sensitivity options for string comparisons. */
namespace ESearchCase
{
enum Type
{
/** Case sensitive. Upper/lower casing must match for strings to be considered equal. */
CaseSensitive,
/** Ignore case. Upper/lower casing does not matter when making a comparison. */
IgnoreCase,
};
};
FString::Equals는 길이 먼저 비교 후 대소문자 구분에 따라 FCString::Strcmp, FCString::Stricmp로 비교를 진행한다.
// in UnrealString.h
/**
* Lexicographically tests whether this string is equivalent to the Other given string
*
* @param Other The string test against
* @param SearchCase Whether or not the comparison should ignore case
* @return true if this string is lexicographically equivalent to the other, otherwise false
*/
UE_NODISCARD FORCEINLINE bool Equals(const FString& Other, ESearchCase::Type SearchCase = ESearchCase::CaseSensitive) const
{
int32 Num = Data.Num();
int32 OtherNum = Other.Data.Num();
if (Num != OtherNum)
{
// Handle special case where FString() == FString("")
return Num + OtherNum == 1;
}
else if (Num > 1)
{
if (SearchCase == ESearchCase::CaseSensitive)
{
return FCString::Strcmp(Data.GetData(), Other.Data.GetData()) == 0;
}
else
{
return FCString::Stricmp(Data.GetData(), Other.Data.GetData()) == 0;
}
}
return true;
}
== 연산자 외에 <, >, <=, >= 등의 비교도 있으니 세부 구현 내용을 찾아보면 될 것 같다.
3-3. 검색
검색은 1) Conatins() 2) Find() 두 가지 방법이 있다.
두 방법 모두 FString에서 서브 스트링이 있는지 찾으며 ESearchCase(대소문자 구분), ESearchDir(검색 방향)을 지정할 수 있다.
3-3-1. Contains
Contains는 서브스트링을 찾았는지 여부를 true, false로 반환한다.
// Example
TestHUDString.Contains(TEXT("Test"), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
Contains는 내부적으로 Find 함수를 호출해 사용한다.
// in UnrealString.h
// Class: FString
/**
* Returns whether this string contains the specified substring.
*
* @param SubStr Text to search for
* @param SearchCase Indicates whether the search is case sensitive or not ( defaults to ESearchCase::IgnoreCase )
* @param SearchDir Indicates whether the search starts at the beginning or at the end ( defaults to ESearchDir::FromStart )
* @return Returns whether the string contains the substring. If the substring is empty, returns true.
**/
UE_NODISCARD FORCEINLINE bool Contains(const TCHAR* SubStr, ESearchCase::Type SearchCase = ESearchCase::IgnoreCase,
ESearchDir::Type SearchDir = ESearchDir::FromStart) const
{
return Find(SubStr, SearchCase, SearchDir) != INDEX_NONE;
}
3-3-2. Find
Find는 처음 찾은 서브스트링 인덱스를 반환한다.
검색을 시작할 인덱스를 지정할 수도 있으며, 찾지 못하는 경우 -1를 반환한다.
// Example
TestHUDString.Find(TEXT("test"), ESearchCase::CaseSensitive, ESearchDir::FromEnd, 10);
// in UnrealString.h
// Class: FString
/**
* Searches the string for a substring, and returns index into this string of the first found instance. Can search
* from beginning or end, and ignore case or not. If substring is empty, returns clamped StartPosition.
*
* @param SubStr The string array of TCHAR to search for
* @param StartPosition The start character position to search from. See note below.
* @param SearchCase Indicates whether the search is case sensitive or not
* @param SearchDir Indicates whether the search starts at the beginning or at the end.
*/
UE_NODISCARD int32 Find(const TCHAR* SubStr, ESearchCase::Type SearchCase = ESearchCase::IgnoreCase,
ESearchDir::Type SearchDir = ESearchDir::FromStart, int32 StartPosition = INDEX_NONE) const
{
return SubStr ? Find(SubStr, FCString::Strlen(SubStr), SearchCase, SearchDir, StartPosition) : INDEX_NONE;
}
Find 내부 구현을 보면, End -> Start 탐색은 브루트포스로 처리하는 것을 볼 수가 있다.
Start -> End 탐색은 FCString::Strnsrt(Strnistr if CaseSensitive)에 위임한다.
// in String.cpp
int32 FString::Find(const TCHAR* SubStr, int32 SubStrLen, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir, int32 StartPosition) const
{
checkf(SubStrLen >= 0, TEXT("Invalid SubStrLen: %d"), SubStrLen);
if (SearchDir == ESearchDir::FromStart)
{
const TCHAR* Start = **this;
int32 RemainingLength = Len();
if (StartPosition != INDEX_NONE && RemainingLength > 0)
{
const TCHAR* End = Start + RemainingLength;
Start += FMath::Clamp(StartPosition, 0, RemainingLength - 1);
RemainingLength = UE_PTRDIFF_TO_INT32(End - Start);
}
const TCHAR* Tmp = SearchCase == ESearchCase::IgnoreCase
? FCString::Strnistr(Start, RemainingLength, SubStr, SubStrLen)
: FCString::Strnstr(Start, RemainingLength, SubStr, SubStrLen);
return Tmp ? UE_PTRDIFF_TO_INT32(Tmp-**this) : INDEX_NONE;
}
else
{
// if ignoring, do a onetime ToUpper on both strings, to avoid ToUppering multiple
// times in the loop below
if ( SearchCase == ESearchCase::IgnoreCase)
{
return ToUpper().Find(FString(SubStrLen, SubStr).ToUpper(), ESearchCase::CaseSensitive, SearchDir, StartPosition);
}
else
{
const int32 SearchStringLength=FMath::Max(1, SubStrLen);
if (StartPosition == INDEX_NONE || StartPosition >= Len())
{
StartPosition = Len();
}
for (int32 i = StartPosition - SearchStringLength; i >= 0; i--)
{
int32 j;
for (j=0; j != SubStrLen; j++)
{
if ((*this)[i+j]!=SubStr[j])
{
break;
}
}
if (j == SubStrLen)
{
return i;
}
}
return INDEX_NONE;
}
}
}
FCString::Strnistr 내부 구현을 살펴보면 마찬가지로 브루트포스 방식으로 처리됨을 확인할 수 있다.
// in CString.h
template <typename T>
const typename TCString<T>::CharType* TCString<T>::Strnistr(const CharType* Str, int32 InStrLen, const CharType* Find, int32 FindLen)
{
if (FindLen <= 0)
{
checkf(FindLen >= 0, TEXT("Invalid FindLen: %d"), FindLen);
return Str;
}
if (InStrLen < FindLen)
{
checkf(InStrLen >= 0, TEXT("Invalid InStrLen: %d"), InStrLen);
return nullptr;
}
// get upper-case first letter of the find string (to reduce the number of full strnicmps)
CharType FindInitial = TChar<CharType>::ToUpper(*Find);
// Set FindSuffix,FindSuffixLength to the characters of Find after the first letter
int32 FindSuffixLength = FindLen - 1;
const CharType* FindSuffix = Find + 1;
// while the length of the remaining string is >= FindLen
const CharType* StrLastChance = Str + InStrLen - FindLen;
while (Str <= StrLastChance)
{
CharType StrChar = *Str++;
// make sure it's upper-case
StrChar = TChar<CharType>::ToUpper(StrChar);
// if it matches the first letter of the find string, do a case-insensitive string compare for the length of the find string
if (StrChar == FindInitial && !Strnicmp(Str, FindSuffix, FindSuffixLength))
{
// if we found the string, then return a pointer to the beginning of it in the search string
return Str - 1;
}
}
// if nothing was found, return nullptr
return nullptr;
}
FString 비교 시 성능이 중요한 경우 KMP 등의 문자열 처리 알고리즘을 추가로 구현할 필요가 있어보인다.
3.4. 접합
FString에 FString을 더할 수 있으며, +=과 + 연산자로 가능하다.
// += 연산자
StringResult += AddedString;
// + 연산자
StringResult = AddedString1 + AddedString2;
+= 내부 구현은 ApeendChars 함수를 호출해 진행되고 있다.
FString이 TCHAR* 배열로 구현되어 있어 FString 추가 역시 CHAR 배열에 글자를 추가하는 방식으로 작성되어 있다.
// in UnrealString.h
/** Append a string and return a reference to this */
template <typename StrType>
FORCEINLINE auto operator+=(StrType&& Str) -> decltype(Append(Forward<StrType>(Str)))
{
return Append(Forward<StrType>(Str));
}
// in String.cpp
void FString::AppendChars(const ANSICHAR* Str, int32 Count)
{
CheckInvariants();
AppendCharacters(Data, Str, Count);
}
template<typename CharType>
void AppendCharacters(TArray<TCHAR>& Out, const CharType* Str, int32 Count)
{
check(Count >= 0);
if (!Count)
{
return;
}
checkSlow(Str);
int32 OldEnd = Out.Num();
// Try to reserve enough space by guessing that the new length will be the same as the input length.
// Include an extra gap for a null terminator if we don't already have a string allocated
Out.AddUninitialized(Count + (OldEnd ? 0 : 1));
OldEnd -= OldEnd ? 1 : 0;
TCHAR* Dest = Out.GetData() + OldEnd;
// Try copying characters to end of string, overwriting null terminator if we already have one
TCHAR* NewEnd = FPlatformString::Convert(Dest, Count, Str, Count);
if (!NewEnd)
{
// If that failed, it will have meant that conversion likely contained multi-code unit characters
// and so the buffer wasn't long enough, so calculate it properly.
int32 Length = FPlatformString::ConvertedLength<TCHAR>(Str, Count);
// Add the extra bytes that we need
Out.AddUninitialized(Length - Count);
// Restablish destination pointer in case a realloc happened
Dest = Out.GetData() + OldEnd;
NewEnd = FPlatformString::Convert(Dest, Length, Str, Count);
checkSlow(NewEnd);
}
else
{
int32 NewEndIndex = (int32)(NewEnd - Dest);
if (NewEndIndex < Count)
{
Out.SetNumUninitialized(OldEnd + NewEndIndex + 1, /*bAllowShrinking=*/false);
}
}
// (Re-)establish the null terminator
*NewEnd = '\0';
}
// CHAR* 타입에 따라 ANSICHAR*, WIDECHAR*, UCS2CHAR*, UTF8CHAR*로 각각 구현되어 있음.
3-5. Printf
C에서 자주 쓰던 Printf는 언리얼에서 FString::Printf로 쓸 수 있다.
디버깅 메시지로 자주 사용하며 아래 예시처럼 작성한다.
FString AShooterHUD::GetTimeString(float TimeSeconds)
{
// 분과 초만 관련이 있습니다.
const int32 TotalSeconds = FMath::Max(0, FMath::TruncToInt(TimeSeconds) % 3600);
const int32 NumMinutes = TotalSeconds / 60;
const int32 NumSeconds = TotalSeconds % 60;
const FString TimeDesc = FString::Printf(TEXT("%02d:%02d"), NumMinutes, NumSeconds);
return TimeDesc;
}
참고 사이트
언리얼 엔진의 스트링 처리 | 언리얼 엔진 5.3 문서 | Epic Developer Community
FName, FText, FString에 대한 레퍼런스 가이드와 언리얼 엔진에서 사용할 수 있는 문자열 클래스에 대한 개요입니다.
dev.epicgames.com
FName
언리얼 엔진의 FName | 언리얼 엔진 5.3 문서 | Epic Developer Community
언리얼 엔진에서 FName 제작, 변환 및 비교를 위한 참고 자료입니다.
dev.epicgames.com
FText
언리얼 엔진의 FText | 언리얼 엔진 5.3 문서 | Epic Developer Community
언리얼 엔진의 FText를 통한 생성, 변환, 비교 등에 대한 레퍼런스입니다.
dev.epicgames.com
FString
'게임개발 > Unreal Engine' 카테고리의 다른 글
| [UE5] 언리얼의 Multi-threading (0) | 2026.02.16 |
|---|---|
| [UE5] Replicate Montage Multicast in C++ not working (0) | 2025.03.01 |
| [UE5] C++ AActor::Destroy() not working in BeginPlay() (0) | 2025.02.06 |
| [UE5] Unreal Engine C++ API References (2) | 2024.08.20 |
| [UE5] Root Motion with custom mesh not working (0) | 2024.07.17 |