Unreal's gameplay framework took me an embarrassingly long time to actually understand, as opposed to copy from a tutorial and hope. The classes have sensible names, each one is documented, and yet the shape of how they fit together stayed murky for months. The thing that unlocked it wasn't a deeper API dive. It was one sentence: Unreal carefully separates who is playing from what they are currently controlling. Once that split is in your head, the rest of the class diagram stops looking arbitrary.
the cast of characters
There are five classes you trip over constantly, so here they are with the one job each actually has.
- GameMode is the rules of the session. How many players, win conditions, which Pawn to spawn, what happens on respawn. It exists only on the server. If you've ever wondered why your GameMode logic "doesn't run on clients", that's why, and it's by design.
- PlayerController is the player's will. Input arrives here. It's the bridge between a human (or AI) and the world. Crucially it persists even when the thing it's driving dies.
- Pawn (and its fancier child, Character) is the body. The thing in the world that gets possessed, moved, shot at, and destroyed. It is deliberately disposable.
- PlayerState is the player's scoreboard identity. Name, score, team. It survives death and respawn, and it replicates to everyone, because everyone needs to see your score.
- GameState is the same idea for the whole match: the shared, replicated truth that all clients can read.
The penny-drop moment is the Possess relationship. A PlayerController possesses a Pawn. They are not the same object and they don't share a lifetime. When your character dies, the Pawn is destroyed, but the PlayerController carries on, unbothered, ready to possess a freshly spawned Pawn on respawn. That's why your input bindings live on the controller and not the body: bodies are temporary, the will is not.
where things should live
This split also answers the question that haunts every Unreal beginner: "where do I put this variable?" Put it where its lifetime belongs.
// Pawn: this dies with the body, and should.
float CurrentHealth;
// PlayerState: this survives respawn, and replicates to all.
UPROPERTY(Replicated)
int32 Score;
// GameState: shared truth for the whole match.
UPROPERTY(Replicated)
float MatchTimeRemaining;
Health belongs on the Pawn because a dead body has no health and a new body starts fresh. Score belongs on the PlayerState because you keep your score across deaths and everyone needs to see it. Match time belongs on the GameState because it's one value the whole session shares. Get these placements wrong and you'll spend an evening wondering why your score resets every time you respawn, or why your health bar mysteriously shows someone else's number.
None of this is hidden. It's all in the docs. But the docs describe the classes one at a time, and the understanding lives in the relationships between them, which no single page can give you. The map is small once you draw it: GameMode sets the rules on the server, the PlayerController is the persistent will, the Pawn is the disposable body it possesses, and PlayerState plus GameState are the replicated facts everyone agrees on. Hold that, and the framework stops feeling like Epic being arbitrary and starts feeling like Epic being right.