Intermediate30 min read

From Blueprint to C++: A Migration Guide

When to keep logic in Blueprints, when to move to C++, and how to maintain a productive hybrid workflow.

When to Stay in Blueprint

Blueprint isn't just for beginners. Many production games ship with significant Blueprint code. Stay in Blueprint when:

  • Rapid iteration matters: UI, prototypes, game modes
  • Designers need access: Tunable gameplay parameters
  • Performance isn't critical: Event-driven logic, one-time initialization
  • Visual debugging helps: Complex state machines, AI behavior trees

When to Move to C++

Consider C++ when:

  • Tight loops: Per-frame calculations over many objects
  • Core systems: Networking, save systems, complex data structures
  • Engine integration: Custom components, subsystems, editor tools
  • Team scale: Large teams benefit from code review, typing, IDE features
  • Reusability: Systems that will be used across multiple projects

Setting Up C++

Converting a Blueprint Project

  1. File → New C++ Class
  2. Choose a parent class (Actor, Character, etc.)
  3. Name your class
  4. UE generates files and opens your IDE
  5. Project is now a mixed Blueprint/C++ project

Project Structure

MyProject/
├── Source/
│   └── MyProject/
│       ├── MyProject.Build.cs
│       ├── MyProject.h
│       ├── MyProject.cpp
│       └── MyCharacter.cpp/h
├── Content/
│   └── Blueprints/
└── MyProject.uproject

The Hybrid Approach

The best workflow uses both: C++ for the heavy lifting, Blueprint for the creative layer.

C++ Base, Blueprint Extension

// C++: Base character with core logic
UCLASS(Blueprintable)
class AMyCharacter : public ACharacter
{GENERATED_BODY() public: UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) float MaxHealth = 100.0f; UFUNCTION(BlueprintCallable) void TakeDamage(float Amount); UFUNCTION(BlueprintImplementableEvent) void OnDeath();};

Designers create Blueprint children of the C++ class, overrideOnDeath to add effects, tweak MaxHealth per character type.

BlueprintNativeEvent Pattern

// C++ has default implementation
UFUNCTION(BlueprintNativeEvent)
void OnHit(float Damage);

// Implementation
void AMyCharacter::OnHit_Implementation(float Damage)
{Health -= Damage; // Blueprint can override to add effects}

Migration Strategy

Step 1: Identify the Hot Path

Don't migrate everything. Use stat Blueprint to find what's actually expensive. Focus on:

  • Functions called in Tick
  • Loops over large arrays
  • Functions called hundreds of times per frame

Step 2: Create C++ Interface

Write the C++ function with the same signature as your Blueprint function. Make it BlueprintCallable so the transition is seamless.

UFUNCTION(BlueprintCallable, Category="Combat")
float CalculateDamage(AActor* Target, float BaseDamage);

Step 3: Call C++ from Blueprint

Replace the Blueprint implementation with a single node calling your C++ function. This lets you verify behavior before removing the Blueprint version entirely.

Step 4: Move More Logic

Once verified, move related logic to C++. Group related functions into classes (DamageCalculator, InventoryManager, etc.).

Common Patterns

Exposing C++ to Blueprint

// Callable from Blueprint
UFUNCTION(BlueprintCallable)
void DoThing();

// Readable/writable property
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Speed;

// Override in Blueprint
UFUNCTION(BlueprintNativeEvent)
void OnSpawn();

// Pure Blueprint implementation
UFUNCTION(BlueprintImplementableEvent)
void OnCustomEvent();

Delegates for Events

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
    FOnHealthChanged, float, NewHealth);

UPROPERTY(BlueprintAssignable)
FOnHealthChanged OnHealthChanged;

// In C++
OnHealthChanged.Broadcast(Health);

// Blueprint binds to this event

Data-Only Blueprints

Create C++ base classes with BlueprintReadWrite properties. Designers create Blueprint children that only set data—no graph logic. Best of both worlds: type safety + designer control.

Performance Comparison

OperationBlueprintC++Speedup
Function call overhead~1μs~10ns~100x
Vector math (1000x)~0.5ms~0.01ms~50x
Array iteration (1000 elements)~0.3ms~0.01ms~30x
Simple branch logic~0.5μs~5ns~100x

Raw speedup is significant, but remember: most code isn't in tight loops. A function that runs once per frame at 0.001ms doesn't benefit from being 100x faster.

Maintaining Both

Coding Standards

  • C++ base classes go in Source/
  • Blueprint children go in Content/Blueprints/
  • Name Blueprint children with BP_ prefix
  • Document which functions are meant to be overridden

Testing

  • Unit test C++ logic (Unreal Automation Framework)
  • Functional test Blueprint behavior (in-editor playtests)
  • Performance test both (stat commands, profiler)

Version Control

  • C++ files: normal Git workflow
  • Blueprint binaries: Git LFS
  • Blueprint merge conflicts: often easier to redo than resolve

Common Mistakes

  • Migrating too early: Profile first, optimize after
  • Migrating everything: Keep designer-facing logic in Blueprint
  • Poor interface design: Think about the Blueprint experience when designing C++ APIs
  • Forgetting hot reload limits: Adding properties requires full rebuild
  • Over-engineering: Start simple, refactor when needed

Summary

The Blueprint-to-C++ migration isn't binary. The most effective teams use both: C++ for systems and performance-critical code, Blueprint for rapid iteration and designer accessibility.

Start in Blueprint. Migrate when profiling shows you need to. Maintain clean interfaces between the two layers. This hybrid approach gives you the best of both worlds.