Skip to main content

πŸš€ Lesson 1.5: Your First C++ Class & the Live Coding Loop

Everything so far has been groundwork. Now you'll create a real C++ actor, make it do something visible, place it in a level, and use Live Coding to change its behavior without ever closing the editor. This is the loop you'll run thousands of times.

🎯 Learning Objectives

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

  • Create a C++ actor class with the editor's class wizard
  • Read and understand every line of a generated actor's .h and .cpp
  • Add a UPROPERTY, log to the Output Log, and make the actor rotate each frame
  • Use Live Coding to compile changes while the editor stays open
  • Know when Live Coding is enough and when you need a full rebuild

Estimated Time: 60 minutes

Engine Version: Unreal Engine 5.8

In This Lesson

Creating the Class

Unreal generates the boilerplate for you. In the editor:

  1. Open Tools β†’ New C++ Class…
  2. Choose Actor as the parent class, click Next
  3. Name it SpinningCube, confirm the module, and click Create Class

Unreal creates SpinningCube.h and SpinningCube.cpp in your Source/ tree, adds them to the build, and compiles. The class name becomes ASpinningCube β€” the A prefix marks it as an actor (a convention enforced by the header tool; more on prefixes in Lesson 2.1).

graph LR A["New C++ Class wizard"] --> B["Generates SpinningCube.h + .cpp"] B --> C["Adds to module & compiles"] C --> D["Appears in Content Browser β†’ C++ Classes"]
Figure 1: The class wizard does the plumbing so you can start with logic. Behind the scenes it also regenerates project files.

Anatomy of the Generated Files

Here's what the wizard produces, annotated line by line. First the header:

// SpinningCube.h
#pragma once                       // include guard β€” appears once per header

#include "CoreMinimal.h"           // the lightweight base include for most headers
#include "GameFramework/Actor.h"   // we derive from AActor, so we need its header
#include "SpinningCube.generated.h" // MUST be last β€” generated reflection code

UCLASS()                           // reflection macro: register this class
class MYPROJECT_API ASpinningCube : public AActor
{
    GENERATED_BODY()               // expands to engine boilerplate for this class

public:
    ASpinningCube();               // constructor: set defaults, create components

protected:
    virtual void BeginPlay() override;   // called once when play starts

public:
    virtual void Tick(float DeltaTime) override;  // called every frame
};

πŸ“– MYPROJECT_API?

That macro controls DLL export so other modules can use your class. The header tool defines it per module (it'll match your project's name). Leave it as generated β€” it matters when other modules link against yours.

Now the source file:

// SpinningCube.cpp
#include "SpinningCube.h"

// Constructor: runs when the object is created (in editor AND at runtime).
ASpinningCube::ASpinningCube()
{
    // Tell the engine this actor wants Tick() called every frame.
    // (It's off by default for performance β€” you opt in.)
    PrimaryActorTick.bCanEverTick = true;
}

// Called once, when the game starts or the actor is spawned into a running level.
void ASpinningCube::BeginPlay()
{
    Super::BeginPlay();   // ALWAYS call the base version first
}

// Called every frame; DeltaTime is seconds since the last frame.
void ASpinningCube::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);   // ALWAYS call the base version
}

⚠️ Never forget Super::

When you override an engine lifecycle function (BeginPlay, Tick, etc.), call Super:: β€” it runs the base class's implementation, which does real engine work. Forgetting it causes subtle, hard-to-diagnose bugs.

Making It Do Something

Let's give the cube a visible mesh, an exposed rotation-speed property, a startup log, and per-frame rotation. This touches everything from Module 1 at once. The header first:

// SpinningCube.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SpinningCube.generated.h"

class UStaticMeshComponent;   // forward declaration β€” we only hold a pointer here

UCLASS()
class MYPROJECT_API ASpinningCube : public AActor
{
    GENERATED_BODY()

public:
    ASpinningCube();

protected:
    virtual void BeginPlay() override;

public:
    virtual void Tick(float DeltaTime) override;

    // The visible cube. VisibleAnywhere: shown but not reassignable in Details.
    UPROPERTY(VisibleAnywhere, Category = "Spinning Cube")
    UStaticMeshComponent* Mesh;

    // Degrees per second. EditAnywhere + BlueprintReadWrite so designers and
    // Blueprint subclasses can tune it. This appears in the Details panel.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spinning Cube")
    float RotationSpeed = 90.0f;
};

Now the implementation:

// SpinningCube.cpp
#include "SpinningCube.h"
#include "Components/StaticMeshComponent.h"   // full header β€” we call its members

ASpinningCube::ASpinningCube()
{
    PrimaryActorTick.bCanEverTick = true;

    // Create the mesh component and make it this actor's root.
    // CreateDefaultSubobject is the constructor-only way to build components.
    Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    RootComponent = Mesh;
}

void ASpinningCube::BeginPlay()
{
    Super::BeginPlay();

    // Our first real log: %s prints an FString; the leading * converts
    // GetName()'s FString to the raw characters the format expects.
    UE_LOG(LogTemp, Warning, TEXT("%s is ready to spin at %.1f deg/s"),
        *GetName(), RotationSpeed);
}

void ASpinningCube::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    // Frame-rate independent rotation: multiplying by DeltaTime means the cube
    // spins RotationSpeed degrees PER SECOND regardless of frame rate.
    AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
}

βœ… Why * DeltaTime matters

Without it, the cube would rotate RotationSpeed degrees per frame β€” spinning faster on fast machines. Multiplying by DeltaTime (seconds since last frame) makes motion depend on time, not frame rate. This is a universal game-programming habit; make it reflex.

πŸ“– CreateDefaultSubobject

This is the only correct way to create a component from a constructor. It registers the component with the engine's object system. Using plain new here would break serialization and garbage collection β€” a rule we'll justify fully in Module 2 and Module 3.

Place, Compile, Play

With the code written:

  1. Compile β€” press the Compile button in the editor toolbar (or build from your IDE). This triggers Live Coding (next section).
  2. Assign a mesh β€” drag ASpinningCube from C++ Classes into the level, select it, and in the Details panel set the mesh component's Static Mesh to the engine's Cube (or make a Blueprint subclass with the mesh pre-set β€” the Module 6 way).
  3. Press Play β€” the cube spins, and the Output Log shows your "…is ready to spin at 90.0 deg/s" line.
  4. Tune it live β€” while stopped, change RotationSpeed in Details to 360 and play again. No recompile needed, because the value is data, not code.
πŸ’‘ Data vs code: changing a UPROPERTY value in Details is instant β€” it's data on an instance. Changing the logic in .cpp requires a compile. Knowing which kind of change you're making tells you whether you need Live Coding.

The Live Coding Loop

Live Coding lets you recompile C++ and patch it into the running editor β€” no restart. It's the feature that makes C++ iteration bearable. The loop:

graph LR A["Edit .cpp"] --> B["Press Ctrl+Alt+F11
(or Compile button)"] B --> C["Live Coding recompiles"] C --> D["Editor patches the code in place"] D --> E["Test immediately"] E --> A
Figure 2: The Live Coding loop. Ctrl+Alt+F11 triggers a compile while the editor keeps running.

What Live Coding handles well

  • Changes to function bodies β€” new logic inside Tick, BeginPlay, your own methods
  • Adjusting calculations, adding logs, fixing behavior

What needs a full rebuild (close editor, build, reopen)

  • Adding or removing member variables or UPROPERTY declarations (changes the object's memory layout)
  • Adding or removing classes, changing a class's parent, or editing headers structurally
  • Changing reflection macros (new UFUNCTION, changed specifiers)

⚠️ The rule of thumb

Edits inside .cpp function bodies β†’ Live Coding. Edits to .h structure (new members, new classes, changed inheritance) β†’ close the editor and do a full build. Trying to Live-Code a header layout change is the most common source of "why didn't my change take?" β€” and occasionally of instability. When in doubt, full rebuild.

βœ… Pro Tip

Live Coding is enabled by default in 5.8. You'll see its status in the bottom-right of the editor. If a Live Coding patch ever leaves the editor in a weird state, a full close-and-rebuild always resets you to solid ground.

Hands-on Exercise & Quiz

πŸ‹οΈ Exercise: Extend the cube

Objective: Practice the write β†’ Live Code β†’ test loop and feel the Live-Coding boundary.

  1. Add a bobbing motion: in Tick, also move the cube up and down over time. (Hint: accumulate time and use FMath::Sin, then AddActorLocalOffset.)
  2. Compile with Ctrl+Alt+F11 and confirm the change appears without restarting the editor.
  3. Now add a new UPROPERTY float BobHeight. Try Live Coding β€” note that this header change needs a full rebuild. Do the rebuild and confirm BobHeight appears in Details.
πŸ’‘ Hint β€” the bob in Tick
// Add a member: float RunningTime = 0.0f;  (in the header β€” needs a rebuild)
RunningTime += DeltaTime;
const float Offset = FMath::Sin(RunningTime) * BobHeight * DeltaTime;
AddActorLocalOffset(FVector(0.0f, 0.0f, Offset));
βœ… What you should observe

Step 1's logic change (if you hardcode values) Live-Codes fine. The moment you add the RunningTime or BobHeight members in the header, Live Coding can't apply it and you need a full rebuild β€” exactly the boundary from the rule of thumb.

🎯 Quick Quiz

Question 1: Why multiply rotation by DeltaTime?

Question 2: Which change can Live Coding apply without an editor restart?

Question 3: What's the correct way to create a component in a constructor?

Summary

πŸŽ‰ Key Takeaways

  • The New C++ Class wizard generates a compile-ready actor; the A prefix marks actors.
  • Always call Super:: when overriding lifecycle functions like BeginPlay and Tick.
  • Create components in the constructor with CreateDefaultSubobject<T> β€” never plain new.
  • Multiply per-frame motion by DeltaTime for frame-rate independence; expose tunables with UPROPERTY.
  • Live Coding hot-patches .cpp body edits into the running editor; structural .h changes need a full rebuild.

πŸ“š Additional Resources

πŸš€ What's Next?

You've shipped your first working C++ actor β€” congratulations, that's the hardest first step. Module 2 goes under the hood: the reflection system that made UCLASS and UPROPERTY work, Unreal's own types and containers, and the memory model that explains why we never use plain new.

πŸŽ‰ Module 1 complete!

You can create, compile, and iterate C++ in Unreal. Now let's understand what's really happening.