개발자 면접 공부/언리얼 엔진

언리얼 엔진 (FString, FText, FName) 비교

chogyujin 2025. 12. 2. 16:13
728x90

1. 개요

오늘은 언리얼 엔진의 문자 FString, FText, FName에 대해 알아보겠습니다.


2. 아니 왜 나눠놨을까?

나눈 이유는 매우 단순합니다.

각 사용에 맞게 쓰게 할려고 나눴습니다.

다만, 셋 다 TCHAR 배열을 사용하며 TCHAR는 2바이트 유니코드 문자입니다.

그럼 아래에서 각각에 역할을 알아보겠습니다.


3. FString

세 가지 문자열 타입 중 유일하게 수정이 가능하다는게 핵심

1. FString의 주요 역할 (Role)

  1. 문자열 조립 및 포맷팅:
    • 여러 변수(숫자, 이름 등)를 섞어서 문장을 만들 때 사용합니다.
    • 예: "Player HP: " + FString::FromInt(Health)
  2. 파일 경로 및 URL 처리:
    • 디렉토리 경로를 합치거나, 확장자를 떼어내는 등의 작업에 쓰입니다.
    • 예: FPaths::Combine() 함수들은 내부적으로 FString을 반환합니다.
  3. 데이터 파싱 (JSON, XML 등):
    • 서버에서 받은 데이터를 쪼개거나 분석할 때 사용합니다.
  4. 디버깅 및 로그:
    • UE_LOG 매크로는 FString 형식을 주로 사용합니다.
  5. 변환의 다리 (Bridge):
    • FName과 FText는 서로 직접 변환이 불가능한 경우가 많습니다. 이때 FString을 거쳐서 변환합니다.
    • FName → FString → FText

2. 장점 (Pros)

  1. 수정 가능 (Mutable):
    • FName이나 FText와 달리, 생성된 후에 글자를 추가하거나, 중간을 자르거나, 대소문자를 바꾸는 등 내용을 변경할 수 있습니다.
  2. 방대한 조작 함수 제공:
    • 언리얼 엔진은 FString을 위해 엄청나게 많은 편의 함수를 제공합니다.
    • Left(), Right(), Mid(): 문자열 자르기
    • Printf(): C언어 스타일의 포맷팅 (FString::Printf(TEXT("Score: %d"), 100))
    • ParseIntoArray(): 쉼표 등으로 문자열 분리하기
    • Replace(): 특정 단어 교체하기
    • Contains(): 특정 단어 포함 여부 확인
  3. 직관적인 사용:
    • C++ 표준의 std::string이나 C#의 String과 사용법이 비슷하여 배우기 쉽습니다.

3. 단점 (Cons)

  1. 무거운 성능 (Memory Overhead):
    • FString은 동적 배열(TArray<TCHAR>) 형태입니다.
    • 문자열을 합치거나 수정할 때마다 힙(Heap) 메모리 할당과 해제가 빈번하게 발생할 수 있어, FName에 비해 훨씬 무겁습니다.
    • 게임 프레임마다 FString을 계속 생성하고 삭제하면 성능 저하(GC는 아니지만 메모리 파편화 및 오버헤드)의 원인이 됩니다.
  2. 느린 비교 속도:
    • 두 FString이 같은지 비교(==)할 때, 글자 하나하나를 순서대로 비교(O(N)) 합니다.
    • 단순 숫자 비교(O(1))만 하는 FName보다 훨씬 느립니다. 따라서 맵에 있는 수만 개의 액터를 이름으로 찾을 때 FString을 쓰면 안 됩니다.
  3. 다국어 지원 불가:
    • 단순한 "글자들의 나열"일 뿐이므로, 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)

  1. 완벽한 현지화 (Localization Ready):
    • 개발 단계에서 FText로 만들어두면, 나중에 엑셀이나 번역 툴(Poedit 등)을 통해 코드 수정 없이 언어만 갈아끼울 수 있습니다.
  2. 가독성 향상:
    • FText::AsNumber(1234567)이라고만 써도 화면에 1,234,567로 예쁘게 나옵니다. FString으로 하려면 콤마 찍는 알고리즘을 직접 짜야 합니다.
  3. 메모리 안전성:
    • FText는 기본적으로 불변(Immutable)에 가깝게 설계되어 있어, 복사 비용을 줄이는(Copy-on-write) 최적화가 내부적으로 되어 있습니다.

3. 단점 (Cons)

  1. 매우 무거움 (Heavy):
    • 단순 글자뿐만 아니라 번역 키, 포맷팅 정보 등을 담고 있어서 FString보다 메모리를 훨씬 많이 차지합니다.
    • 따라서 대량의 데이터 처리나 루프 안에서는 절대 쓰면 안 됩니다.
  2. 느린 연산 속도:
    • 문자열 비교나 검색이 매우 느립니다. 내부 로직용(예: 아이템 ID 비교)으로는 부적합합니다.
  3. 전송 불가:
    • 네트워크(서버-클라이언트)로 FText 자체를 보내는 것은 비효율적이며, 보통은 FString이나 FName(ID)을 보낸 뒤 받는 쪽에서 FText로 변환하여 보여줍니다.

4. 언제 쓰고, 언제 쓰지 말아야 할까?

✅ 언제 써야 할까? (Good Case)

"이 글자를 플레이어가 읽어야 하는가?" 라는 질문에 YES라면 무조건 FText입니다.

  1. UI (유저 인터페이스) 표시
    • 버튼 이름, 메뉴 타이틀, 퀘스트 설명, 아이템 이름 등 HUD나 위젯에 들어가는 모든 글자.
    • 언리얼의 UMG(위젯 블루프린트)의 텍스트 블록은 기본적으로 FText를 받습니다.
  2. 현지화(Localization)가 필요한 경우
    • 한국어, 영어, 일본어 등 언어 설정에 따라 바뀌어야 하는 모든 문구.
    • NSLOCTEXT나 스트링 테이블을 사용해 키(Key) 값으로 관리해야 하는 경우.
  3. 숫자 및 날짜 포맷팅 (예쁘게 보여주기)
    • 숫자에 콤마 찍기: 12345 → 12,345 (FText::AsNumber)
    • 퍼센트 표시: 0.15 → 15% (FText::AsPercent)
    • 화폐 단위: $10.50, ₩10,000 (FText::AsCurrency)
    • 시간 표시: 13:00 vs 1:00 PM 등 국가별 시간 표기법 자동 적용.
  4. 문장 조립 (FText::Format)
    • 어순이 다른 언어를 처리할 때 필수적입니다.
    • 영어: "Kill {Count} Enemies" (동사 + 숫자 + 명사)
    • 한국어: "{Count}명의 적을 처치하세요" (숫자 + 명사 + 동사)
    • FString::Printf는 순서가 고정되지만, FText::Format은 {이름} 기반이라 어순이 바뀌어도 문제없습니다.

❌ 언제 쓰지 말아야 할까? (Bad Case)

"개발자나 컴퓨터가 처리하는 데이터인가?" 라면 쓰지 마세요.

  1. 내부 식별자 (ID) 비교
    • 특정 액터를 찾거나 상태를 구분할 때 FText를 비교(==)하면 안 됩니다.
    • 이유: 매우 느리고, 언어 설정에 따라 글자가 바뀌면 로직이 꼬입니다. (한국어에서는 "공격", 영어에서는 "Attack"이면 비교 불가)
    • 👉 대안: FName (가장 빠름) 또는 enum을 사용하세요.
  2. 파일 경로 및 저장
    • C:/MyGame/SaveData.sav 같은 경로를 FText로 다루지 마세요.
    • 이유: 운영체제 파일 시스템은 현지화 기능이 필요 없습니다.
    • 👉 대안: FString을 사용하세요.
  3. 네트워크 패킷 전송
    • 서버에서 클라이언트로 메시지를 보낼 때, 완성된 FText를 통째로 보내는 것은 비효율적입니다.
    • 이유: 데이터 크기가 크고 무겁습니다.
    • 👉 대안: ID(FName/Integer)나 수치만 보내고, 클라이언트가 받아서 FText로 변환해 보여주는 것이 정석입니다.
  4. 문자열 가공 (파싱, 자르기)
    • 문자열을 쪼개거나(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)

  1. 초고속 비교 (Fast Comparison):
    • 두 FName이 같은지 비교할 때(==), 글자 하나하나를 비교하지 않고 내부 숫자(Index)만 비교합니다.
    • if (NameA == NameB)는 사실상 if (1024 == 1024)와 같은 정수 비교 연산이라 CPU 비용이 거의 0에 가깝습니다.
  2. 메모리 절약 (Lightweight):
    • "Pelvis"라는 FName을 100만 개의 변수에 저장해도, 실제 메모리에는 "Pelvis"라는 글자가 딱 한 번만 저장됩니다. 나머지 100만 개는 아주 작은 숫자 데이터만 가집니다.
  3. 대소문자 무시 (Case-Insensitive):
    • FName("Apple")과 FName("apple")은 내부적으로 같은 것으로 취급합니다. 실수로 대소문자를 틀려도 ID를 잘 찾아줍니다.

3. 단점 (Cons)

  1. 수정 불가 (Immutable):
    • 한번 생성된 FName은 절대 변경할 수 없습니다. 글자를 추가하거나 자르는 기능이 아예 없습니다.
    • 바꾸려면 FString으로 변환해서 고친 뒤 다시 FName으로 만들어야 합니다.
  2. 생성 비용 발생:
    • 처음 FName("NewString")을 만들 때, 이 문자열이 네임 테이블에 이미 있는지 검색하고, 없으면 등록하는 과정이 필요합니다. 따라서 반복문 안에서 매번 새로운 FName을 생성하는 것은 피해야 합니다.
  3. 데이터 손실:
    • 대소문자를 구분하지 않기 때문에, 대소문자 구분이 중요한 데이터(암호 등)에는 사용할 수 없습니다.

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 전송용으로 적합 비추천 (무거움) 데이터 저장/전송에 적합