Skip to main content

šŸ”— Lesson 6.1: Exposing Functions to Blueprint

This is the module the whole course has pointed toward. C++ and Blueprint aren't rivals — they're layers, and functions are how they call across the boundary. The trick is knowing which of four specifiers to use, because each sends the call in a specific direction.

šŸŽÆ Learning Objectives

By the end of this lesson, you will be able to:

  • Explain the two directions a call can cross the C++/Blueprint boundary
  • Use BlueprintCallable and BlueprintPure to call C++ from Blueprint
  • Use BlueprintImplementableEvent to call Blueprint from C++
  • Use BlueprintNativeEvent for a C++ default that Blueprint can override
  • Choose the right specifier for a given interop need

Estimated Time: 60 minutes

Engine Version: Unreal Engine 5.8

In This Lesson

Two Directions

Every interop function crosses the boundary in one of two directions. Getting the direction straight in your head makes the four specifiers obvious.

graph LR subgraph BPtoCPP["Blueprint calls C++"] BP1["Blueprint graph"] -->|"BlueprintCallable / BlueprintPure"| CPP1["C++ function runs"] end subgraph CPPtoBP["C++ calls Blueprint"] CPP2["C++ code"] -->|"BlueprintImplementableEvent / NativeEvent"| BP2["Blueprint graph runs"] end
Figure 1: Two directions, four specifiers. The left pair lets designers call your mechanics; the right pair lets your mechanics invoke designer-authored behavior.
DirectionSpecifierMeaning
BP → C++BlueprintCallableBlueprint calls your C++ (has side effects)
BP → C++BlueprintPureBlueprint calls your C++ getter (no side effects)
C++ → BPBlueprintImplementableEventC++ calls Blueprint; body defined only in BP
C++ → BPBlueprintNativeEventC++ calls; C++ has a default, BP can override

BlueprintCallable & Pure

You met these in Lesson 2.3. Now the full picture: they expose a C++ function as a node a designer drops into a Blueprint graph. This is how your mechanics become designer-usable.

// BlueprintCallable: an ACTION node with an execution pin. Has side effects.
UFUNCTION(BlueprintCallable, Category = "Combat")
void Fire();

// BlueprintPure: a data node, NO exec pin — used like a getter.
UFUNCTION(BlueprintPure, Category = "Combat")
int32 GetAmmo() const;

// Parameters and return values cross the boundary too:
UFUNCTION(BlueprintCallable, Category = "Combat")
bool TryReload(int32 AmountToLoad);

šŸ“– What "crosses" the boundary

Function parameters and return values must be reflection-friendly types — the same rule as UPROPERTY: primitives, FString/FName/FText, UObject*, and USTRUCT/UENUM marked BlueprintType (Lesson 6.3). You can't pass a raw std::vector or an un-reflected struct across — Blueprint wouldn't know how to draw the pin.

āš ļø BlueprintPure, revisited

A pure node may be evaluated multiple times or in an unpredictable order by Blueprint. Only mark truly side-effect-free getters pure (as in Lesson 2.3). Marking Fire() pure would be a disaster — the "getter" would fire your weapon whenever Blueprint felt like re-evaluating it.

BlueprintImplementableEvent

Now the reverse direction. BlueprintImplementableEvent declares a function in C++ that has no C++ body at all — you call it from C++, and a designer implements it in the Blueprint graph. It's how your mechanics hand off cosmetic or design decisions.

// Weapon.h — declared, but NOT defined in C++. Designers implement it in BP.
UFUNCTION(BlueprintImplementableEvent, Category = "Combat")
void OnFired();   // e.g. play muzzle flash, sound, camera shake

// Weapon.cpp — you CALL it from your mechanics; the BP graph runs.
void AWeapon::Fire()
{
    // ... C++ firing mechanics: spawn projectile, decrement ammo ...
    OnFired();   // triggers whatever the designer wired in Blueprint
}

āœ… The clean division of labor

Fire() (C++) owns the mechanics: projectile, ammo, damage. OnFired() (Blueprint) owns the presentation: muzzle flash, sound, screen shake — the things designers tune constantly and want to iterate on without a compile. This is the hybrid model (Lesson 1.1) at the function level: C++ decides that it fired; Blueprint decides what that looks like.

āš ļø Don't provide a C++ body for it

BlueprintImplementableEvent functions must not have a C++ implementation — UHT generates the calling glue, and defining a body causes a linker error. If a Blueprint doesn't implement it, calling it simply does nothing (safe no-op). If you need a C++ default, that's the next specifier.

BlueprintNativeEvent

BlueprintNativeEvent is the best of both: C++ provides a default implementation, and Blueprint may override it. You saw this on interfaces (Lesson 5.5); here it is on a regular class. The C++ body lives in a function with an _Implementation suffix.

// Enemy.h — has a C++ default, overridable in Blueprint.
UFUNCTION(BlueprintNativeEvent, Category = "AI")
void ReactToPlayer(AActor* Player);
// Enemy.cpp — the C++ default goes in ReactToPlayer_Implementation:
void AEnemy::ReactToPlayer_Implementation(AActor* Player)
{
    // Default behavior in C++: turn toward the player.
    if (IsValid(Player))
    {
        const FVector Dir = Player->GetActorLocation() - GetActorLocation();
        SetActorRotation(Dir.Rotation());
    }
}

// To CALL it (so a Blueprint override is respected), use Execute_ :
void AEnemy::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    if (SeesPlayer())
    {
        // Runs the BP override if present, else the C++ _Implementation.
        Execute_ReactToPlayer(this, DetectedPlayer);
    }
}

šŸ“– The two suffixes, one more time

  • Define the C++ default in Fn_Implementation.
  • Call it via Execute_Fn(Object, Args) so a Blueprint override wins when present — exactly the convention from Lesson 5.5. Calling ReactToPlayer() directly would bypass Blueprint overrides.

Choosing a Specifier

A quick router for the four:

graph TD Start["I want to..."] --> Q1{"Which direction?"} Q1 -->|"BP should call my C++"| Q2{"Side effects?"} Q2 -->|Yes| BC["BlueprintCallable"] Q2 -->|"No, it's a getter"| BP["BlueprintPure"] Q1 -->|"My C++ should call BP"| Q3{"Need a C++ default?"} Q3 -->|"No, BP-only"| BIE["BlueprintImplementableEvent"] Q3 -->|"Yes, overridable"| BNE["BlueprintNativeEvent"]
Figure 2: Direction first, then details. Nearly every interop decision reduces to these two questions.

āœ… The mental shortcut

"Blueprint drives C++" → Callable/Pure. "C++ drives Blueprint" → ImplementableEvent (no default) or NativeEvent (with default). Keep the mechanics in C++ and the tunable/cosmetic responses in Blueprint, and these choices become second nature.

Hands-on Exercise & Quiz

šŸ‹ļø Exercise: Design a door's interop surface

Objective: Pick a specifier for each function on an ADoor.

  1. Open() — designers should be able to call it from a Blueprint graph; it changes state.
  2. IsOpen() — a getter Blueprint reads for logic.
  3. OnOpened() — the C++ Open() calls this so designers can play a sound/animation; no C++ default needed.
  4. GetAutoCloseDelay() — has a sensible C++ default, but a specific door Blueprint might override it.
āœ… Answers
  • 1. UFUNCTION(BlueprintCallable) — action with side effects.
  • 2. UFUNCTION(BlueprintPure) — side-effect-free getter.
  • 3. UFUNCTION(BlueprintImplementableEvent) — C++ calls it, BP implements, no default.
  • 4. UFUNCTION(BlueprintNativeEvent) — C++ default in _Implementation, override-able, called via Execute_.

šŸŽÆ Quick Quiz

Question 1: Which lets your C++ call a designer-authored Blueprint graph, with no C++ body?

Question 2: A BlueprintNativeEvent's C++ default lives in a function named:

Question 3: To respect a Blueprint override of a BlueprintNativeEvent, call it via:

Summary

šŸŽ‰ Key Takeaways

  • Interop functions flow in two directions: BP→C++ and C++→BP. Identify the direction first.
  • BP → C++: BlueprintCallable (action, side effects) and BlueprintPure (side-effect-free getter).
  • C++ → BP: BlueprintImplementableEvent (no C++ body — BP implements) and BlueprintNativeEvent (C++ default in _Implementation, overridable).
  • Call BlueprintNativeEvent/interface functions via Execute_Fn so Blueprint overrides win.
  • Keep mechanics in C++ and cosmetic/tunable responses in Blueprint — the hybrid model at the function level.

šŸ“š Additional Resources

šŸš€ What's Next?

Functions cross the boundary; next, properties. We'll go deeper on exposing member variables to Blueprint — read/write control, expose-on-spawn, and the editor polish that makes your C++ classes a joy for designers to configure.

šŸŽ‰ Lesson complete!

The boundary is open both ways. Now let's expose the data.