[UE5] Understanding the Game Framework: The Roles of GameMode, GameState, and PlayerState

Created: 2026-02-07Last updated: 2026-07-19

An illustrated guide to how GameMode, GameState, and PlayerState divide the work at the core of UE's Game Framework: how replication works in multiplayer, how a score reaches every screen, and a C++ walkthrough that builds the foundation of a team deathmatch.

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++.

A referee figure reading a rulebook, a scoreboard behind, and a stat card at its feet, illustrating the Game Framework

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)

Sponsored

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.

ClassWhere it existsRoleReplicated
GameModeServer onlyDefines and enforces the game's rulesNo
GameStateServer + all clientsManages the state of the game as a wholeYes
PlayerStateServer + all clientsManages each player's stateYes
The server holds all three classes, while clients only receive GameState and PlayerState. Rules stay on the server, state goes to everyone

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.

ClassCharacteristicsRecommended for
AGameModeBaseSimple and lightweightSingle-player and non-match-based games
AGameModeIncludes a match state machineMatch-based multiplayer such as team deathmatch
AGameMode's match state machine, running from EnteringMap to LeavingMap, with Aborted for abnormal cases

AGameMode has a built-in state machine that works like a schedule for the match. It manages six match states (EnteringMapWaitingToStartInProgressWaitingPostMatchLeavingMap, 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

A comparison of GameState as a notice board everyone looks at, and PlayerState as one stat card per person

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 PlayerArray property
  • 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.

Sponsored

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.

Structural diagram showing three PlayerState cards holding Name, Score and Ping bound into GameState's PlayerArray, each mapping to a different player

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."

A Pawn defeats an enemy, GameMode judges by the rules, PlayerState updates the score, and replication reflects it in everyone's UI
  1. The player's Pawn kills an enemy
  2. That report reaches the server, and the GameMode on the server decides, based on the rules, to award points
  3. GameMode gets that player's PlayerState and updates the score
  4. Because the score variable is marked Replicated, the change on the server propagates automatically to all clients
  5. 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.

Sponsored

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 moment from a team deathmatch. The scoreboard reads 2-1, and a +1 lands on the killer's side the instant the kill happens. The number moves on everyone's screen at once

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
  • Replicated and DOREPLIFETIME always come as a pair: if you add UPROPERTY(Replicated), register it in GetLifetimeReplicatedProps too. One without the other means no synchronization (DOREPLIFETIME_CONDITION also 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
PlayerController is the one controlling, Pawn is the body being controlled, PlayerState is the stat card carried along. The body can vanish, the stat card stays
  • RepNotify is the standard way to update UI: to detect a replicated variable arriving on the client, combine UPROPERTY(ReplicatedUsing=OnRep_KillCount) with UFUNCTION() void OnRep_KillCount(). The OnRep_ 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 GameInstanceChoosing 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.

Further Reading