🎛️ Lesson 2.3: UPROPERTY & UFUNCTION Specifiers
The macros from the last lesson take specifiers — the keywords in the parentheses that decide exactly how a member surfaces. Master a dozen of these and you can shape precisely what the editor shows, what Blueprint can touch, and what stays private to C++.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Choose the right edit/visibility specifier:
EditAnywhere,VisibleAnywhere,EditDefaultsOnly,EditInstanceOnly - Control Blueprint access with
BlueprintReadWritevsBlueprintReadOnly - Organize the Details panel with
Categoryand refine behavior withmeta - Apply the common
UFUNCTIONspecifiers:BlueprintCallable,BlueprintPure,CallInEditor - Recognize why a forgotten
UPROPERTYcan get your pointer garbage-collected
Estimated Time: 60 minutes
Engine Version: Unreal Engine 5.8
In This Lesson
Edit vs Visible
The first choice for any property: can it be edited in the Details panel, or only viewed? And does that apply to the class defaults, to placed instances, or both? Two axes, combined into keywords:
| Specifier | Editable? | Where |
|---|---|---|
EditAnywhere | Yes | Class defaults (Blueprint/archetype) AND placed instances |
EditDefaultsOnly | Yes | Class defaults only (not per-instance) |
EditInstanceOnly | Yes | Placed instances only (not the default) |
VisibleAnywhere | No (read-only display) | Defaults AND instances |
VisibleDefaultsOnly / VisibleInstanceOnly | No | Defaults / instances respectively |
// A tunable designers set on the Blueprint default and per placed actor:
UPROPERTY(EditAnywhere, Category = "Combat")
float FireRate = 5.0f;
// A component pointer: shown for inspection, but you don't reassign it by hand:
UPROPERTY(VisibleAnywhere, Category = "Components")
UStaticMeshComponent* Mesh;
// A value that should be identical for every instance of a weapon type —
// edit it on the Blueprint default, not per-instance:
UPROPERTY(EditDefaultsOnly, Category = "Combat")
int32 MagazineSize = 30;
✅ Pro Tip
Use VisibleAnywhere for component pointers (you want to see them, not swap them) and EditDefaultsOnly for values that define a type rather than an instance. Reserve EditAnywhere for things a designer legitimately tunes per placed actor.
Blueprint Access
Separately from the editor, you control whether Blueprint graphs can read or write a property. These stack with the edit/visible specifiers:
BlueprintReadWrite— Blueprint can get and set the valueBlueprintReadOnly— Blueprint can get but not set (good for state C++ owns)- (neither) — invisible to Blueprint entirely
// Designers tune it in Details AND Blueprint logic can change it at runtime:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")
float Damage = 25.0f;
// Blueprint can read current health for a HUD, but only C++ changes it:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "State")
float CurrentHealth = 100.0f;
📖 Two independent questions
"Can the editor edit it?" (EditAnywhere/VisibleAnywhere…) and "Can Blueprint graphs touch it?" (BlueprintReadWrite/BlueprintReadOnly) are separate. You combine one from each axis. VisibleAnywhere, BlueprintReadOnly means "show it, don't let anyone edit it by hand or in script" — perfect for C++-owned state.
Category & Meta
As your classes grow, the Details panel gets crowded. Category groups properties into collapsible sections; nested categories use |.
UPROPERTY(EditAnywhere, Category = "Combat|Ranged")
float Range = 5000.0f;
UPROPERTY(EditAnywhere, Category = "Combat|Ranged")
float Spread = 2.0f;
meta unlocks finer control. A few you'll use constantly:
// Clamp the slider AND the typed value between 0 and 100:
UPROPERTY(EditAnywhere, Category = "Stats",
meta = (ClampMin = "0.0", ClampMax = "100.0", UIMin = "0.0", UIMax = "100.0"))
float HealthPercent = 100.0f;
// Only show this property when bUseSpread is true:
UPROPERTY(EditAnywhere, Category = "Combat",
meta = (EditCondition = "bUseSpread"))
float SpreadAngle = 2.0f;
UPROPERTY(EditAnywhere, Category = "Combat")
bool bUseSpread = false;
✅ Pro Tip
ClampMin/ClampMax enforce hard limits (typed values are clamped too); UIMin/UIMax only set the slider range. EditCondition creates dependent properties that gray out or hide — a clean way to build self-documenting inspectors.
UFUNCTION Specifiers
UFUNCTION exposes a C++ function to the reflection system — most often to make it callable from Blueprint. The everyday specifiers:
| Specifier | Effect |
|---|---|
BlueprintCallable | Callable from Blueprint as an action node (has an execution pin) |
BlueprintPure | Callable with no side effects — no exec pin, used like a getter |
CallInEditor | Adds a button in the Details panel to run it in-editor |
BlueprintImplementableEvent | Declared in C++, implemented in Blueprint (Module 6) |
BlueprintNativeEvent | C++ default that Blueprint can override (Module 6) |
// An action a designer can call from a Blueprint graph:
UFUNCTION(BlueprintCallable, Category = "Combat")
void Fire();
// A pure getter — shows as a node with only a return value, no exec pins:
UFUNCTION(BlueprintPure, Category = "State")
bool IsAlive() const;
// A handy editor button, e.g. to reset or randomize during level design:
UFUNCTION(CallInEditor, Category = "Debug")
void ResetToDefaults();
⚠️ BlueprintPure = promise of no side effects
Mark a function BlueprintPure only if it truly doesn't change state — it's a getter/query. Blueprint may call a pure node multiple times or in any order. Marking a state-changing function pure leads to baffling bugs. If in doubt, use BlueprintCallable.
The Silent GC Trap
Here's a subtle but critical reason UPROPERTY matters beyond the editor. Unreal garbage-collects UObjects. The garbage collector only "sees" a pointer as keeping an object alive if that pointer is a reflected UPROPERTY. A raw, un-reflected UObject* is invisible to the GC.
UCLASS()
class MYPROJECT_API AWeapon : public AActor
{
GENERATED_BODY()
// SAFE: the GC sees this pointer and keeps the ammo object alive.
UPROPERTY()
UAmmoData* CurrentAmmo;
// DANGER: no UPROPERTY — the GC can't see this. The object it points to
// may be collected out from under you, leaving a dangling pointer.
UAmmoData* CachedAmmo; // ⚠ do not do this for UObject pointers
};
📖 The rule you'll never break again
Every UObject* member you want to keep alive must be a UPROPERTY() — even an empty UPROPERTY() with no specifiers works, because the point is GC visibility, not editor exposure. We unpack the full memory model in Lesson 2.5; for now, treat "UObject pointer member → UPROPERTY" as automatic.
Hands-on Exercise & Quiz
🏋️ Exercise: Specify a weapon
Objective: Pick specifiers deliberately for each member.
Declare properties/functions for a weapon with these requirements:
Damage— designers tune per instance; Blueprint may modify at runtime; clamp 0–1000MagazineSize— same for all weapons of a type; not per-instanceCurrentAmmo— C++ owns it; HUD Blueprint reads it; shown read-onlyFire()— callable from Blueprint, has effects- A pointer to a
UAmmoDatathat must survive garbage collection
✅ Sample solution
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat",
meta = (ClampMin = "0.0", ClampMax = "1000.0"))
float Damage = 25.0f;
UPROPERTY(EditDefaultsOnly, Category = "Combat")
int32 MagazineSize = 30;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "State")
int32 CurrentAmmo = 30;
UFUNCTION(BlueprintCallable, Category = "Combat")
void Fire();
UPROPERTY() // GC visibility — no editor exposure needed
UAmmoData* AmmoData;
🎯 Quick Quiz
Question 1: You want a value editable on the Blueprint default but NOT on each placed actor. Use:
Question 2: A function that only computes and returns a value, no state change. Best specifier:
Question 3: Why must a UObject* member you keep be a UPROPERTY()?
Summary
🎉 Key Takeaways
- Edit/visible specifiers control the editor:
EditAnywhere,EditDefaultsOnly,EditInstanceOnly, and the read-onlyVisible*forms. - Blueprint access is a separate axis:
BlueprintReadWrite,BlueprintReadOnly, or neither — combine one from each axis. Category(with|nesting) organizes the Details panel;metaaddsClampMin/Max,UIMin/Max,EditCondition, and more.UFUNCTIONexposes functions:BlueprintCallable(action),BlueprintPure(side-effect-free getter),CallInEditor(a button).- Any
UObject*member you want kept alive must be aUPROPERTY()— that's how the GC sees it.
📚 Additional Resources
🚀 What's Next?
You can shape how any member surfaces. Next we meet the types those members hold — Unreal's own strings (FString/FName/FText) and containers (TArray/TMap/TSet) — and why you use them instead of the C++ standard library.
🎉 Lesson complete!
You speak specifier now. On to Unreal's types.