š 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
BlueprintCallableandBlueprintPureto call C++ from Blueprint - Use
BlueprintImplementableEventto call Blueprint from C++ - Use
BlueprintNativeEventfor 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.
| Direction | Specifier | Meaning |
|---|---|---|
| BP ā C++ | BlueprintCallable | Blueprint calls your C++ (has side effects) |
| BP ā C++ | BlueprintPure | Blueprint calls your C++ getter (no side effects) |
| C++ ā BP | BlueprintImplementableEvent | C++ calls Blueprint; body defined only in BP |
| C++ ā BP | BlueprintNativeEvent | C++ 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. CallingReactToPlayer()directly would bypass Blueprint overrides.
Choosing a Specifier
A quick router for the four:
ā 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.
Open()ā designers should be able to call it from a Blueprint graph; it changes state.IsOpen()ā a getter Blueprint reads for logic.OnOpened()ā the C++Open()calls this so designers can play a sound/animation; no C++ default needed.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 viaExecute_.
šÆ 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) andBlueprintPure(side-effect-free getter). - C++ ā BP:
BlueprintImplementableEvent(no C++ body ā BP implements) andBlueprintNativeEvent(C++ default in_Implementation, overridable). - Call
BlueprintNativeEvent/interface functions viaExecute_Fnso 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.