RealDocs

The Gameplay Framework

Every Unreal game session gets the same standard cast of objects: a GameMode, a GameState, a PlayerController and PlayerState per player, a GameInstance, and a World to hold them. You never spawn these yourself — you subclass them. This guide explains what each one is for, who creates what, and the single idea that makes the whole design click: Unreal was built for multiplayer first, and every one of these classes exists on a specific side of the network for a reason.

1. The Big Picture

In a networked game there is one server that owns the true state of the match, and any number of clients that each see a partial, replicated copy of it. The framework classes are split across that boundary deliberately: some exist only on the server, some are mirrored to every client, and some are private to one machine. The diagram shows the server and one client side by side — who creates what, and what crosses the wire.

SERVER — THE AUTHORITY the network ONE CLIENT — A PLAYER'S MACHINE UGameInstance — 1 per process, survives level changes UGameInstance — this machine's own UWorld — the loaded map UWorld — this machine's copy of the map spawns Login() spawns owns possesses replicates replicates replicates — movement & state Server RPC — the only road back to the server AGameModeBase AGameStateBase shared match facts APlayerController APlayerState public per-player facts APawn the body — spawned by GameMode owns the one true copy of everything AGameStateBase score, timer, phase — read it here no GameMode on clients GetGameMode() → null APlayerState one per player — everyone's APlayerController only yours — input, camera, UI APawn proxy — moves via replication …plus a replicated APlayerState and pawn proxy for every other player

One server, one of N clients. In single-player the same picture holds — your machine simply plays both columns at once, which is why everything "just works" until you go multiplayer.

Server only — clients can never see it
Replicated — server writes, every client gets a copy
Local — each machine has its own, not replicated to others
replication (one-way, server → client)
Server RPC (client → server request)

You don't create any of these objects yourself. The engine spawns them at level load and player join, using the subclasses you registered — in Project Settings → Maps & Modes, per-map World Settings, or on the GameMode itself (§4). Your job is to subclass and configure, never to SpawnActor a GameMode.

2. Designed for Multiplayer From Day One

Unreal's gameplay framework descends directly from Unreal Tournament — a competitive online shooter. Its networking model was baked in from the start, and it shapes the design of every class on this page, even if your game is single-player. Three rules explain it:

Rule 1 — the server is the authority

One machine — the server — owns the true state of the game. Clients are, in effect, sophisticated viewers: they render the replicated state they receive and send input. A client never decides "I hit him for 50 damage"; it asks, and the server decides. This is what stops cheating and keeps every player's world consistent. Single-player and play-in-editor are just a listen server with one local player — the same machinery runs, with the network latency set to zero. That's why the framework looks identical either way, and why netcode bugs hide until you actually test with two clients.

Rule 2 — replication flows one way: server → clients

Actors and properties marked for replication are copied from the server to clients, automatically, whenever they change. Clients cannot push a replicated value back — if a client writes to a replicated variable, only its local copy changes, and the next server update overwrites it.

Rule 3 — RPCs are the only conversation across the wire

Besides replicated state, the only way code on one machine can make something happen on another is a remote procedure call — a UFUNCTION tagged with where it should run:

// Replicated state: server writes, every client receives. One-way.
UPROPERTY(Replicated)               // or ReplicatedUsing=OnRep_Health for a change callback
float Health;

// RPCs — the three directions:
UFUNCTION(Server, Reliable)         // client asks the server to do something
void ServerFireWeapon();

UFUNCTION(Client, Reliable)         // server tells one owning client something private
void ClientShowHitMarker();

UFUNCTION(NetMulticast, Unreliable) // server broadcasts a cosmetic event to everyone
void MulticastPlayExplosion();

Now the framework's class splits stop looking arbitrary and start looking inevitable:

  • GameMode is server-only because it is the authority: it holds the rules, spawn logic, and win conditions — exactly the things clients must never be able to read or tamper with.
  • GameState and PlayerState exist purely as the replicated, readable mirrors of the facts clients do need — the scoreboard, the timer, everyone's names. They are the GameMode's public noticeboard.
  • PlayerController is each player's private channel: it exists on the owning client and the server, and nowhere else. It's where input becomes Server RPCs, and where the server sends private replies.
Test with two clients early. In the editor, set Play → Number of Players to 2+ and Net Mode to "Play as Client". Single-player runs everything as the server, which silently hides every authority, replication, and null-GameMode bug on this page.

3. The Cast at a Glance

The reference table. Each class gets a deeper section below; the "Exists on" column is the one to memorise.

Class How many Exists on Job
AGameModeBase 1 per match Server only The rules: who can join, what pawn to spawn, when the match ends
AGameStateBase 1 per match Everywhere (replicated) Shared match state every client can see: score, timer, phase
APlayerController 1 per player Owning client + server Input, camera, and UI for one human
APlayerState 1 per player Everywhere (replicated) Per-player facts everyone can see: name, score, ping
APawn 1 per player (usually) Everywhere (replicated) The body in the world — disposable; respawns spawn a new one
UGameInstance 1 per game process Each machine, locally Survives level changes — settings, session data, persistent systems
UWorld 1 per loaded map Each machine The container everything above (except GameInstance) lives in
GameMode only exists on the server. GetGameMode() returns null on every client in a networked game. Anything clients need to read belongs in GameState or PlayerState — that's the entire reason those classes exist. (In single-player everything runs "on the server", which is why this trap only bites once you go multiplayer.)

The order these objects get created in — and where in that sequence your players spawn — is charted in the Execution Order guide §11.

4. GameMode & GameState — Rules vs Scoreboard

AGameModeBase is the referee. It runs only on the server and answers the authority-only questions: may this player join (PreLogin)? What pawn class do they get, and where does it spawn (RestartPlayer, spawn points)? Has someone won? It's also the registry for the rest of the cast — one GameMode subclass picks the whole team:

AMyGameMode::AMyGameMode()
{
    DefaultPawnClass      = AMyCharacter::StaticClass();
    PlayerControllerClass = AMyPlayerController::StaticClass();
    GameStateClass        = AMyGameState::StaticClass();
    PlayerStateClass      = AMyPlayerState::StaticClass();
    HUDClass              = AMyHUD::StaticClass();
}

Which GameMode runs is set in Project Settings → Maps & Modes (the default) or overridden per-map in World Settings — which is how your menu level runs a different GameMode than your gameplay levels.

AGameStateBase is the scoreboard: the GameMode's decisions, published. It replicates to every client and carries the match facts everyone renders — elapsed time, match phase, team scores — plus PlayerArray, the replicated list of every player's PlayerState (the only way a client can enumerate who's in the match). It also provides server-synchronised time via GetServerWorldTimeSeconds(). The division of labour: GameMode decides, GameState displays.

Base or not-Base? AGameModeBase/AGameStateBase are the modern minimal pair. Their subclasses AGameMode/AGameState add a classic match-state machine (WaitingToStart → InProgress → WaitingPostMatch, ready checks, spectating) inherited from shooter games. Start from Base; reach for the non-Base pair only if you want that match flow.

5. PlayerController & PlayerState — One Human, Two Objects

Each human connected to the game is represented by a pair of actors that both outlive any particular pawn: a private one and a public one.

APlayerController is the player's will — the persistent, private half:

  • Turns input into actions, owns the camera (PlayerCameraManager), and is the usual owner of UMG widgets — HUD and menus are per-player concerns.
  • Exists only on the owning client and the server. Other players' machines have no copy of your controller at all — you cannot iterate "all PlayerControllers" on a client and expect more than your own.
  • Is the client's voice: gameplay requests travel as Server RPCs on the PlayerController (or on the pawn it owns), because it's the actor the connection owns.
  • Survives pawn death. When your character dies, the controller stays connected and simply possesses the next body (§6).

APlayerState is the player's public record — the replicated half everyone can see:

  • Name (GetPlayerName()), score, ping, team — the scoreboard row for one player.
  • Replicated to every client, and listed for everyone in GameState->PlayerArray.
  • The right home for any per-player fact that must survive respawns and be visible to other players: kills, deaths, character selection, ready status.

One more split-screen wrinkle: on a single machine, each local human is a ULocalPlayer (owned by the GameInstance) that pairs up with one PlayerController in the current world. You'll mostly meet it as the thing CreateWidget and Enhanced Input subsystems hang off.

6. Pawns and Possession

A APawn is a body: an actor that can be possessed by a controller and driven around the world. The controller–pawn split is the framework's cleanest idea — the mind is persistent, the body is disposable:

// Respawn, on the server: new body, same mind.
APawn* NewPawn = GetWorld()->SpawnActor<APawn>(PawnClass, SpawnTransform);
Controller->Possess(NewPawn);   // old pawn (if any) is unpossessed first
  • Possession is a server decision — Possess() only works with authority. The GameMode's RestartPlayer() does exactly the snippet above for you at spawn time.
  • NPCs use the same machinery with an AAIController possessing the pawn instead of a player.
  • The pawn replicates to everyone (people need to see your character), but only the owning client sends input for it. Whether to derive from Pawn or ACharacter — which adds walking, jumping and networked movement — is covered in Core Classes §6.

The exact callback sequence when possession happens (and which side each callback runs on) is charted in the Execution Order guide §12.

7. GameInstance & World — The Containers

UGameInstance is the outermost box: one per running process, created before any level and destroyed only when the game quits. Everything else on this page is torn down and rebuilt when you change levels — the GameInstance is what survives the trip. That makes it the home for settings, the party you formed in the menu, save-game handles, and long-lived services. For those services, prefer subclassing UGameInstanceSubsystem over stuffing the GameInstance itself — subsystems are auto-created per GameInstance and keep systems decoupled. Note it is local: the server and every client each have their own, and nothing in it replicates.

UWorld is the loaded map: the container all the framework actors live in (GameMode, controllers, states, pawns — everything except GameInstance). One world is current at a time in a running game, but more exist than you'd think: the editor viewport is a world, and each play-in-editor client is another. That's why engine code is scrupulous about GetWorld() instead of any global — "which world am I in?" has more than one answer, especially during PIE. Level travel replaces the current world wholesale; anything you need on the far side goes in the GameInstance.

8. Where Should This Variable Live?

The practical payoff of the whole framework. Classify the fact, and the class picks itself:

The fact is… It lives on Why
A rule, spawn decision, or win condition GameMode Authority-only; clients must not read or tamper with it
Match state every client renders (timer, phase, team scores) GameState One copy, replicated to all
A per-player fact everyone sees (name, kills, team, ready) PlayerState Replicated to all, and it survives respawns
Per-player private state (input mode, camera, open menus) PlayerController Only the owning player and the server need it
Physical state of the body (health, ammo in the equipped gun) Pawn / components Dies with the body — for these facts, that's correct
Anything that must survive level travel (settings, party, save data) GameInstance The only thing here that outlives the world
The classic mistake is picking by convenience. Score stored on your Character vanishes at the first respawn; score stored on the GameMode is invisible to every client's scoreboard. Both compile, both work in single-player, both are wrong. Score is a per-player fact everyone sees — it goes on APlayerState.

9. Common Gotchas

"GetGameMode() returns null"

You're on a client — this is by design, not a bug (§2). Whatever you wanted from the GameMode, the replicated version of it belongs in GameState or PlayerState. If it isn't there yet, that's the fix — don't try to reach the GameMode from client code.

"GameState / PlayerState is null in BeginPlay"

On clients, framework actors arrive over the network and there's no guarantee they exist before your actor's BeginPlay runs. Null-check and defer (a short timer, or better: react in an OnRep_ callback when the data actually arrives) instead of assuming order. The Execution Order guide covers what is and isn't guaranteed.

"It worked fine until we tested multiplayer"

Single-player runs everything as the server, so authority checks, replication, and RPC direction are never exercised. Habitually run PIE with 2+ players and "Play as Client", and use network emulation (Project Settings, or Net PktLag) to surface timing assumptions before your players do.

"I stored the score on my Character"

The pawn is disposable — respawn spawns a new pawn and your data dies with the old one. The pawn is not the player; the PlayerState is. Persistent per-player facts go on APlayerState (visible to all) or the PlayerController (private). See the table in §8.

"I set a replicated variable on the client and nobody saw it"

Replication is strictly one-way, server → client. A client writing a replicated property changes only its local copy, which the server's next update silently overwrites. To change shared state from a client, send a Server RPC and let the server make the change — that's the green arrow in the §1 diagram.