GameState and PlayerState in practice¶
GameState is public match state. PlayerState is public per-player state. If a client needs to see it and it should survive pawn replacement, it probably does not belong only in GameMode, PlayerController, a widget, or the current pawn.
The one-minute version¶
- GameMode decides rules on the server. GameState is the replicated public state about the current match/map that clients can read.
- PlayerState exists for every connected player on the server and clients. It is the normal home for public per-player data such as score, team, display name, ready state, and a replicated loadout summary.
- A pawn or character can die, be destroyed, and be replaced. PlayerState continues for that connected player, so it is the safer home for state that should survive respawn.
- A PlayerController is private to the server and owning client. Do not put scoreboard data there if other clients need it.
- A widget is local presentation. It should read GameState/PlayerState data, not own the multiplayer truth.
- Use RepNotify on GameState/PlayerState variables when local UI, audio, or materials should refresh as replicated state arrives.
The framework split¶
The beginner multiplayer split is:
GameMode:
server-only rules and decisions
GameState:
replicated game-wide state clients should observe
PlayerState:
replicated public state for one connected player
PlayerController:
private server <-> owning-client control path
Pawn / Character:
current body in the world; can be replaced
Widget:
local presentation of the state above
If you pick the wrong home, the bug usually sounds like one of these:
"The host sees the timer, but clients see None."
-> It was stored in GameMode, which remote clients do not have.
"My score resets when I die."
-> It was stored only on the pawn that death destroyed.
"Other players cannot see my team color."
-> It was stored in PlayerController or local UI instead of PlayerState.
GameState: public match state¶
Use GameState for state about the current game session/map that all clients or spectators should know:
| State | Why GameState fits |
|---|---|
| Match phase: Waiting, Countdown, InProgress, Finished | It is game-wide and every client needs to present it. |
| Match timer / round time remaining | Everyone reads the same public clock or countdown. |
| Team score | It belongs to the match, not one pawn. |
| Objective state: generators repaired, payload progress, wave number | The state describes the shared match. |
| Public winner/loser result | Everyone needs the same final state. |
| Array/list of public match objectives | Clients need a replicated source for UI and world indicators. |
The server should set these values. Clients receive them through replication and update local presentation.
Server GameMode:
decides countdown ended
-> GameState.MatchPhase = InProgress
-> GameState.RemainingTime = 300
Clients:
GameState values replicate
-> OnRep_MatchPhase / OnRep_RemainingTime
-> local HUD updates
GameMode can decide. GameState can announce.
PlayerState: public per-player state¶
Use PlayerState for state about one player that other clients should be able to see:
| State | Why PlayerState fits |
|---|---|
| Score / kills / deaths / assists | Public per-player scoreboard state. |
| Team / squad / color | Other clients need to classify that player. |
| Ready state | Lobby and match start UI need every player's status. |
| Display name | Every client can show nameplates/scoreboard rows. |
| Ping or connection-facing public status | Already aligned with player identity. |
| Loadout summary / selected character class | Public identity or gameplay-visible choice. |
| Alive/spectating flag that should outlive the pawn body | The body can be destroyed; the player remains connected. |
PlayerState is not private player settings and not local-only UI state. If only the owning player should know it, keep it in PlayerController, local UI, GameInstance, or SaveGame as appropriate.
Why pawns are not enough¶
The pawn is the body in the world:
- movement;
- collision;
- mesh and animation;
- current body health;
- weapon/component references attached to the body;
- abilities whose lifetime is tied to that body.
That body can be destroyed during death, unpossessed during spectating, or replaced during respawn. State stored only there dies with it unless you copy it somewhere else.
Use this split:
| Data | Better home |
|---|---|
| Current body health | Pawn/Character or HealthComponent. |
| Score that survives death | PlayerState. |
| Team visible to every player | PlayerState. |
| Current pawn movement mode | Pawn/Character. |
| Whether the connected player is ready | PlayerState. |
| Private selected graphics setting | GameInstance / SaveGame, not replicated. |
| Equipped weapon actor reference for this body | Pawn/Character or component. |
| Public loadout ID shown on scoreboard | PlayerState. |
If data is about the body, start at the pawn. If data is about the player identity in this match, start at PlayerState.
A practical first setup¶
GameMode sets the default classes¶
In your GameMode Blueprint defaults:
GameState Class = BP_MatchGameState
PlayerState Class = BP_MatchPlayerState
Default Pawn Class = BP_PlayerCharacter
Player Controller Class = BP_PlayerController
GameMode exists on the server and chooses these classes. The actual replicated state should live in the GameState/PlayerState classes where clients can read it.
PlayerState variables¶
In BP_MatchPlayerState:
Score: integer, RepNotify
TeamId: enum or gameplay tag, RepNotify
bReady: boolean, RepNotify
LoadoutId: name/gameplay tag, RepNotify
OnRep functions call local presentation helpers:
OnRep_Score:
Broadcast local PlayerStateUpdated dispatcher
scoreboard widget refreshes this row
OnRep_TeamId:
nameplate/outline/team color refreshes locally
GameState variables¶
In BP_MatchGameState:
MatchPhase: enum, RepNotify
TeamAScore: integer, RepNotify
TeamBScore: integer, RepNotify
ObjectiveState: struct, RepNotify
If several values must update together, prefer one struct:
FMatchPublicState
MatchPhase
RemainingTime
TeamAScore
TeamBScore
That avoids racing separate OnRep functions that each assume the other value already arrived.
How clients find PlayerState¶
Common Blueprint paths:
Local player:
Get Player Controller
-> Get Player State
-> Cast to BP_MatchPlayerState
Current pawn:
Get Player State
-> Cast to BP_MatchPlayerState
Scoreboard:
Get Game State
-> PlayerArray
-> ForEach PlayerState
-> Cast each to BP_MatchPlayerState
Indexed helper:
Get Player State(Player State Index)
Get Player State(index) reads from the GameState's PlayerArray after
initial replication. It is not the same thing as Get Player Controller(index).
PlayerState is the public replicated player record; PlayerController is not
present for every other player on every client.
How a ready button should work¶
Owning client's lobby widget:
Ready button clicked
-> widget calls PlayerController/owned pawn request
-> ServerSetReady(true)
ServerSetReady:
validate the player may ready up
-> Get PlayerState
-> bReady = true
All clients:
bReady replicates on that player's PlayerState
-> OnRep_Ready
-> local lobby row refreshes
Server GameMode:
checks all PlayerStates
-> if all ready, sets GameState.MatchPhase = Countdown
The widget asks. The server decides. PlayerState shows each player's public ready value. GameState shows the shared phase.
How score should work¶
Server confirms pickup/kill/objective:
ScoringPlayerState.Score += Points
Scoring player's client:
Score replicates
-> local HUD/scoreboard row updates
Other clients:
same PlayerState Score replicates
-> their scoreboard rows update
Do not store the only score in a widget; other machines cannot read your widget. Do not store the only score in the pawn if death destroys the pawn. Do not store public score in PlayerController if other clients need it.
When it fails, and what failure looks like¶
- Reading GameMode on a client returns nothing useful. Remote clients do not have the server's authoritative GameMode instance.
- PlayerState has not arrived yet. Early BeginPlay or immediate widget
construction can run before all replicated state is ready. Bind refresh
events and handle
Nonetemporarily. - You cast the wrong class. The active GameMode may still be using the default PlayerState/GameState class.
- You changed the value on a client. The local copy changes briefly or only locally, then the server's value wins.
- You kept pawn-only state through respawn by accident. Respawn creates a new body; copy persistent player state through PlayerState or another deliberate owner.
- You used PlayerController for public data. Other clients usually do not have that controller.
- You used one array index as identity forever. Player order can change as players join/leave. Use stable IDs, PlayerState references, or player names as appropriate for your feature.
Print the active GameMode class on the server, the GameState class on clients, the PlayerState class for each row, and whether the value was changed on the server. Most bugs are wrong class, wrong machine, or too early.
Lookalikes - which one do I want?¶
| Object | Use when | Not for |
|---|---|---|
| GameMode | Server-only rules, login/start/restart decisions, choosing default classes. | Client-readable timers, score, or objective UI. |
| GameState | Public match/session state everyone should know. | Per-player public stats that belong to one player. |
| PlayerState | Public per-player state that survives pawn replacement. | Private local UI/settings or body-specific health. |
| PlayerController | Local input, private UI, owner-only client messages. | Scoreboard data every other client needs. |
| Pawn / Character | Current body movement, collision, health, abilities, animation. | Player identity/state that should survive death. |
| GameInstance | Local per-process data across level travel. | Replicated match state. |
| Widget | Local presentation and input controls. | Multiplayer authority or replicated truth. |
The quick path is to put "global" variables in GameMode and "player" variables on the pawn because those are easy to find. The concrete cost is that remote clients cannot read GameMode and respawn can destroy the pawn. GameState and PlayerState exist to avoid exactly those two bugs.
Going deeper¶
- Multiplayer roles - the object-existence table this page makes practical.
- Replication basics - how actor variables reach clients.
- RepNotify variables and local presentation - UI/audio/VFX refresh from GameState and PlayerState values.
- GameMode & friends, single-player edition - the non-networked framework split before multiplayer changes the audience.
- Player Start, Restart Player, and checkpoints - pawn replacement and why PlayerState is useful across respawn.
- Get Player Character / Pawn / Controller - why player-slot getters and PlayerController are not "get every player."
- Official docs: Game Mode and Game State, Get Player State, Multiplayer in Blueprints, and Networking Overview.
- Engine source (requires engine access - we do not reproduce it here):
framework declarations live under
Engine/Source/Runtime/Engine/Classes/GameFramework/; useful starting points includeGameModeBase.h,GameStateBase.h,PlayerState.h,PlayerController.h, andPawn.h. For spawn/login state flow, inspectAGameModeBase::InitGameState,AGameModeBase::PostLogin, andAGameModeBase::RestartPlayerinEngine/Source/Runtime/Engine/Private/GameModeBase.cpp.