GameMode, GameState, PlayerState. The names are similar and the usual explanations of their roles are abstract, so these three siblings confuse just about everyone learning UE.
The trick to telling them apart is to remember them not by feature but by where they exist and who they're shared with. With that one axis, you'll be fine in both single-player and multiplayer. This article covers how the three classes divide the work, how information flows when a player scores, and how to build the foundation of a team deathmatch in C++.
What You'll Learn
- Tell the three classes apart by where they exist and whether they replicate
- GameMode = the rulebook, GameState = the scoreboard, PlayerState = a personal stat card
- The information flow that carries a score to everyone's screen
- Hands-on: building the foundation of a team deathmatch in C++ (
Replicated+DOREPLIFETIME)
- The Three Classes Compared: Where They Live and Who Sees Them
- GameMode: The Rulebook
- GameState: The Scoreboard
- PlayerState: A Personal Stat Card
- Information Flow in Practice: How a Score Reaches Everyone
- Hands-On: Building a Team Deathmatch Foundation in C++
- Bonus: Good to Know for Later
- Summary
- Further Reading
The Three Classes Compared: Where They Live and Who Sees Them
Let's start with the big picture. This table is the map for the rest of the article.
| Class | Where it exists | Role | Replicated |
|---|---|---|---|
| GameMode | Server only | Defines and enforces the game's rules | No |
| GameState | Server + all clients | Manages the state of the game as a whole | Yes |
| PlayerState | Server + all clients | Manages each player's state | Yes |

The most important point is that GameMode exists only on the server. That's by design, so clients can't tamper with rules like victory conditions and spawn locations. Anything a client needs to know is synchronized (replicated) via GameState and PlayerState. In single-player, your own PC also acts as the server, so everything works without you thinking about this structure. The underlying mechanism, however, is exactly the same.
GameMode: The Rulebook
GameMode is the class that defines the rules themselves. Thinking of it as the referee, or the rulebook, makes it easier to picture.
- Handling players joining and leaving (
PostLogin/Logout) - Where, when, and under what conditions Pawns spawn
- Match start and end conditions, and mode-specific rules (deathmatch, CTF, and so on)
You can specify a GameMode in Project Settings' Maps & Modes, in per-level World Settings, or via URL arguments, so each level can run under different rules.
AGameModeBase vs AGameMode
There are two base classes, and new projects default to AGameModeBase.
| Class | Characteristics | Recommended for |
|---|---|---|
AGameModeBase | Simple and lightweight | Single-player and non-match-based games |
AGameMode | Includes a match state machine | Match-based multiplayer such as team deathmatch |

AGameMode has a built-in state machine that works like a schedule for the match. It manages six match states (EnteringMap → WaitingToStart → InProgress → WaitingPostMatch → LeavingMap, plus Aborted for abnormal cases). That saves you from writing flow control such as "wait until everyone has joined" or "show a results screen after the match" yourself.
GameState: The Scoreboard

GameState manages the current state of the game as a whole and synchronizes it to all clients. If GameMode is the rulebook, GameState is the scoreboard and the timer hanging in the venue.
- Elapsed game time:
GetServerWorldTimeSeconds()(accurate, server-synchronized time) - The list of connected players: the
PlayerArrayproperty - Team scores and any other game-wide information not tied to a specific player
Sort it with one question: is this one person's, or everyone's? Only the everyone's belongs to GameState. Individual scores and names go to PlayerState, up next.
PlayerState: A Personal Stat Card
PlayerState is the class that manages an individual player's state. Because it's replicated to all clients, you can use it directly for things like showing other players' scores in a ranking UI.
- Player name:
GetPlayerName() - Score:
GetScore()/SetScore() - Ping:
GetPingInMilliseconds() - Player ID and bot check:
GetPlayerId()/IsABot()
GameState's PlayerArray holds PlayerState instances. In other words, the scoreboard (GameState) has everyone's stat cards (PlayerState) bound into it.

Information Flow in Practice: How a Score Reaches Everyone
Let's follow how the three classes work together in the scenario "the player kills an enemy and earns points."

- The player's Pawn kills an enemy
- That report reaches the server, and the GameMode on the server decides, based on the rules, to award points
- GameMode gets that player's PlayerState and updates the score
- Because the score variable is marked
Replicated, the change on the server propagates automatically to all clients - Each client's UI picks up the PlayerState change and updates the display
The server's GameMode enforces the rules, and replication of GameState / PlayerState shares the results. That's the basic shape of multiplayer design in UE.
Hands-On: Building a Team Deathmatch Foundation in C++
With the concepts connected, let's write some code. The subject is a team deathmatch, but the shape of this foundation is the same for lap rankings in a racing game or shared progress in a co-op game. We'll turn the rule "team-wide numbers go in GameState, personal numbers go in PlayerState" into a C++ skeleton.

A Custom GameMode: Tying the Three Classes Together
Here we inherit from AGameModeBase as the minimal foundation. For serious use that needs progress management (match states) such as a start-of-match wait or a results screen, swap the parent to AGameMode.
// MyGameMode.h
#pragma once
#include "GameFramework/GameModeBase.h"
#include "MyGameMode.generated.h"
UCLASS()
class AMyGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AMyGameMode();
// Entry point that receives the "kill" report and enforces the rules (exposed so Blueprint can call it)
UFUNCTION(BlueprintCallable, Category = "Score")
void OnPlayerScored(APlayerController* Scorer, int32 Points);
};
// MyGameMode.cpp
#include "MyGameMode.h"
#include "MyGameState.h"
#include "MyPlayerState.h"
AMyGameMode::AMyGameMode()
{
// Specify the GameState/PlayerState classes here and UE creates them for you
GameStateClass = AMyGameState::StaticClass();
PlayerStateClass = AMyPlayerState::StaticClass();
}
void AMyGameMode::OnPlayerScored(APlayerController* Scorer, int32 Points)
{
// Pull out that player's stat card and add points (rules are enforced here, on the server)
if (AMyPlayerState* PS = Scorer->GetPlayerState<AMyPlayerState>())
{
PS->AddScore(Points);
}
}
A Custom GameState: Replicating Team Scores
// MyGameState.h
#pragma once
#include "GameFramework/GameStateBase.h"
#include "MyGameState.generated.h"
UCLASS()
class AMyGameState : public AGameStateBase
{
GENERATED_BODY()
public:
// Variables marked Replicated are synchronized to all clients automatically
UPROPERTY(Replicated, BlueprintReadOnly, Category = "Score")
int32 TeamAScore = 0;
UPROPERTY(Replicated, BlueprintReadOnly, Category = "Score")
int32 TeamBScore = 0;
virtual void GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
// MyGameState.cpp
#include "MyGameState.h"
#include "Net/UnrealNetwork.h" // Required for DOREPLIFETIME
void AMyGameState::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Register the variables you want replicated here (forget this and nothing syncs)
DOREPLIFETIME(AMyGameState, TeamAScore);
DOREPLIFETIME(AMyGameState, TeamBScore);
}
A Custom PlayerState: Replicating Personal Stats
// MyPlayerState.h
#pragma once
#include "GameFramework/PlayerState.h"
#include "MyPlayerState.generated.h"
UCLASS()
class AMyPlayerState : public APlayerState
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Score")
void AddScore(int32 Points);
UPROPERTY(Replicated, BlueprintReadOnly, Category = "Score")
int32 KillCount = 0;
virtual void GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
// MyPlayerState.cpp
#include "MyPlayerState.h"
#include "Net/UnrealNetwork.h"
void AMyPlayerState::AddScore(int32 Points)
{
SetScore(GetScore() + Points); // Update the built-in score
KillCount++; // Add to our custom stat as well
}
void AMyPlayerState::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyPlayerState, KillCount);
}
Once it's assembled, let's see it run. In the options next to the editor's Play button, set Number of Players to 2 and Net Mode to Play As Client, then hit Play to test with a server window and a client window.
Wire up one trigger to move the score. For testing, add the following to a PlayerController or the Level Blueprint.
Key Input K (Pressed)
→ Get Game Mode → Cast To AMyGameMode
→ On Player Scored (Scorer: Get Player Controller, Points: 100)
Get Game Mode returns a value only on the server, so this wiring succeeds only when you press it in the server window (in the client window the Cast fails and nothing happens). To watch the numbers, add a Print String that shows the PlayerState's Score and KillCount on screen.
If it works, the instant you press K in the server window, both windows show Score change from 0 to 100 and KillCount from 0 to 1. You didn't write a single line of synchronization code, yet even the client window's numbers move. That's replication. If it doesn't synchronize, suspect a missing DOREPLIFETIME registration, or that you're making the change on the client (replication is one-way, server to client). The replication mechanism itself is explored in Multiplayer replication basics.
Two things to take away.
- The sorting question is "whose number is this?": team score belongs to everyone → GameState; kill count belongs to one player → PlayerState. Decide that split first and the rest is boilerplate
ReplicatedandDOREPLIFETIMEalways come as a pair: if you addUPROPERTY(Replicated), register it inGetLifetimeReplicatedPropstoo. One without the other means no synchronization (DOREPLIFETIME_CONDITIONalso lets you replicate conditionally, such as "owner only," which is useful for saving bandwidth)
Bonus: Good to Know for Later
- PlayerController and Pawn are part of the family too: the PlayerController (the one doing the controlling) drives the Pawn (the body) and carries the PlayerState (the stat card) around. When a Pawn is destroyed and rebuilt on respawn, the PlayerState survives, so the score doesn't disappear. Implementing that recovery itself — rebuilding or repositioning the Pawn — is covered in Checkpoints and respawning

- RepNotify is the standard way to update UI: to detect a replicated variable arriving on the client, combine
UPROPERTY(ReplicatedUsing=OnRep_KillCount)withUFUNCTION() void OnRep_KillCount(). TheOnRep_function is called the instant the value arrives, so put your score display update there - Simplifying is fine for single-player: if you have no plans for multiplayer at all, concentrating logic in GameMode is a valid simplified pattern. But if multiplayer is even remotely in view, splitting across the three classes from the start dramatically reduces the cost of migrating
- Data that crosses levels belongs elsewhere: the GameMode family is rebuilt whenever the level changes. The place for data that must survive a level change is GameInstance → Choosing the Right Subsystem
Summary
- GameMode = the rulebook. Exists only on the server and is not replicated
- GameState = the scoreboard. Synchronizes the state of the whole game to all clients
- PlayerState = a personal stat card. Synchronizes per-player information to all clients
- The sorting axes are "where it lives and who sees it" and "whose number is this?"
- Replication is registered as a pair:
UPROPERTY(Replicated)+DOREPLIFETIME
The Actor and GameMode fundamentals these three classes build on are covered in UE5 Core Concepts, and C++ syntax itself in Your First Step from Blueprint to C++.
In the game you're designing right now, what counts as a "whole-game number" and what counts as a "personal number"? Write them out on paper and sort them, and your design is already half done.