Unreal C++ Mental Model¶
Unreal C++ is not ordinary application C++. It is C++ plus Unreal's reflection system, object model, editor serialization, garbage collection, Blueprint integration, and build tooling. If you come from Java, JavaScript, Go, or older C++, the surprising part is not usually the language syntax itself. The surprising part is that the C++ header is also a contract with Unreal Editor.
The engine runs UnrealHeaderTool before normal C++ compilation. UnrealHeaderTool reads macros such as UCLASS, USTRUCT, UENUM, UPROPERTY, and UFUNCTION, then generates additional code that makes those types visible to the editor, Blueprints, serialization, garbage collection, and networking systems.
That is why so much meaningful state is declared in .h files. In ordinary C++ you might hide most fields in the .cpp file or make them private implementation details. In Unreal, reflected fields often need to live in the header because the editor and generated code must know they exist.
This does not mean the project is abandoning normal C++ discipline. The implementation still belongs in .cpp files, and helper functions can still stay private to a translation unit when Unreal does not need to know about them. The rule is narrower:
If Unreal Editor, Blueprint, serialization, garbage collection, delegates, or asset loading need to see it, it usually needs reflection metadata in the header.
If only native C++ implementation needs it, keep it as ordinary C++.
The Four Layers¶
The current Andromeda code follows this mental model:
C++ class = schema + behavior
Blueprint class = configured subclass asset
Level instance = placed object with instance overrides
Data Asset = authored gameplay data
Widget Blueprint = UI layout asset
For example, AAndromedaCharacter declares input action references in C++:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Andromeda|Input")
TObjectPtr<UInputAction> InteractAction;
That does not mean C++ constructs the IA_Interact asset. It means the C++ class has a reflected field where a Blueprint subclass can store a reference to an input action asset.
The actual value is populated in BP_PlayerCharacter Class Defaults. At runtime, the spawned character instance reads that configured value in SetupPlayerInputComponent.
This is the key distinction:
Declared in .h InteractAction exists as a reflected property.
Implemented in .cpp SetupPlayerInputComponent reads and binds it.
Configured in Blueprint BP_PlayerCharacter assigns IA_Interact.
Instanced at runtime The level/game mode spawns a character with that value.
Constructors vs Blueprint Defaults¶
C++ constructors create default subobjects and assign native defaults:
FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
CarryComponent = CreateDefaultSubobject<UAndromedaCarryComponent>(TEXT("Carry"));
That code creates components that every AAndromedaCharacter has. Blueprint Class Defaults then configure exposed fields such as input action references, prompt widget class, or carry offsets.
Do not expect every meaningful runtime value to be assigned in the constructor. Unreal projects commonly declare a property in C++, configure it in a Blueprint subclass, and use it at runtime. That is normal, not a missing assignment.
The common mistake when coming from Java, Go, or JavaScript is to read the C++ constructor as the only place dependency setup can happen. In Unreal, constructor setup is only one layer. Editor-authored defaults are another layer.
For Andromeda, the constructor should usually create stable native components:
camera component
carry component
station mesh component
station placement point
interactable component
Blueprint Class Defaults should usually assign project content references:
input action assets
input mapping contexts
widget classes
static meshes
data assets
per-Blueprint prompt text
station action settings
Level instances should usually carry placement-specific changes:
where this station is in the room
which spawn point is active
which warehouse zone sits in which area
which prop instance is rotated against a wall
Data Assets should usually carry authored gameplay facts:
item name
manifest claim
radiation summary
required package category
processing rules
payouts
consequence text
Important Unreal Types¶
This table is the quick map, but each item is explained below because these are the parts that make Unreal C++ feel unlike ordinary application C++.
| Thing in code | What it means in Unreal | Rough analogy |
|---|---|---|
UCLASS |
Reflected Unreal class | Framework-managed class type |
USTRUCT |
Reflected value type | Serializable DTO/value object |
UENUM |
Reflected enum | Enum visible to editor/data assets |
GENERATED_BODY() |
Hook for generated code | Framework-generated boilerplate insertion point |
UPROPERTY |
Reflected field known to editor/serializer/GC | Serialized field / dependency-injected field |
UFUNCTION |
Reflected function callable by Unreal systems | Method exposed to framework/runtime |
TObjectPtr<T> |
GC-aware Unreal object reference | Managed object reference |
TSubclassOf<T> |
Editable class reference constrained to type | Class<? extends T> |
TArray<T> |
Unreal dynamic array | ArrayList<T> / Go slice |
FText |
Localizable user-facing text | UI string, not raw string |
FString |
General mutable string | Normal string |
FName |
Interned name identifier | Symbol/name key |
UCLASS¶
UCLASS marks a C++ class as an Unreal reflected class.
Example:
This tells Unreal that AAndromedaScanner is more than a native C++ class. It can participate in the Unreal object system. That means it can be used by the editor, inherited by Blueprint if allowed, constructed by the engine, serialized as part of assets or levels, and inspected by Unreal's generated code.
The A prefix tells you this is an Actor-derived type. It is a naming convention, not magic syntax:
A... = Actor class
U... = UObject class or component class
F... = struct/value type
E... = enum type
I... = native interface body
ANDROMEDA_API is the module export macro. It makes the type visible across module boundaries. For this project, you can mostly read it as "this class belongs to the Andromeda module and can be linked correctly."
USTRUCT¶
USTRUCT marks a struct as visible to Unreal's reflection system.
Andromeda uses reflected structs for data that should be editable in assets or passed cleanly to UI:
USTRUCT(BlueprintType)
struct FAndromedaScanProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Andromeda|Scan")
FText RadiationSummary;
};
This is not just a plain C++ struct. Unreal can now display it in Details panels, serialize it into assets, and expose it to Blueprint/widget code if needed.
The F prefix is Unreal's convention for plain value types. The struct is copied by value unless you explicitly pass it by reference or pointer, just like ordinary C++ value types.
UENUM¶
UENUM marks an enum as reflected.
Andromeda uses enums for authored categories such as processing actions, processing state, handling size, and package category. These are code-owned concepts because the valid choices define gameplay rules.
The important part is usually:
enum class is modern C++ scoped enum syntax. BlueprintType tells Unreal this enum can appear in editor-exposed fields and Blueprint-aware data. The uint8 storage type is common for Unreal reflected enums.
The reason these enums live in C++ rather than being created only in the editor is that gameplay code branches on them. If C++ owns the rules, C++ should own the enum definition. Data Assets can then choose enum values without redefining the rule vocabulary.
GENERATED_BODY()¶
GENERATED_BODY() is where Unreal inserts generated glue code.
You will see it inside every reflected class and struct:
This is why UnrealHeaderTool has to parse headers before normal C++ compilation. The macro expands into code that supports reflection, serialization, construction helpers, Blueprint integration, and other Unreal systems.
From a practical reading perspective, do not treat GENERATED_BODY() as gameplay logic. Treat it as a required integration point between your native type and the engine.
UPROPERTY¶
UPROPERTY makes a field visible to Unreal's property system.
Example:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Andromeda|Input")
TObjectPtr<UInputAction> InteractAction;
That one line does several things:
EditAnywhere The editor can assign the value in class defaults or instances.
BlueprintReadOnly Blueprint can read it, but should not assign it through graph logic.
Category The Details panel groups it under this category.
TObjectPtr Unreal can track this UObject reference.
Most importantly, UPROPERTY lets Unreal serialize the assigned value into a .uasset Blueprint. That is why InteractAction can be empty in native C++ but still have IA_Interact assigned in BP_PlayerCharacter.
For object references, UPROPERTY also matters for garbage collection. If a runtime UObject reference should keep pointing to a valid object, Unreal needs to know about it.
UFUNCTION¶
UFUNCTION makes a function visible to Unreal's reflected function system.
Example:
UFUNCTION(BlueprintCallable, Category="Andromeda|Interaction")
void SetInteractionPromptSuppressed(bool bSuppressed);
This means the function can be called from Blueprint or other reflected systems. That does not mean the project wants Blueprint Event Graph logic everywhere. It only means the function is exposed to Unreal's runtime/editor systems.
Dynamic delegate handlers also need UFUNCTION, even when the function is not meant as public game scripting. For example, widget button handlers are bound through Unreal's delegate system:
For AddDynamic to work reliably, the target function must be visible to reflection.
BlueprintType, Blueprintable, BlueprintCallable, And BlueprintPure¶
These names are easy to misread as "we are doing Blueprint logic." They are really exposure controls.
BlueprintType means the type can be used as a variable or property type in Blueprint-aware places. Andromeda uses this for data structs and enums so Data Assets and widgets can understand them.
Blueprintable means Blueprint subclasses can be created from the C++ type. Actor and widget C++ classes often need this because the project wants Blueprint assets for composition, mesh assignment, widget layout, and class defaults.
BlueprintCallable means Blueprint code can call the function. That can be useful even if the function is usually called from C++, because some setup or future presentation glue may need to invoke it.
BlueprintPure means the function has no execution pin in Blueprint and is expected to behave like a read-only query. Use it for getters or formatting helpers that should not mutate gameplay state.
The project rule is still:
EditAnywhere, VisibleAnywhere, And Transient¶
These specifiers answer different questions.
EditAnywhere means the value can be edited in the editor. Use it for configuration that designers or Blueprint class defaults should own, such as widget classes, input action references, station prompts, and data asset references.
VisibleAnywhere means the value is visible in the editor but not editable. Use it for native components that should exist on every instance but should not be replaced casually from the editor.
Transient means the value is runtime-only and should not be serialized as asset/map state. It can still be tracked by Unreal and still be visible to reflection if you expose it.
So these are not interchangeable:
EditAnywhere This is authored/configured data.
VisibleAnywhere This is native structure you can inspect.
Transient This is runtime state, not saved content.
TArray¶
TArray is Unreal's dynamic array type.
Rough equivalents:
Andromeda uses TArray for things like default input mapping contexts and processing rules:
TArray<TObjectPtr<UInputMappingContext>> DefaultMappingContexts;
TArray<FAndromedaProcessingRule> ProcessingRules;
The reason to prefer TArray in Unreal gameplay code is that it integrates with Unreal's memory, reflection, serialization, and editor systems. If a collection is a UPROPERTY, it generally needs Unreal container types.
TObjectPtr¶
Modern Unreal commonly uses:
instead of a plain reflected raw pointer such as:
especially inside UPROPERTY fields.
TObjectPtr<T> is an Unreal-aware pointer wrapper for UObject references. For practical reading, treat it as:
It still behaves pointer-like:
means:
Do not read TObjectPtr as ownership in the C++ unique_ptr sense. Unreal owns UObject lifetime through its object system and garbage collector. TObjectPtr is a tracked reference, not a unique owner.
TSubclassOf¶
TSubclassOf<T> stores a class reference constrained to a parent type.
Example:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Andromeda|UI")
TSubclassOf<UAndromedaScanResultWidget> ScanResultWidgetClass;
This does not hold a widget instance. It holds a class that can be instantiated later.
Rough Java analogy:
In Unreal terms:
The C++ scanner says it needs some scan-result widget class.
BP_Scanner chooses WBP_ScanResult as that class.
At runtime, C++ calls CreateWidget with that class.
The result is a widget instance.
The distinction is:
FText, FString, And FName¶
Unreal has multiple string-like types because they solve different problems.
Use FText for player-facing display text. It is designed for localization and UI. Salvage item names, manifest claims, scan summaries, result descriptions, and consequence text should be FText.
Use FString for general string manipulation and debug output. If you are building an internal debug line, temporary filename, or log-only message, FString is often the right tool.
Use FName for stable identifiers and fast comparisons. FName is interned, so it behaves more like a symbol or key than a human-facing string.
In Andromeda:
are FText because they are shown to the player.
The debug inspection message converts to string-style debug output because UE_LOG and on-screen debug messages are not localized UI.
UPROPERTY(Transient)¶
Transient means runtime state that should not be saved as authored asset data.
Examples:
UPROPERTY(Transient)
TObjectPtr<UObject> FocusedInteractableObject;
UPROPERTY(Transient, BlueprintReadOnly, Category="Andromeda|Carry")
TObjectPtr<AAndromedaSalvageItem> HeldItem;
These values are real runtime state. They also need to be visible to Unreal's garbage collector because they reference Unreal objects. A normal raw C++ pointer might work briefly, but it would not participate in reflection or GC in the same way.
The mental model is:
Transient UPROPERTY = runtime-only, but still tracked by Unreal.
Normal C++ field = invisible to Unreal reflection/serialization/GC.
For object references in gameplay code, prefer reflected TObjectPtr unless there is a specific reason not to.
Why The Code Can Look "Unassigned"¶
DefaultMappingContexts, input actions, widget classes, item data, and station widget references are often configured in Blueprint assets. The C++ declares the slot and uses it, but the editor-authored asset supplies the value.
That is why a property like this can be correct even if C++ never assigns it:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Andromeda|UI")
TSubclassOf<UAndromedaScanResultWidget> ScanResultWidgetClass;
The scanner C++ owns behavior. BP_Scanner owns the concrete WBP_ScanResult class reference.