Skip to main content

Gameplay framework overview

Every gameplay tutorial throws GameMode, PlayerController, Pawn, and PlayerState at you in the same paragraph, and it's easy to come away thinking they're interchangeable "player stuff" classes. They're not — each one has a specific owner, a specific lifetime, and a specific replication story, and mixing those up is how you end up with state that works in single-player and silently breaks the moment a second client connects. This doc is the map; the rest of this folder is the territory.

Why this matters

Unreal splits "a player" into four separate objects on purpose: something that enforces the rules (GameMode), something that broadcasts shared state (GameState), something that represents a connection and its input (PlayerController), something that persists player identity across pawn death (PlayerState), and something that's physically in the world (Pawn/Character). If you don't know which object a piece of data belongs on, you'll put it on the wrong one — and the wrong one is usually the one that doesn't replicate to the client that needs it, or the one that gets destroyed and recreated when you didn't expect it to be.

Mental model: who spawns and owns whom

Read the arrows as ownership and lifetime, not just association. AGameModeBase is the root — it exists only on the server (or the host of a listen server) and spawns everything else. AGameStateBase and APlayerState are the two objects Epic's replication system treats as "always relevant," which is why game-wide and player-wide data belongs there rather than on GameMode. A PlayerController possesses a Pawn, but neither owns the other outright: a controller can UnPossess() one pawn and Possess() another (spectating, vehicle swaps), and a pawn can go temporarily unpossessed without being destroyed.

The mechanics

The five core classes

ClassLives whereReplicates toSurvives pawn death?Covered in
AGameModeBaseServer onlyNeverN/A (not per-player)Game mode and game state
AGameStateBaseServer + all clientsAll clientsN/A (per-match)Game mode and game state
APlayerControllerServer + owning clientOwning client only (mostly)YesPlayer controller and player state
APlayerStateServer + all clientsAll clientsYesPlayer controller and player state
APawn / ACharacterServer + relevant clientsClients that can see itNo — respawnedPawn and character

Where this sits in the bigger architecture

This folder assumes you've already read Engine architecture map, which places the gameplay framework as a layer above the engine's rendering/physics/animation/networking subsystems and below your own game module. Everything below assumes that layering — a Pawn is a coordinator that owns components which each talk to one of those subsystems; it does not talk to physics or rendering directly.

Underneath all of it: Actor and ActorComponent

Every class in the table above except the components is an AActor subclass, which means all of them go through the same spawn/initialize/tick/destroy sequence described in Actor lifecycle. The pieces a Pawn is built from — actor components and scene components — are not AActors themselves; they're attached to one. And all of this runs inside a UWorld loaded from one or more ULevels, covered in World and levels, with a UGameInstance sitting above the world for anything that needs to survive a level change — see Game instance.

Server authority in one sentence

Only the server (or the host acting as server in a listen server setup) runs AGameModeBase logic and is authoritative over gameplay decisions; clients receive a filtered, replicated view of the world through AGameStateBase, APlayerState, and whichever actors are relevant to them. Code that reads HasAuthority() before mutating gameplay state is checking exactly this: "am I the server, or a client looking at a replicated copy?"

A minimal look at where authority is checked
void AMyPickupActor::OnOverlapBegin(AActor* OtherActor)
{
if (!HasAuthority())
{
return; // clients don't decide pickup outcomes, they just see the replicated result
}

// server-only mutation of gameplay state happens here
}

Gotchas

Don't assume every player has a PlayerController on every machine

A APlayerController only fully exists (with valid input, camera, and replicated properties) on the server and on the one client it belongs to. On every other client, a remote player is represented by their Pawn and PlayerState, not a usable PlayerController. Code that does Cast<APlayerController>(SomeActor)->DoSomething() on a machine that isn't the owning client is a common source of silent no-ops or crashes once you test with more than one client.

"The player" is not one object

New Unreal developers reach for GameMode to store per-player score because it's the first class they learn. GameMode is server-only and un-replicated — put per-player, client-visible data on PlayerState instead. See Game mode and game state for the concrete failure mode.

See also