Chapter 02

Blueprint vs. C++ Workflows

The core philosophy, the hybrid pattern, when to refactor, and Blueprint Interfaces for decoupled systems.

In this chapter

  1. The Core Philosophy
  2. The Hybrid Pattern
  3. Knowing When to Refactor
  4. Blueprint Interfaces
1

The Core Philosophy

The single most important principle for scalable Unreal development:

C++ defines rules and capabilities. Blueprints compose and configure behavior.
C++ establishes what can happen. Blueprints decide when, where, and with what values it happens.

This isn't a preference — it's an architectural boundary. Blurring it creates projects that are hard to maintain, hard to iterate on, and hard to hand off to other developers or designers.

Concrete examples of the boundary

Belongs in C++Belongs in Blueprint
Damage calculation logicDamage values and curves per weapon
Movement component rulesWalk speed, jump height per character
Interaction interface definitionWhat each interactable actually does
Health component with die/respawnDeath VFX, sound, respawn location
AI behavior tree structureSpecific patrol routes, aggro ranges

If you follow this rule consistently, designers can tune and configure gameplay without touching C++, and engineers can change rules without breaking Blueprint graphs.


2

The Hybrid Pattern

The most powerful Unreal workflow is the hybrid pattern: define a base class in C++, then derive Blueprint subclasses for actual in-game use.

// C++ — defines the rules
UCLASS()
class AWeapon : public AActor
{
    GENERATED_BODY()

public:
    // Exposed to Blueprint — designer fills in the value
    UPROPERTY(EditDefaultsOnly, Category="Stats")
    float BaseDamage = 25.f;

    // Callable from Blueprint — C++ defines behavior
    UFUNCTION(BlueprintCallable)
    void Fire();
};

From this single C++ base class, you can derive:

Each variant is a Blueprint subclass that only specifies what makes it different. The shared firing logic, damage application, and lifetime management all live once in C++.

This pattern also means non-engineers can create new weapon types entirely in Blueprint, without needing to touch C++ or wait on an engineer — as long as the base class exposes the right properties and functions.

UPROPERTY specifiers that drive the hybrid pattern

SpecifierMeaning
EditDefaultsOnlyEditable in the Blueprint Class Defaults — the most common for per-type tuning
EditInstanceOnlyEditable per placed instance in the level
EditAnywhereEditable in both Class Defaults and instances
BlueprintReadWriteBlueprint can read and write the value at runtime
BlueprintReadOnlyBlueprint can read but not write — good for exposing state

3

Knowing When to Refactor

There are two clear signals that tell you the balance has shifted and it's time to move work between layers:

Move Blueprint → C++
  • You are copying Blueprint graphs between actors
  • The graph is too large to read or reason about
  • Performance is suffering from Blueprint overhead in a hot path
  • Logic needs unit testing
Move C++ → Blueprint
  • You are recompiling the project every time a gameplay value changes
  • Designers need to adjust behavior but can't touch C++
  • Values that are tuned constantly are hardcoded in constructors
The moment you find yourself copying Blueprint graphs between actors, you are already overdue for a C++ abstraction. A shared behavior that appears in two places belongs in one base class.

The duplication rule

Copy/paste in Blueprints is the same code smell as copy/paste in C++. If the same graph appears in BP_Door and BP_Elevator, extract the shared logic into a C++ base class or a Blueprint Function Library, then call it from both.


4

Blueprint Interfaces

Blueprint Interfaces are the most scalable way to communicate between Blueprints without creating hard dependencies. An interface defines a contract — a function that any implementing class promises to respond to — without specifying how.

Example: BPI_Interactable

Define an interface with a single function: Interact. Any Blueprint that implements BPI_Interactable promises to respond to that call.

// Player interaction logic — no knowledge of what it's hitting
FHitResult Hit;
if (LineTrace(Hit))
{
    // Call Interact on whatever was hit — door, switch, NPC, pickup
    IInteractable::Execute_Interact(Hit.GetActor(), this);
}

The player's interaction code does not know — and does not care — whether the hit actor is a door, a light switch, an NPC, or a pickup. Each implements Interact differently:

This decoupling is the key benefit. Adding a new interactable object requires zero changes to the player character — just implement the interface on the new actor.

Interfaces vs. Casting

Cast ToInterface
Couples caller toSpecific classA contract only
Adding new typesRequires modifying callerZero caller changes
Best forKnown relationshipsOpen-ended systems

Use a Cast when you know exactly what type you're dealing with and need class-specific functionality. Use an Interface when you want a system to work with any object that satisfies a contract, regardless of what it actually is.

← Chapter 1 ↑ Index Chapter 3 →