Unreal Engine 5.8 tutorial

Part 2 — Blueprints vs C++

How Blueprints and C++ relate in Unreal Engine 5.8, when to use which, reading a Blueprint graph, and writing a first custom C++ Actor with UPROPERTY and UFUNCTION.

2.1 What each one is

  • Blueprints are Unreal’s visual scripting language: node graphs you wire together. They compile to bytecode run by a VM. Think of them as a typed, visual, gameplay-focused scripting layer — like a domain-specific language that’s fast to iterate in and impossible to forget-to-recompile.
  • C++ is the engine’s native language. Everything Blueprints can do, C++ can do, plus the things Blueprints can’t (low-level systems, performance-critical loops, new core types).

2.2 When to use which (the honest version)

Use Blueprints for: rapid prototyping, level scripting, UI logic, designer-tweakable behavior, anything you want to iterate on without a compile cycle, and one-off gameplay.

Use C++ for: core systems and base classes, performance-critical code (anything in a tight per-frame loop over many objects), complex math/algorithms, things you want under source control as text and diffable, and reusable framework you’ll subclass in Blueprint.

The standard professional pattern: write base classes and systems in C++, expose tunable values and events to Blueprint with UPROPERTY(EditAnywhere, BlueprintReadWrite) and UFUNCTION(BlueprintCallable), then subclass those C++ classes as Blueprints for the designer-facing layer. You get C++ performance + Blueprint iteration speed. You do not have to choose globally.

Performance note: a Blueprint VM node is far slower than native C++ per call. For 5 things per frame it’s irrelevant; for 50,000 it matters. Don’t prematurely rewrite working Blueprints in C++ — profile first (Part 9).

2.3 Reading a Blueprint graph

  • Event nodes (red) — entry points: Event BeginPlay, Event Tick, input events, custom events.
  • Execution pins (white triangles, the wire along the top) — control flow / order of execution. This is your call stack made visible.
  • Data pins (colored, typed) — values flowing between nodes. Color = type (red=bool, green=float, blue=object reference, etc.).
  • Variables & Functions (left panel) — your member fields and methods.
  • Right-click the graph to search the entire node palette. Drag off a pin to get only context-valid nodes.

2.4 A first taste of C++ (a custom Actor)

Add C++ to a project via Tools → New C++ Class. Pick a parent (e.g. Actor). Unreal generates a header/source pair and regenerates the project files. A minimal Actor:

// Pickup.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Pickup.generated.h"   // MUST be the last include

UCLASS()
class MYGAME_API APickup : public AActor
{
    GENERATED_BODY()

public:
    APickup();

    // EditAnywhere -> tweak per-instance in the editor.
    // BlueprintReadWrite -> readable/writable from Blueprint.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pickup")
    int32 ScoreValue = 10;

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<class UStaticMeshComponent> Mesh;   // GC-tracked member pointer

protected:
    virtual void BeginPlay() override;

    UFUNCTION()  // required so the reflection system can bind this to an event
    void OnOverlap(UPrimitiveComponent* Overlapped, AActor* Other,
                   UPrimitiveComponent* OtherComp, int32 BodyIndex,
                   bool bFromSweep, const FHitResult& Sweep);
};
// Pickup.cpp
#include "Pickup.h"
#include "Components/StaticMeshComponent.h"

APickup::APickup()
{
    PrimaryActorTick.bCanEverTick = false;     // no per-frame cost we don't need
    Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    RootComponent = Mesh;
}

void APickup::BeginPlay()
{
    Super::BeginPlay();
    Mesh->OnComponentBeginOverlap.AddDynamic(this, &APickup::OnOverlap);
}

void APickup::OnOverlap(UPrimitiveComponent*, AActor* Other, UPrimitiveComponent*,
                        int32, bool, const FHitResult&)
{
    // award score to Other, then:
    Destroy();
}

Things to notice as a developer:

  • The macros (UCLASS, UPROPERTY, UFUNCTION, GENERATED_BODY) feed the Unreal Header Tool, which code-generates the reflection/serialization glue at build time. The .generated.h include is mandatory and must be last.
  • MYGAME_API is the module export macro (DLL boundary). Auto-generated per module.
  • Super:: is how you call the parent implementation — every override should usually call it.
  • You build by compiling in your IDE or using Live Coding (Ctrl+Alt+F11 in the editor) to hot-reload C++ without restarting. Live Coding is great for function bodies; structural changes (new UPROPERTYs) want a full rebuild.
Exercise 2

In the Third Person template, open BP_ThirdPersonCharacter. Add a float variable Health = 100. On Event BeginPlay, print it with a Print String node. Then (optional) create a C++ Actor subclass, add an EditAnywhere float, compile, drag it into the level, and confirm the property shows in the Details panel. You’ve now touched both worlds and seen them interoperate.