Skip to main content

06 / 16

Reflection and Blueprint boundaries

Expose the smallest surface the editor and Blueprint need.

Anything reflected becomes an API for designers, Blueprint graphs, serialization, and replication. Renaming it later breaks assets even when the C++ still compiles.

Rules

  1. 01It's ideal to start with the narrowest editor and Blueprint access that satisfies the use case.
  2. 02Use metadata for editor presentation and validation. Metadata does not exist in a shipped runtime contract.
  3. 03Keep categories stable. Renaming a public Blueprint function, property, or category breaks every asset that uses it, even when the C++ still compiles.
  4. 04A BlueprintNativeEvent generates a separate _Implementation function that holds the C++ default. Override that one. Overriding the declared name instead compiles cleanly and stops the Blueprint override from ever running.
  5. 05Mark runtime state Transient when it must not serialize. Without it the value is written into the asset and comes back as stale data on the next load.
  6. 06Every UFUNCTION and UPROPERTY name is content that assets depend on. Renaming one breaks Blueprints silently at load rather than at compile time, so treat a rename as a content migration.
  7. 07The Specifier Reference covers the exact behavior of every reflection flag.

Property exposure

EditDefaultsOnly

Designers set one class or asset default. Keeps placed instances from drifting away from the class default.

CostsPlaced instances cannot override the value.

EditInstanceOnly

A placed actor needs level-specific configuration. Keeps the class default fixed.

CostsDuplicated instance values are harder to update globally.

VisibleInstanceOnly

Runtime state needs inspection but not authoring. Lets you read the value without allowing edits.

CostsAdds another reflected field to the editor.

BlueprintReadOnly

Blueprint reads a value managed by C++. Keeps changes behind C++ functions.

CostsA setter or command function is required for changes.

BlueprintReadWrite

Blueprint is allowed to change the field directly. Needs no wrapper function for authoring.

CostsC++ cannot enforce a rule each time Blueprint writes the value.

Expose the operation instead of the field

Designers can set the default and Blueprint can read it, but C++ still controls how the value changes.

HealthComponent.hcpp
UFUNCTION(BlueprintPure, Category = "Health")
float GetMaxHealth() const
{
    return MaxHealth;
}

UPROPERTY(EditDefaultsOnly, Category = "Health", meta = (ClampMin = "1.0"))
float MaxHealth = 100.0f;

UPROPERTY(VisibleInstanceOnly, Category = "Health")
float Health = 0.0f;
Why
Blueprint depends on GetMaxHealth, not the way the class stores health. Validation or replication can change later without rewriting every caller.
Costs
Every exposed function makes the Blueprint API larger. Treat its name and category with the same care as a public C++ method.
Instead
Use BlueprintReadWrite for author-owned data whose direct mutation is part of the contract. Use VisibleInstanceOnly for runtime state that designers only need to inspect.

All 16 rules