Skip to main content

🧰 Lesson 2.4: Unreal Core Types β€” FString, FName, FText & Containers

Unreal has its own strings and containers, and it expects you to use them. They aren't reinventions for their own sake β€” each solves a real engine need (reflection, performance, localization) that the C++ standard library doesn't address.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Choose correctly between FString, FName, and FText
  • Use TArray, TMap, and TSet for common gameplay data
  • Explain why Unreal containers are preferred over std::vector/std::map here
  • Wrap string literals in TEXT() and understand why
  • Convert between the string types when you must

Estimated Time: 60 minutes

Engine Version: Unreal Engine 5.8

In This Lesson

Three Strings, Three Jobs

Newcomers are surprised Unreal has three string types. The reason is that "a string" plays three different roles in a game, with different priorities:

TypeUse it forKey property
FStringMutable text you build, manipulate, logFlexible but heaviest; not for identifiers
FNameIdentifiers: asset names, bone names, tags, keysImmutable, case-insensitive, hashed β€” comparison is nearly free
FTextAnything shown to a playerLocalizable β€” supports translation & culture formatting
// FString β€” you're assembling/altering text (e.g. a debug string):
FString DebugLine = FString::Printf(TEXT("Health: %d / %d"), Current, Max);

// FName β€” a lightweight identifier used as a key or lookup:
FName SocketName = TEXT("hand_r");
if (Mesh->DoesSocketExist(SocketName)) { /* ... */ }

// FText β€” player-facing, so it can be localized:
UPROPERTY(EditAnywhere, Category = "UI")
FText ButtonLabel = FText::FromString(TEXT("Start Game"));

βœ… The one-line decision

Shown to a player? FText. Used as an identifier/key? FName. Everything else (building, parsing, logging)? FString. Using FName for keys instead of FString is a real performance win β€” FName comparison is an integer compare, not a character-by-character one.

The TEXT() Macro

You've seen TEXT("...") around every string literal. It's not optional decoration β€” it forces the literal to be wide (UTF-16) characters, which is what Unreal's string types expect. Omit it and you get needless conversions or subtle bugs.

FString Good = TEXT("Ready");     // wide literal β€” correct, no conversion
FString Risky = "Ready";          // narrow literal β€” forces a runtime conversion

UE_LOG(LogTemp, Warning, TEXT("Always wrap log format strings in TEXT()"));

⚠️ Make it a reflex

Wrap every string literal that feeds an Unreal API in TEXT(). It's the norm across the entire engine codebase, and forgetting it is a classic newcomer smell that reviewers will flag immediately.

TArray β€” the workhorse container

TArray<T> is Unreal's dynamic array β€” the container you'll reach for most. It's the equivalent of std::vector, but reflection-aware and integrated with the engine.

TArray<AActor*> NearbyEnemies;

NearbyEnemies.Add(SomeEnemy);                 // append
NearbyEnemies.Num();                          // count (not .size())
NearbyEnemies.Contains(SomeEnemy);            // membership test
NearbyEnemies.Remove(SomeEnemy);              // remove by value
NearbyEnemies[0];                             // indexed access

// Range-based for β€” the idiomatic way to iterate:
for (AActor* Enemy : NearbyEnemies)
{
    if (Enemy) { /* always null-check UObject pointers */ }
}

// Reflected TArray shows up as an editable list in the Details panel:
UPROPERTY(EditAnywhere, Category = "Spawning")
TArray<TSubclassOf<AActor>> SpawnableTypes;

πŸ“– Unreal names, not STL names

Note the API: .Num() not .size(), .Add() not .push_back(), .Contains() not std::find. Same concepts, Unreal spelling. When a TArray is a UPROPERTY, the editor renders it as an add/remove list for free.

TMap & TSet

TMap<Key, Value> stores key→value pairs; TSet<T> stores unique values. Both are hashed for fast lookup.

// TMap: an inventory of item name -> quantity
TMap<FName, int32> Inventory;

Inventory.Add(TEXT("Potion"), 3);
Inventory.Add(TEXT("Key"), 1);

if (int32* Count = Inventory.Find(TEXT("Potion")))
{
    // Find returns a pointer to the value, or nullptr if the key is absent.
    (*Count)++;   // now 4
}

for (const TPair<FName, int32>& Entry : Inventory)
{
    UE_LOG(LogTemp, Log, TEXT("%s x%d"), *Entry.Key.ToString(), Entry.Value);
}

// TSet: a collection of unique tags, no duplicates
TSet<FName> ActiveTags;
ActiveTags.Add(TEXT("OnFire"));
ActiveTags.Add(TEXT("OnFire"));   // ignored β€” already present
bool bBurning = ActiveTags.Contains(TEXT("OnFire"));

⚠️ TMap::Find returns a pointer

Inventory.Find(Key) gives you a pointer to the value (or nullptr) β€” not the value itself. Always check it before dereferencing. This trips people up coming from other languages where map lookup returns a value or throws.

βœ… Pro Tip

FName keys pair beautifully with TMap/TSet because their hashing and comparison are cheap. An Inventory keyed by FName is idiomatic Unreal.

Why Not std::?

You can use std::vector in a .cpp, but Unreal's containers are strongly preferred in gameplay code. The reasons are concrete:

graph TD A["Unreal containers (TArray, TMap, TSet)"] --> R["Reflection-aware:
can be UPROPERTY, shown in editor"] A --> S["Serialization:
save/load & replicate"] A --> M["Engine memory allocators & profiling"] A --> D["First-class debugger & log support"] B["std:: containers"] --> N["None of the above out of the box"]
Figure 1: Unreal containers plug into reflection, serialization, and the engine's memory system. A std::vector can't be a UPROPERTY.
πŸ’‘ The practical line: if the data is a member of a reflected class, needs to appear in the editor, be saved, or be replicated, it must be an Unreal container. For a purely local, throwaway computation inside one function, either works β€” but staying consistent with Unreal's types keeps the codebase uniform.

Hands-on Exercise & Quiz

πŸ‹οΈ Exercise: Pick the type

Objective: Match each piece of data to the right Unreal type.

  1. The label on a quest button, shown to players in multiple languages
  2. The key identifying which weapon socket to attach to
  3. A list of all pickups currently overlapping the player
  4. A mapping from ability name to its cooldown remaining
  5. A debug message you assemble from several numbers
βœ… Answers
  • 1. FText β€” player-facing, localizable.
  • 2. FName β€” an identifier/key, compared cheaply.
  • 3. TArray<AActor*> (or of your pickup type) β€” an ordered, growable list.
  • 4. TMap<FName, float> β€” keyβ†’value lookup.
  • 5. FString via FString::Printf(TEXT("..."), ...) β€” mutable, built text.

🎯 Quick Quiz

Question 1: Which type should hold text displayed to the player?

Question 2: What does TMap::Find(Key) return when the key is missing?

Question 3: Why can't a std::vector member be a UPROPERTY?

Summary

πŸŽ‰ Key Takeaways

  • Three strings, three jobs: FText for player-facing/localizable text, FName for cheap identifiers/keys, FString for mutable/built text.
  • Wrap every string literal feeding an Unreal API in TEXT().
  • TArray (list), TMap (keyβ†’value), and TSet (unique values) are the core containers β€” note Unreal's API names (.Num(), .Add(), .Contains()).
  • TMap::Find returns a pointer (nullptr if absent) β€” always check it.
  • Prefer Unreal containers in gameplay code: only they can be UPROPERTYs and plug into the editor, serialization, and networking.

πŸ“š Additional Resources

πŸš€ What's Next?

We keep hinting at garbage collection and "keeping objects alive." Time to face it directly: the memory model β€” the UObject lifecycle, how the GC decides what lives, TObjectPtr, and the smart pointers you use for non-UObjects.

πŸŽ‰ Lesson complete!

You've got Unreal's vocabulary. Now, its memory.