Skip to main content

🎛️ 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 BlueprintReadWrite vs BlueprintReadOnly
  • Organize the Details panel with Category and refine behavior with meta
  • Apply the common UFUNCTION specifiers: BlueprintCallable, BlueprintPure, CallInEditor
  • Recognize why a forgotten UPROPERTY can 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:

SpecifierEditable?Where
EditAnywhereYesClass defaults (Blueprint/archetype) AND placed instances
EditDefaultsOnlyYesClass defaults only (not per-instance)
EditInstanceOnlyYesPlaced instances only (not the default)
VisibleAnywhereNo (read-only display)Defaults AND instances
VisibleDefaultsOnly / VisibleInstanceOnlyNoDefaults / 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 value
  • BlueprintReadOnly — 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:

SpecifierEffect
BlueprintCallableCallable from Blueprint as an action node (has an execution pin)
BlueprintPureCallable with no side effects — no exec pin, used like a getter
CallInEditorAdds a button in the Details panel to run it in-editor
BlueprintImplementableEventDeclared in C++, implemented in Blueprint (Module 6)
BlueprintNativeEventC++ 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:

  1. Damage — designers tune per instance; Blueprint may modify at runtime; clamp 0–1000
  2. MagazineSize — same for all weapons of a type; not per-instance
  3. CurrentAmmo — C++ owns it; HUD Blueprint reads it; shown read-only
  4. Fire() — callable from Blueprint, has effects
  5. A pointer to a UAmmoData that 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-only Visible* forms.
  • Blueprint access is a separate axis: BlueprintReadWrite, BlueprintReadOnly, or neither — combine one from each axis.
  • Category (with | nesting) organizes the Details panel; meta adds ClampMin/Max, UIMin/Max, EditCondition, and more.
  • UFUNCTION exposes functions: BlueprintCallable (action), BlueprintPure (side-effect-free getter), CallInEditor (a button).
  • Any UObject* member you want kept alive must be a UPROPERTY() — 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.