728x90
1. 개요
오늘은 언리얼 엔진의 문자 FString, FText, FName에 대해 알아보겠습니다.
2. 아니 왜 나눠놨을까?
나눈 이유는 매우 단순합니다.
각 사용에 맞게 쓰게 할려고 나눴습니다.
다만, 셋 다 TCHAR 배열을 사용하며 TCHAR는 2바이트 유니코드 문자입니다.
그럼 아래에서 각각에 역할을 알아보겠습니다.
3. FString
세 가지 문자열 타입 중 유일하게 수정이 가능하다는게 핵심
1. FString의 주요 역할 (Role)
- 문자열 조립 및 포맷팅:
- 여러 변수(숫자, 이름 등)를 섞어서 문장을 만들 때 사용합니다.
- 예: "Player HP: " + FString::FromInt(Health)
- 파일 경로 및 URL 처리:
- 디렉토리 경로를 합치거나, 확장자를 떼어내는 등의 작업에 쓰입니다.
- 예: FPaths::Combine() 함수들은 내부적으로 FString을 반환합니다.
- 데이터 파싱 (JSON, XML 등):
- 서버에서 받은 데이터를 쪼개거나 분석할 때 사용합니다.
- 디버깅 및 로그:
- UE_LOG 매크로는 FString 형식을 주로 사용합니다.
- 변환의 다리 (Bridge):
- FName과 FText는 서로 직접 변환이 불가능한 경우가 많습니다. 이때 FString을 거쳐서 변환합니다.
- FName → FString → FText
2. 장점 (Pros)
- 수정 가능 (Mutable):
- FName이나 FText와 달리, 생성된 후에 글자를 추가하거나, 중간을 자르거나, 대소문자를 바꾸는 등 내용을 변경할 수 있습니다.
- 방대한 조작 함수 제공:
- 언리얼 엔진은 FString을 위해 엄청나게 많은 편의 함수를 제공합니다.
- Left(), Right(), Mid(): 문자열 자르기
- Printf(): C언어 스타일의 포맷팅 (FString::Printf(TEXT("Score: %d"), 100))
- ParseIntoArray(): 쉼표 등으로 문자열 분리하기
- Replace(): 특정 단어 교체하기
- Contains(): 특정 단어 포함 여부 확인
- 직관적인 사용:
- C++ 표준의 std::string이나 C#의 String과 사용법이 비슷하여 배우기 쉽습니다.
3. 단점 (Cons)
- 무거운 성능 (Memory Overhead):
- FString은 동적 배열(TArray<TCHAR>) 형태입니다.
- 문자열을 합치거나 수정할 때마다 힙(Heap) 메모리 할당과 해제가 빈번하게 발생할 수 있어, FName에 비해 훨씬 무겁습니다.
- 게임 프레임마다 FString을 계속 생성하고 삭제하면 성능 저하(GC는 아니지만 메모리 파편화 및 오버헤드)의 원인이 됩니다.
- 느린 비교 속도:
- 두 FString이 같은지 비교(==)할 때, 글자 하나하나를 순서대로 비교(O(N)) 합니다.
- 단순 숫자 비교(O(1))만 하는 FName보다 훨씬 느립니다. 따라서 맵에 있는 수만 개의 액터를 이름으로 찾을 때 FString을 쓰면 안 됩니다.
- 다국어 지원 불가:
- 단순한 "글자들의 나열"일 뿐이므로, FText처럼 자동 번역(Localization) 테이블과 연동되지 않습니다.
4. 언제 쓰고, 언제 쓰지 말아야 할까?
✅ 이럴 때 쓰세요 (Good)
- 복잡한 문자열을 만들 때: "공격력: 50 / 방어력: 20" 처럼 숫자와 글자를 합칠 때.
- 파일 입출력: 저장 파일 이름이나 경로를 만들 때.
- 네트워크 통신: 서버에 보낼 JSON 패킷을 만들 때.
- 디버그 로그: UE_LOG로 값을 찍어볼 때.
❌ 이럴 때 쓰지 마세요 (Bad)
- 자주 호출되는 비교 구문: if (MyActor->GetName() == "Enemy") (매 프레임 실행되는 Tick 함수 등에서는 FName을 써야 함)
- UI 표시: 플레이어에게 보여줄 퀘스트 이름, 아이템 설명 등은 번역이 필요하므로 FText를 써야 합니다.
- 식별자: 본(Bone) 이름, 소켓 이름, 태그 등은 FName을 써야 합니다.
// [생성 및 조작]
FString UserName = "Vic";
int32 Score = 100;
// Printf를 이용한 포맷팅 (가장 많이 씀)
FString ResultMessage = FString::Printf(TEXT("User %s has score %d"), *UserName, Score);
// 문자열 붙이기
ResultMessage += TEXT(" - Good Job!");
// [변환]
// FString -> FName (데이터 손실 주의: 대소문자 구분 없어짐)
FName UserID = FName(*UserName);
// FString -> FText (UI 표시에 사용)
FText UI_Message = FText::FromString(ResultMessage);
4. FText
FText는 철저하게 "화면 표시와 현지화"를 위해 존재하는 문자열
① 다국어 지원 (Localization / 번역)
가장 중요한 역할입니다. 한국어 윈도우에서는 "게임 시작", 영어 윈도우에서는 "Start Game"으로 자동 변환해주는 기능을 담당합니다.
- 내부적으로 "네임스페이스(Namespace)", "키(Key)", "원본 문자열(Source String)" 정보를 모두 가지고 있어, 언어 설정에 맞는 텍스트를 스트링 테이블에서 찾아옵니다.
② 고급 포맷팅 (Text Formatting)
숫자나 날짜를 사람이 읽기 편하게 자동으로 바꿔줍니다.
- 숫자: 12345 → 12,345 (콤마 자동 삽입)
- 날짜: 국가별 날짜 표기법 자동 적용 (YYYY/MM/DD vs MM/DD/YYYY)
- 퍼센트: 0.5 → 50%
③ 문법적 처리
언어에 따라 달라지는 문법(복수형 처리 등)을 지원합니다.
- 예: 영어에서 1 Apple, 2 Apples 처럼 숫자에 따라 단어가 바뀌는 것을 처리할 수 있습니다.
2. 장점 (Pros)
- 완벽한 현지화 (Localization Ready):
- 개발 단계에서 FText로 만들어두면, 나중에 엑셀이나 번역 툴(Poedit 등)을 통해 코드 수정 없이 언어만 갈아끼울 수 있습니다.
- 가독성 향상:
- FText::AsNumber(1234567)이라고만 써도 화면에 1,234,567로 예쁘게 나옵니다. FString으로 하려면 콤마 찍는 알고리즘을 직접 짜야 합니다.
- 메모리 안전성:
- FText는 기본적으로 불변(Immutable)에 가깝게 설계되어 있어, 복사 비용을 줄이는(Copy-on-write) 최적화가 내부적으로 되어 있습니다.
3. 단점 (Cons)
- 매우 무거움 (Heavy):
- 단순 글자뿐만 아니라 번역 키, 포맷팅 정보 등을 담고 있어서 FString보다 메모리를 훨씬 많이 차지합니다.
- 따라서 대량의 데이터 처리나 루프 안에서는 절대 쓰면 안 됩니다.
- 느린 연산 속도:
- 문자열 비교나 검색이 매우 느립니다. 내부 로직용(예: 아이템 ID 비교)으로는 부적합합니다.
- 전송 불가:
- 네트워크(서버-클라이언트)로 FText 자체를 보내는 것은 비효율적이며, 보통은 FString이나 FName(ID)을 보낸 뒤 받는 쪽에서 FText로 변환하여 보여줍니다.
4. 언제 쓰고, 언제 쓰지 말아야 할까?
✅ 언제 써야 할까? (Good Case)
"이 글자를 플레이어가 읽어야 하는가?" 라는 질문에 YES라면 무조건 FText입니다.
- UI (유저 인터페이스) 표시
- 버튼 이름, 메뉴 타이틀, 퀘스트 설명, 아이템 이름 등 HUD나 위젯에 들어가는 모든 글자.
- 언리얼의 UMG(위젯 블루프린트)의 텍스트 블록은 기본적으로 FText를 받습니다.
- 현지화(Localization)가 필요한 경우
- 한국어, 영어, 일본어 등 언어 설정에 따라 바뀌어야 하는 모든 문구.
- NSLOCTEXT나 스트링 테이블을 사용해 키(Key) 값으로 관리해야 하는 경우.
- 숫자 및 날짜 포맷팅 (예쁘게 보여주기)
- 숫자에 콤마 찍기: 12345 → 12,345 (FText::AsNumber)
- 퍼센트 표시: 0.15 → 15% (FText::AsPercent)
- 화폐 단위: $10.50, ₩10,000 (FText::AsCurrency)
- 시간 표시: 13:00 vs 1:00 PM 등 국가별 시간 표기법 자동 적용.
- 문장 조립 (FText::Format)
- 어순이 다른 언어를 처리할 때 필수적입니다.
- 영어: "Kill {Count} Enemies" (동사 + 숫자 + 명사)
- 한국어: "{Count}명의 적을 처치하세요" (숫자 + 명사 + 동사)
- FString::Printf는 순서가 고정되지만, FText::Format은 {이름} 기반이라 어순이 바뀌어도 문제없습니다.
❌ 언제 쓰지 말아야 할까? (Bad Case)
"개발자나 컴퓨터가 처리하는 데이터인가?" 라면 쓰지 마세요.
- 내부 식별자 (ID) 비교
- 특정 액터를 찾거나 상태를 구분할 때 FText를 비교(==)하면 안 됩니다.
- 이유: 매우 느리고, 언어 설정에 따라 글자가 바뀌면 로직이 꼬입니다. (한국어에서는 "공격", 영어에서는 "Attack"이면 비교 불가)
- 👉 대안: FName (가장 빠름) 또는 enum을 사용하세요.
- 파일 경로 및 저장
- C:/MyGame/SaveData.sav 같은 경로를 FText로 다루지 마세요.
- 이유: 운영체제 파일 시스템은 현지화 기능이 필요 없습니다.
- 👉 대안: FString을 사용하세요.
- 네트워크 패킷 전송
- 서버에서 클라이언트로 메시지를 보낼 때, 완성된 FText를 통째로 보내는 것은 비효율적입니다.
- 이유: 데이터 크기가 크고 무겁습니다.
- 👉 대안: ID(FName/Integer)나 수치만 보내고, 클라이언트가 받아서 FText로 변환해 보여주는 것이 정석입니다.
- 문자열 가공 (파싱, 자르기)
- 문자열을 쪼개거나(Split), 특정 단어를 찾거나(Find), 뒤집거나 하는 조작.
- 이유: FText는 이런 기능이 거의 없거나 매우 제한적입니다.
- 👉 대안: FString으로 변환해서 작업하세요.
// ====================================================
// 1. FName: 식별자 (ID)
// ====================================================
// 용도: 내부적으로 이 캐릭터가 누구인지 구분할 때 사용 (비교 속도 최상)
FName CharacterID = TEXT("Hero_Warrior");
FName TargetSocket = TEXT("Hand_R");
// FName은 '==' 비교가 숫자 비교만큼 빠릅니다. (매 프레임 체크해도 부담 없음)
if (CharacterID == "Hero_Warrior")
{
// 전사 캐릭터 로직 수행...
}
// ====================================================
// 2. FString: 문자열 조작 (로직)
// ====================================================
// 용도: 파일 경로를 만들거나, 로그를 남기거나, 서버에 데이터를 보낼 때
FString BasePath = TEXT("/Game/Characters/Meshes/");
FString FileName = TEXT("SK_Warrior_v2");
// 문자열 합치기 (경로 생성)
// FString은 수정(Mutable)이 가능하므로 경로를 조립하기 좋습니다.
FString FullPath = BasePath + FileName + TEXT(".uasset");
// 로그 출력 (개발자용) - * 연산자를 붙여서 TCHAR* 로 변환해야 함
UE_LOG(LogTemp, Log, TEXT("Loading Asset form Path: %s"), *FullPath);
// ====================================================
// 3. FText: UI 표시 (플레이어용)
// ====================================================
// 용도: 화면에 "전사(Warrior)"라고 예쁘게 띄워줄 때 (자동 번역 지원)
// NSLOCTEXT("네임스페이스", "키", "원본글자")
FText DisplayName = NSLOCTEXT("CharacterNames", "WarriorName", "Mighty Warrior");
int32 CurrentHP = 1500;
int32 MaxHP = 2000;
// 복잡한 문장 만들기 (FText::Format 사용)
// 포맷: "{Name}의 체력은 {Current} / {Max} 입니다."
// 한국어, 영어 어순이 달라도 {Key}값으로 찾으므로 번역에 완벽 대응
FText UI_Format = NSLOCTEXT("HUD", "HP_Message", "{Name} is currently at {Current} / {Max} HP.");
FFormatNamedArguments Args;
Args.Add("Name", DisplayName); // FText 넣기
Args.Add("Current", FText::AsNumber(CurrentHP)); // 숫자를 '1,500' 형태의 FText로 변환
Args.Add("Max", FText::AsNumber(MaxHP)); // 숫자를 '2,000' 형태의 FText로 변환
// 최종 UI 메시지 생성
FText FinalMessage = FText::Format(UI_Format, Args);
// 결과(화면 출력): "Mighty Warrior is currently at 1,500 / 2,000 HP."
// 위젯의 TextBlock에 넣기: MyTextBlock->SetText(FinalMessage);
5. FName
FName은 컴퓨터가 데이터를 찾기 위한 주민등록번호(ID) 라고 생각하면 무방하다.
1. FName의 핵심 역할 (Role)
① 고유 식별자 (Unique Identifier)
시스템 내부에서 에셋, 본(Bone), 소켓, 액터 태그 등을 구별하는 ID 카드 역할을 합니다.
- 시스템 전역의 **"네임 테이블(Name Table)"**에 문자열을 딱 한 번만 저장하고, 변수에는 그 문자열이 저장된 **위치(Index/숫자)**만 들고 있습니다.
② 키 값 (Key)
TMap 같은 자료구조에서 데이터를 찾을 때 사용하는 키(Key)로 최적화되어 있습니다.
- 문자열 자체를 비교하는 게 아니라, 내부적으로 숫자(해시값)만 비교하기 때문에 검색 속도가 압도적으로 빠릅니다.
2. 장점 (Pros)
- 초고속 비교 (Fast Comparison):
- 두 FName이 같은지 비교할 때(==), 글자 하나하나를 비교하지 않고 내부 숫자(Index)만 비교합니다.
- if (NameA == NameB)는 사실상 if (1024 == 1024)와 같은 정수 비교 연산이라 CPU 비용이 거의 0에 가깝습니다.
- 메모리 절약 (Lightweight):
- "Pelvis"라는 FName을 100만 개의 변수에 저장해도, 실제 메모리에는 "Pelvis"라는 글자가 딱 한 번만 저장됩니다. 나머지 100만 개는 아주 작은 숫자 데이터만 가집니다.
- 대소문자 무시 (Case-Insensitive):
- FName("Apple")과 FName("apple")은 내부적으로 같은 것으로 취급합니다. 실수로 대소문자를 틀려도 ID를 잘 찾아줍니다.
3. 단점 (Cons)
- 수정 불가 (Immutable):
- 한번 생성된 FName은 절대 변경할 수 없습니다. 글자를 추가하거나 자르는 기능이 아예 없습니다.
- 바꾸려면 FString으로 변환해서 고친 뒤 다시 FName으로 만들어야 합니다.
- 생성 비용 발생:
- 처음 FName("NewString")을 만들 때, 이 문자열이 네임 테이블에 이미 있는지 검색하고, 없으면 등록하는 과정이 필요합니다. 따라서 반복문 안에서 매번 새로운 FName을 생성하는 것은 피해야 합니다.
- 데이터 손실:
- 대소문자를 구분하지 않기 때문에, 대소문자 구분이 중요한 데이터(암호 등)에는 사용할 수 없습니다.
4. 언제 쓰고, 언제 쓰지 말아야 할까?
✅ 언제 써야 할까? (Good Case)
- 시스템이 미리 정해둔 이름들:
- 스켈레탈 메시의 본(Bone) 이름 ("Head", "Spine")
- 무기 장착용 소켓(Socket) 이름 ("Hand_RSocket")
- 머티리얼 파라미터 이름 ("Color", "Roughness")
- 맵(Map)의 키(Key):
- TMap<FName, int32> 처럼 데이터를 검색할 때 쓰는 키 값.
- 액터 태그:
- Actor->Tags.Add(TEXT("Enemy"));
❌ 언제 쓰지 말아야 할까? (Bad Case)
- 사용자 입력: 플레이어가 입력한 채팅, 아이디 등 (동적으로 계속 바뀌는 문자열).
- 문자열 조립: 경로를 합치거나 파일 이름을 만들 때 (FString 사용).
- UI 표시: 화면에 보여줄 때 (FText 사용).
// ====================================================
// 1. FName 생성 및 비교 (가장 흔한 패턴)
// ====================================================
// TEXT("Head")는 컴파일 타임에 처리되므로 매우 빠릅니다.
// 비교 연산(==)은 숫자 비교라 번개처럼 빠릅니다.
if (BoneName == TEXT("Head"))
{
ApplyCriticalDamage();
}
else if (BoneName == TEXT("Leg_L") || BoneName == TEXT("Leg_R"))
{
ApplySlowEffect();
}
// ====================================================
// 2. TMap의 Key로 사용 (검색 최적화)
// ====================================================
// 스킬 이름을 Key로, 데미지를 Value로 저장
TMap<FName, float> SkillDamageTable;
// 데이터 추가
SkillDamageTable.Add(TEXT("Fireball"), 100.0f);
SkillDamageTable.Add(TEXT("IceBolt"), 80.0f);
// 데이터 검색 (FString으로 검색하는 것보다 훨씬 빠름)
if (SkillDamageTable.Contains(TEXT("Fireball")))
{
float Dmg = SkillDamageTable[TEXT("Fireball")];
}
// ====================================================
// 3. 변환 (주의: 비용 발생)
// ====================================================
// FString -> FName (테이블 검색/등록 과정 발생)
FString DynamicString = "Combo_A";
FName ConvertedName = FName(*DynamicString);
// FName -> FString (UI에 찍어보거나 로그 남길 때)
// .ToString()은 새로운 문자열 메모리를 할당하므로, 디버깅용 외엔 자제
UE_LOG(LogTemp, Warning, TEXT("Hit Bone: %s"), *BoneName.ToString());
6. 요약
다음은 FString, FText, FName의 요약 표입니다.
| 비교 항목 | FName (이름/식별자) | FText (UI 표시용) | FString (로직/조작용) |
| 핵심 개념 | 주민등록번호 (ID) | 보여주기 위한 글자조작 | 가능한 문자열 |
| 주요 용도 | 에셋 이름, 본(Bone), 소켓, 태그, TMap의 Key |
UI, HUD, 대사, 알림 메시지, 숫자/날짜 포맷팅 |
파일 경로, URL, 파싱, 로그, 데이터 조립 |
| 비교 속도 | 최상 (O(1)) 단순 숫자 비교 |
느림 복잡한 구조체 비교 |
보통 (O(N)) 글자 하나하나 비교 |
| 메모리 | 매우 작음 (중복 제거된 인덱스만 저장) |
무거움 (본문 + 키 + 네임스페이스) |
가변적 (글자 수만큼 동적 할당) |
| 값 변경 | 불가능 (Immutable) | 불가능 (포맷팅으로 새로 생성) | 가능 (Mutable) |
| 다국어(번역) | 지원 안 함 | 완벽 지원 (Localization) | 지원 안 함 |
| 대소문자 | 구분 안 함 (무시) | 구분 함 | 구분 함 |
| 네트워크/저장 | ID 전송용으로 적합 | 비추천 (무거움) | 데이터 저장/전송에 적합 |
'개발자 면접 공부 > 언리얼 엔진' 카테고리의 다른 글
| 언리얼 UAT, UBT, UHT, UnrealPak 이해 (0) | 2025.12.05 |
|---|---|
| 언리얼 엔진의 스마트 포인터 (0) | 2025.11.28 |
| 언리얼 엔진 Actor, Pawn, Character 차이 (0) | 2025.11.26 |
| 언리얼 엔진 액터의 생명주기(액터 스폰) (1) | 2024.03.21 |
| 언리얼 엔진 액터의 생명주기(에디터에서 플레이) (0) | 2024.03.20 |