Every chapter so far used Unity and C#. This chapter switches engines. Unreal Engine is a different game engine made by Epic Games, and it thinks about a running game differently from Unity. The good news: you already know what a game engine is — a program that gives you a 3D world, a renderer, physics, input, and an editor, then calls your code every frame. What changes is the vocabulary and the building blocks. This chapter covers Unreal's core building block, the Actor, the reflection system that makes Unreal's editor and Blueprint tools work, and the small set of framework classes (GameMode, Pawn, Character, PlayerController) that every Unreal project is built from.
We will keep contrasting with Unity the whole way, because that contrast is the fastest way to learn a second engine: you are not learning "what is a game engine" from zero, you are learning "how does this engine's version of the thing I already know work."
Unreal Engine is a general-purpose game engine, in active development since 1998, currently on its fifth major version (UE5). Like Unity, it ships an editor, a renderer, physics, animation tools, audio, networking, and a way to package a build for PC, console, or mobile. Two things separate it from Unity in practice:
Unreal is known for high-end visual fidelity and is the default choice for a lot of AAA console and PC development. Studios and games built on it include Epic Games' own Fortnite and the Gears of War series, Final Fantasy VII Remake/Rebirth (Square Enix), the Star Wars Jedi series (Respawn), Tekken 8 (Bandai Namco), Black Myth: Wukong (Game Science), and CD Projekt Red's upcoming Witcher games, which moved from their own engine to UE5. Plenty of mobile and live-service studios — including some that ship on Unity, like the earlier chapters' reference point HoYoverse — mix in Unreal for specific titles or evaluate it for projects that need Unreal's rendering quality. Knowing both engines is a normal, valuable thing on a game programmer's resume; they solve the same problems with different opinions about how code should be organized.
In Unity, the object you place in a scene is a GameObject — an empty container. It does nothing by itself; every capability, including the Transform (position/rotation/scale) itself, comes from attached Components, and a MonoBehaviour script is just one more component type.
In Unreal, the object you place in a level is an Actor (its C++ base class is AActor). An Actor is "a thing that can exist in the world" — it already comes with a position/rotation/scale and a set of lifecycle calls (which we cover in section 7) built into the base class itself. An Actor can also own Components (its C++ base class is UActorComponent), which are objects attached to the Actor that add a specific capability: a mesh to render, a camera, a collision shape, a movement rule. This is close in spirit to Unity's GameObject + Component model, but the split is different:
Two differences worth naming clearly:
AEnemy, APickup, ADoor), each optionally using a handful of Components for the reusable pieces. Neither approach is "more correct" — Unreal simply defaults the other way from Unity.Almost every engine class you touch in Unreal — AActor, UActorComponent, even non-Actor helper objects — ultimately derives from one root class: UObject. AActor is a UObject (through a chain of base classes), which is the entire reason Actors get three things for free, none of which exist in plain C++:
new and delete yourself (you saw exactly how dangerous that is in the memory chapters). UObjects are tracked and freed automatically by Unreal's own garbage collector, as long as you hold references to them the way the engine expects (that is what UPROPERTY, in the next section, is partly for).UObject's class at runtime: its exact type, its parent class, the list of its properties and functions, all without you writing separate metadata by hand. Standard C++ has no built-in reflection at all — Epic built their own system for it.Reflection is not magic — it is generated code. Unreal's build step runs a tool called the Unreal Header Tool (UHT) before the real C++ compiler does. UHT scans your header files for a specific set of macros (covered next), and for every class it finds, it writes an extra C++ file full of boilerplate — the class's .generated.h file — that plugs your class into the reflection system. This is why every Unreal class header ends with an include like #include "MyActor.generated.h", and why that include must always be the last include in the file: UHT needs to have seen everything above it first.
#include "MyActor.generated.h" anywhere but last, or forgetting it entirely. Both are compile errors in a real Unreal project. The generated file always comes last because it depends on everything declared above it in the same header.Unity gets reflection "for free" from the .NET runtime — a C# field marked with the attribute [SerializeField] is discoverable at runtime through .NET's built-in System.Reflection, no extra build step required. C++ has no such runtime reflection, so Unreal opts individual classes, properties, and functions into its own hand-built reflection system using macros that UHT reads. There are three you will use constantly:
UCLASS(...) — goes directly above a class declaration. Marks the class for reflection. The class must (directly or indirectly) inherit from UObject, and its very first line inside the class body must be the macro GENERATED_BODY(), which pulls in the boilerplate UHT generated for this class.UPROPERTY(...) — goes directly above a member variable. Exposes that variable to the systems above: EditAnywhere makes it editable in the editor's Details panel (on every instance), VisibleAnywhere shows it but not editable, BlueprintReadWrite / BlueprintReadOnly let Blueprint graphs read (and maybe write) it, and Category = "..." groups it visually in the Details panel. It also has a quieter, critical job: it tells the garbage collector that this variable holds a reference to a UObject, so the collector will not free that object out from under you. A raw C++ pointer to a UObject that is not marked UPROPERTY can be garbage-collected without warning — a real and common source of crashes for beginners.UFUNCTION(...) — goes directly above a member function. BlueprintCallable lets Blueprint graphs call it like a node; BlueprintImplementableEvent declares a function whose implementation lives entirely in Blueprint; specifiers like Server, Client, and NetMulticast mark it as a networked remote-procedure call. Some engine lifecycle methods (like BeginPlay, covered in section 7) are already UFUNCTIONs in the base class, so your override does not need to repeat the macro.Here is a small, complete Actor that uses all three. This is the C++ equivalent of a Unity script with [SerializeField] private float health = 100f; and a public method.
// HealthActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HealthActor.generated.h" // must be the LAST include
UCLASS()
class MYGAME_API AHealthActor : public AActor
{
GENERATED_BODY() // must be the first line in the class body
public:
AHealthActor();
// Shows up as an editable slider in the Details panel, grouped
// under "Health". Blueprint graphs can read AND write it too.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
float Health = 100.0f;
// Blueprint graphs can call this like a node. C++ callers can
// call it directly, same as any normal member function.
UFUNCTION(BlueprintCallable, Category = "Health")
void TakeDamage(float Amount);
protected:
virtual void BeginPlay() override;
};
// HealthActor.cpp
#include "HealthActor.h"
AHealthActor::AHealthActor()
{
PrimaryActorTick.bCanEverTick = false; // this actor never needs per-frame work
}
void AHealthActor::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("HealthActor spawned with %f HP"), Health);
}
void AHealthActor::TakeDamage(float Amount)
{
Health -= Amount;
UE_LOG(LogTemp, Warning, TEXT("Took %f damage, Health is now %f"), Amount, Health);
}
UE_LOG is Unreal's version of std::cout or Unity's Debug.Log — it writes a line to the editor's Output Log window. TEXT("...") wraps a string literal so it matches Unreal's string type on every platform; you will see it around almost every string literal in Unreal C++. If you drop this Actor into a level and press Play, the Output Log shows this trace:
LogTemp: Warning: HealthActor spawned with 100.000000 HP
Then call TakeDamage(25.0f) from anywhere with a pointer to this actor (another Actor's code, or a Blueprint node), and the log gains a second line:
LogTemp: Warning: Took 25.000000 damage, Health is now 75.000000
And because Health is EditAnywhere, selecting this actor in the level opens a Details panel that looks roughly like this — no extra code needed to get an editable slider:
To put the three benefits side by side: UPROPERTY's EditAnywhere is editor exposure (a designer can tune Health per-instance without touching code), the reflection data behind every UPROPERTY is what makes serialization work (that 100.0 gets saved into the level file, and can be saved into a save-game slot), and BlueprintReadWrite/BlueprintCallable are what give you Blueprint access (a designer's Blueprint graph can read Health or call TakeDamage without seeing a line of C++). One pair of macros, three payoffs.
Now that reflection makes sense, back to the Actor itself. AActor is the base class for anything that can exist in a level: a character, a light, a pickup, an invisible trigger volume, even the camera. Every Actor carries, from the base class alone, before you add anything:
RootComponent.BeginPlay, Tick, EndPlay.GetWorld()->SpawnActor<T>(...) and Destroy() — this is Unreal's equivalent of Unity's Instantiate and Destroy.Notice the class name starts with A. Unreal uses a small set of one-letter prefixes so you can tell a type's category from its name alone: A for Actor subclasses, U for other UObject-derived classes (including all Components), F for plain C++ structs (like FVector, FString), E for enums, and I for interfaces. Unity has no equivalent convention — a Unity MonoBehaviour and a plain C# class both look like ordinary PascalCase names with no prefix.
Here is the smallest complete Actor you can write — no components, no properties, just the shell:
// MyActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
};
// MyActor.cpp
#include "MyActor.h"
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = true; // allow Tick() to run (see section 7)
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
}
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
Drop this into a level and press Play: nothing visible happens, because it has no mesh and does no work yet, but it does exist — it has a location, it received BeginPlay once, and it is receiving Tick every frame. That "empty but alive" object is the equivalent of an empty Unity GameObject that already has a script attached with an empty Start() and Update().
An Actor on its own has a transform and lifecycle calls, but nothing to render and no collision. You add those capabilities by attaching Components — objects (deriving from UActorComponent) that plug into the Actor and do one job each. The three you will meet immediately:
USceneComponent — the base for any component that has its own transform and can be attached under another one, forming a tree. An Actor's RootComponent is normally a USceneComponent (or a subclass of one).UStaticMeshComponent — renders a static (non-animated) 3D mesh, roughly Unity's MeshFilter + MeshRenderer combined into one component.UCameraComponent — a camera viewpoint, roughly Unity's Camera component.In Unity you usually attach components in the editor by dragging them onto a GameObject, or at runtime with gameObject.AddComponent<T>(). In Unreal C++, the standard place to create an Actor's default components is inside its constructor, using CreateDefaultSubobject<T>(TEXT("Name")). This runs once when the class is first set up (technically, when its Class Default Object is built) and produces the components every instance of this Actor starts with — closer to configuring a Unity prefab than to calling AddComponent at runtime.
// PickupActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PickupActor.generated.h"
UCLASS()
class MYGAME_API APickupActor : public AActor
{
GENERATED_BODY()
public:
APickupActor();
UPROPERTY(VisibleAnywhere, Category = "Components")
USceneComponent* Root;
UPROPERTY(VisibleAnywhere, Category = "Components")
UStaticMeshComponent* Mesh;
};
// PickupActor.cpp
#include "PickupActor.h"
APickupActor::APickupActor()
{
PrimaryActorTick.bCanEverTick = false;
// Create a root so the actor has a transform to attach things to.
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
SetRootComponent(Root);
// Create the mesh and attach it under the root.
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
Mesh->SetupAttachment(Root);
}
The component tree this builds looks like this once the object exists in the world:
Every UPROPERTY component pointer here is also VisibleAnywhere, so selecting a placed APickupActor shows both components listed in the Details panel — you (or a designer) can then assign an actual mesh asset to Mesh without writing more code.
Every Actor goes through the same sequence of calls. If you already know Unity's Awake/Start/Update/OnDestroy, this will feel familiar with different names and one important trap.
AMyActor()). This is where you call CreateDefaultSubobject. Unlike Unity's Awake, the constructor also runs for objects that only exist as editor data (Class Default Objects, used to preview and store default values in the editor), not just for actors placed in a running game. Because of that, code here should be limited to setting up components and default values — never assume the level, other actors, or a running game world exist yet.BeginPlay() — the closest match to Unity's Start(). Called exactly once, only once the actor is part of a world that is actually running. This is where you look up references to other actors, start timers, or play an intro effect.Tick(float DeltaTime) — the closest match to Unity's Update(). Called every frame, but only if you opted in: PrimaryActorTick.bCanEverTick = true; in the constructor. Unreal ticks are opt-in, per actor, on purpose — an empty Tick override on thousands of unused actors is wasted overhead, so the engine makes you ask for it explicitly.EndPlay(EEndPlayReason::Type Reason) — the closest match to Unity's OnDestroy(). Called once when the actor is about to stop existing in a running world, whether because it was explicitly destroyed, the level is unloading, or play mode is stopping.A worked example that touches three of these together, tracking how long the actor has been alive:
// TimedActor.h (declarations only -- assume the usual .generated.h setup)
UCLASS()
class MYGAME_API ATimedActor : public AActor
{
GENERATED_BODY()
public:
ATimedActor();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
UPROPERTY(VisibleAnywhere, Category = "Timer")
float TimeAlive = 0.0f;
};
// TimedActor.cpp
#include "TimedActor.h"
ATimedActor::ATimedActor()
{
PrimaryActorTick.bCanEverTick = true;
}
void ATimedActor::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("TimedActor: BeginPlay, TimeAlive = %f"), TimeAlive);
}
void ATimedActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
TimeAlive += DeltaTime;
if (TimeAlive > 5.0f)
{
UE_LOG(LogTemp, Warning, TEXT("TimedActor: been alive over 5 seconds"));
}
}
void ATimedActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UE_LOG(LogTemp, Warning, TEXT("TimedActor: EndPlay, TimeAlive = %f"), TimeAlive);
Super::EndPlay(EndPlayReason);
}
Trace it: at frame 0, BeginPlay fires once, printing TimeAlive = 0.000000. Every following frame, Tick adds that frame's DeltaTime (a fraction of a second) to TimeAlive. Once the running total crosses 5.0, the "been alive over 5 seconds" line starts printing every single frame from then on (this code has no flag to print it only once — that is left for exercise 1). If you stop Play in the editor, EndPlay fires once with whatever TimeAlive reached.
GetWorld() — in the constructor instead of BeginPlay(). The constructor can run for editor-only preview objects where there is no running world at all, so world-dependent code there can crash or silently do nothing. Constructor: build components and set defaults. BeginPlay: everything that needs a live game world.Super::BeginPlay(), Super::Tick(DeltaTime), and Super::EndPlay(...) inside your overrides, the same reflex as calling base.Start() in a Unity script that overrides a base class method. The base class's own logic often matters — skip it and things break in ways that are hard to trace.Unity gives you a blank scene and no opinion about how a "player," a "match," or "input" should be structured — you build a GameManager singleton and a player controller script yourself, from scratch, on every project. Unreal ships an opinionated starter structure for exactly that, called the Gameplay Framework: a small set of built-in Actor classes whose jobs are already divided up, and which already know how to talk to each other. This chapter focuses on four of them:
AGameModeBase (or its multiplayer-aware cousin AGameMode) — the rulebook. Decides which classes to spawn for a match.APawn — anything in the world that a controller (human or AI) can possess and drive.ACharacter — a specialized APawn built for a bipedal, walking character, with movement and collision already wired up.APlayerController — the object that represents one human player's connection and input, separate from the Pawn it is currently driving.Here is how a single-player session wires them together the moment Play starts:
Read the diagram as a sequence: the GameMode spawns a PlayerController and a Pawn (using whichever classes it was told to use), the PlayerController then possesses the Pawn (meaning: input from this point on is routed into that Pawn), and the Pawn, like any Actor, owns whatever Components it needs to look and behave like something. In bigger, especially multiplayer, projects you will also meet AGameState (shared match data visible to everyone, like the score) and APlayerState (per-player data, like one player's score) — they sit alongside this diagram but are out of scope for this chapter.
Unity has no built-in equivalent of GameMode — every Unity project reinvents its own "who spawns first, what class is the player" logic, usually as a hand-rolled singleton. Unreal formalizes that one job into a class: AGameModeBase exists specifically to answer "what are the rules of this session," and the most common rule it answers is which classes to spawn.
Set per-level (or as a project-wide default), a GameMode subclass typically just fills in a few class references in its constructor:
// MyGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyGameMode.generated.h"
UCLASS()
class MYGAME_API AMyGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AMyGameMode();
};
// MyGameMode.cpp
#include "MyGameMode.h"
#include "MyCharacter.h"
#include "MyPlayerController.h"
AMyGameMode::AMyGameMode()
{
DefaultPawnClass = AMyCharacter::StaticClass();
PlayerControllerClass = AMyPlayerController::StaticClass();
}
That is the entire class: two assignments. StaticClass() returns a handle to the class itself (its reflection data), not an instance — Unreal needs the class here because it is going to SpawnActor from it later, once per player. When Play starts, the engine looks at which GameMode the current level uses, reads DefaultPawnClass and PlayerControllerClass off it, and spawns one of each — that is the top box in section 8's diagram doing its job.
If you want a designer to be able to pick the Pawn class from the editor instead of hard-coding it in C++, expose it as a UPROPERTY using TSubclassOf — a type-safe "pointer to a class" (not an instance) that the Details panel can show as a dropdown of matching classes:
UPROPERTY(EditDefaultsOnly, Category = "Classes")
TSubclassOf<APawn> DefaultPawnClassOverride;
TSubclassOf<APawn> reads as "a class that is APawn or a subclass of it" — the compiler and the editor both enforce that, so you cannot accidentally assign a class that has nothing to do with Pawns.
APawn is the base class for anything in the world that a Controller — human or AI — can possess and drive. Not every Actor is a Pawn: a door, a static prop, a trigger volume are Actors but never Pawns, because nothing is meant to "drive" them. A Pawn is specifically the kind of Actor built to receive control input.
ACharacter is a subclass of APawn, purpose-built for a walking, bipedal character. It adds two things out of the box that a plain APawn does not have:
UCapsuleComponent as its root — a capsule-shaped collision volume, the standard shape for a walking character (this is Unreal's version of Unity's CharacterController component, which also uses a capsule).UCharacterMovementComponent — handles walking, running, jumping, falling, and swimming rules for you, including slope limits and step-up height. In Unity you either write this yourself on top of CharacterController, or use its handful of built-in methods directly; Unreal's version is a larger, more complete built-in system.A minimal playable Character, including a camera on a spring arm (a component that keeps the camera at a fixed distance behind the character and pushes it closer if something blocks the view — Unreal's equivalent of writing your own third-person camera-collision code in Unity):
// MyCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent;
class UInputComponent;
UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
protected:
virtual void BeginPlay() override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
void MoveForward(float Value);
void MoveRight(float Value);
UPROPERTY(VisibleAnywhere, Category = "Camera")
USpringArmComponent* SpringArm;
UPROPERTY(VisibleAnywhere, Category = "Camera")
UCameraComponent* Camera;
};
// MyCharacter.cpp
#include "MyCharacter.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/InputComponent.h"
AMyCharacter::AMyCharacter()
{
// RootComponent, CapsuleComponent, and the movement component
// already exist -- ACharacter's own constructor made them.
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(RootComponent);
SpringArm->TargetArmLength = 400.0f;
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->SetupAttachment(SpringArm);
}
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
}
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);
}
void AMyCharacter::MoveForward(float Value)
{
if (Controller && Value != 0.0f)
{
AddMovementInput(GetActorForwardVector(), Value);
}
}
void AMyCharacter::MoveRight(float Value)
{
if (Controller && Value != 0.0f)
{
AddMovementInput(GetActorRightVector(), Value);
}
}
SetupPlayerInputComponent is where a Pawn/Character binds raw input axes to its own functions — this specific example uses the classic Axis Mapping system (set up in Project Settings), the simplest way to see the idea; newer Unreal versions favor a more flexible system called Enhanced Input, which is worth learning once these basics are solid. Notice the guard if (Controller && ...) — Controller is a pointer this Pawn only has while something possesses it, covered next.
Super::SetupPlayerInputComponent(PlayerInputComponent); at the top of an override. If your class ever derives further, or the base class binds its own input, skipping the Super call silently drops that behavior — same trap as forgetting Super::BeginPlay().In Unity there is usually no separate object for "the player's connection" — you read Input.GetAxis(...) directly inside whatever script sits on the player GameObject, and if that GameObject is destroyed, your input-handling code goes with it. Unreal splits this on purpose: APlayerController represents one human player's control connection, and it is not a physical thing in the level — no mesh, usually no visible presence at all. Its whole job is: read hardware input, and forward the resulting commands into whichever Pawn it currently possesses.
The separation matters for one concrete reason: the Controller persists across Pawn changes. If your Character dies and a new one gets spawned to respawn the player, the same PlayerController can simply possess the new Pawn — the player's connection, camera preferences, and any UI state tied to the controller survive, only the Pawn (the physical body in the world) is replaced.
// MyPlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"
UCLASS()
class MYGAME_API AMyPlayerController : public APlayerController
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
virtual void OnPossess(APawn* InPawn) override;
};
// MyPlayerController.cpp
#include "MyPlayerController.h"
#include "GameFramework/Pawn.h"
void AMyPlayerController::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("PlayerController: BeginPlay"));
}
void AMyPlayerController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
UE_LOG(LogTemp, Warning, TEXT("PlayerController: now possessing %s"), *InPawn->GetName());
}
OnPossess fires on the Controller the moment it takes control of a Pawn — this is the "possesses" arrow from section 8's diagram, made concrete. *InPawn->GetName() dereferences the FString that GetName() returns so %s can print it. If InPawn is your AMyCharacter from section 10, placed as the level's default player start, the trace reads:
LogTemp: Warning: PlayerController: BeginPlay
LogTemp: Warning: PlayerController: now possessing MyCharacter_C_0
Section 8 showed the static diagram; here is the same picture as an event trace, so you can see the order things actually happen in when you press Play in the editor. Assume: AMyGameMode from section 9, using AMyCharacter from section 10 and AMyPlayerController from section 11, and every class logs its own BeginPlay.
Put into the Output Log, one plausible ordering looks like this (the exact order between different actors' BeginPlay calls is not something you should hard-code logic around — only the possession-before-BeginPlay-of-the-pawn relationship and "GameMode exists first" are guaranteed):
LogTemp: Warning: PlayerController: now possessing MyCharacter_C_0
LogTemp: Warning: PlayerController: BeginPlay
LogTemp: Warning: HealthActor spawned with 100.000000 HP
The practical takeaway: by the time any actor's BeginPlay runs, the GameMode has already decided the rules and the PlayerController has already been created — so it is always safe, from inside BeginPlay, to ask "what GameMode is running" or "who is the player's controller." It is not safe to assume that from inside a constructor, which is exactly the pitfall flagged back in section 7.
One reference table, gathering everything this chapter touched on:
The theme underneath the table: Unity gives you fewer built-in opinions and expects you to build your own conventions (your own GameManager, your own player script pattern) on every project. Unreal gives you more built-in structure (GameMode, Pawn/Character split, PlayerController) and a heavier, macro-driven way of exposing things to its editor and to Blueprint, in exchange for that structure. Neither is strictly better — they are different trade-offs between "flexible, build it yourself" and "opinionated, plug into the framework" — and knowing both means you can read a job posting for either engine and already recognize the shape of the code before you open the project.
AActor) — the base class for anything that can be placed in a level; has a transform and lifecycle calls built in.UActorComponent) — an object attached to an Actor that adds one capability (mesh, camera, collision, movement)..generated.h file.UCLASS() — marks a class for reflection; requires GENERATED_BODY() as the first line in the class body.UPROPERTY(...) — marks a member variable for editor exposure, Blueprint access, serialization, and garbage-collector tracking.UFUNCTION(...) — marks a member function for Blueprint access or networked remote calls.GENERATED_BODY() — macro that injects UHT's generated boilerplate into a reflected class.TSubclassOf<T> — a type-safe reference to a class (not an instance) that must be T or a subclass of it.BeginPlay() — called once when an actor starts existing in a running world; the closest match to Unity's Start().Tick(float DeltaTime) — called every frame if enabled; the closest match to Unity's Update().EndPlay(Reason) — called once when an actor is removed from a running world; the closest match to Unity's OnDestroy().AGameModeBase) — the rulebook Actor for a level/session; decides which Pawn and PlayerController classes to spawn.APawn) — the base class for anything a Controller can possess and drive.ACharacter) — a Pawn subclass built for walking characters, with a capsule collider and movement component included.APlayerController) — represents a human player's input connection, separate from the Pawn it possesses.CreateDefaultSubobject<T>(...) — the constructor-time call that creates an Actor's default components.UE_LOG — Unreal's logging macro; writes to the editor's Output Log window.ATimedActor code in section 7. Its Tick currently logs "been alive over 5 seconds" on every frame once TimeAlive passes 5.0, instead of just once. Add a member variable and a small change to Tick so the message prints exactly once. State the type and UPROPERTY (or plain member, your choice) you added, and show the changed Tick function.Add a boolean flag that starts false and gets set once the message has fired:
UPROPERTY(VisibleAnywhere, Category = "Timer")
bool bHasLoggedFiveSeconds = false;
void ATimedActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
TimeAlive += DeltaTime;
if (TimeAlive > 5.0f && !bHasLoggedFiveSeconds)
{
UE_LOG(LogTemp, Warning, TEXT("TimedActor: been alive over 5 seconds"));
bHasLoggedFiveSeconds = true;
}
}
The &&!bHasLoggedFiveSeconds check means the body only ever runs the first time TimeAlive crosses 5.0 — after that, bHasLoggedFiveSeconds is true, so the condition is false on every later frame no matter how large TimeAlive gets. This is the same "print once" pattern you would use in Unity with a bool field checked inside Update().
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = false;
AActor* Other = GetWorld()->SpawnActor<AActor>(AActor::StaticClass());
}
Explain what is wrong and rewrite it correctly.The constructor runs even when there is no running game world — for example when the editor builds this Actor's Class Default Object to show it in menus and previews. At that point GetWorld() can return a null pointer (or a world that is not set up for spawning), so calling SpawnActor on it crashes or misbehaves. Section 7's rule applies exactly here: world-dependent code belongs in BeginPlay(), not the constructor.
// MyActor.h (just the two overrides that matter)
protected:
virtual void BeginPlay() override;
// MyActor.cpp
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = false;
// no world-dependent calls here
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
AActor* Other = GetWorld()->SpawnActor<AActor>(AActor::StaticClass());
}
By the time BeginPlay runs, the actor is guaranteed to be part of a live, running world, so GetWorld() is always safe to use there.
GameMode, PlayerController, or Character/Pawn) is responsible for making it happen:
BeginPlay() runs.DefaultPawnClass and spawns the Character.Correct order: (d), (c), (b), (a).
GameMode is responsible; it needs a PlayerController to exist before there is anyone to possess a Pawn.GameMode is responsible again; it reads its own DefaultPawnClass and spawns the Character into the level, typically at a PlayerStart.PlayerController is responsible; once both it and the Character exist, it possesses the Character, and OnPossess fires on the controller.BeginPlay, which fires once it is part of the running world (this can actually happen close together with possession, but logically the Character must exist, i.e. step (c), before it can either be possessed or receive BeginPlay).The pattern worth remembering: GameMode only ever creates things (it is the rulebook, not a physical presence), PlayerController is the one that connects input to a Pawn, and every actor's own BeginPlay is purely about that actor's own setup, independent of why it was spawned.
That is the Actor model and the Gameplay Framework in outline. You now know what an Actor and a Component are and how they compare to Unity's GameObject/Component pair, why UCLASS/UPROPERTY/UFUNCTION exist and what each one buys you, the shape of an Actor's lifecycle, and how GameMode, Pawn/Character, and PlayerController divide up the job of starting a play session. The next chapters build directly on this: more of the Gameplay Framework, then Blueprint/C++ interaction in more depth.