【Unreal Engine】Understanding Game Framework: Roles of GameMode, GameState, and PlayerState

Created: 2026-02-07

Compare the three core classes of UE's Game Framework — GameMode, GameState, and PlayerState. Learn their roles, multiplayer information flow, and C++ implementation skeletons.

Overview

Tested with: UE 5.4+

"GameMode, GameState, PlayerState... the names are similar but what's the difference?" — This is one of the first walls many developers hit when starting UE development. In multiplayer games especially, without a solid understanding of which class exists where and what gets replicated, you'll struggle with server-client data synchronization.

Understanding the role separation of these three classes enables you to build robust, extensible game logic for both single-player and multiplayer games.

Comparing the Three Classes

Let's grasp the big picture first.

ClassLocationRoleReplicated
GameModeServer onlyDefine and enforce game rulesNo
GameStateServer + all clientsManage overall game stateYes
PlayerStateServer + all clientsManage per-player stateYes

The most critical point is that GameMode exists only on the server. This design prevents clients from tampering with game rules (win conditions, spawn locations, join requirements, etc.). Game state that clients need to know is synchronized through GameState and PlayerState.

GameMode: The Rulebook

GameMode is the class that defines the game's rules themselves. Think of it as the "referee" or "rulebook."

Key responsibilities:

  • Handling player join/leave (login/logout)
  • Pawn spawn location, timing, and conditions
  • Match start/end conditions
  • Game mode-specific rules (deathmatch, CTF, etc.)

AGameModeBase vs AGameMode

UE provides two base classes for GameMode. The default for new projects is AGameModeBase.

ClassFeaturesRecommended For
AGameModeBaseSimple and lightweightSingle-player, non-match-based games
AGameModeBuilt-in match state machineMatch-based multiplayer (team deathmatch, etc.)

AGameMode extends AGameModeBase with a built-in state machine managing six match states: EnteringMapWaitingToStartInProgressWaitingPostMatchLeavingMap (with Aborted for errors). Used for controlling match waiting periods and post-game screens.

GameMode can be set via Project Settings, level World Settings, or URL arguments, and different levels can use different GameModes.

GameState: The Scoreboard

GameState manages the overall current state of the game, synchronized to all clients. If GameMode is the "rulebook," GameState is the "scoreboard" or "timer."

Key responsibilities:

  • Game elapsed time (GetServerWorldTimeSeconds() for server-synchronized accurate time)
  • Connected player list (PlayerArray property)
  • Team scores and other game-wide information not tied to specific players
  • Game started state (HasBegunPlay())

GameState is for managing game-wide state only. Individual player-specific data (personal scores, names, etc.) belongs in PlayerState, covered next.

PlayerState: The Personal Record

PlayerState manages the state of each individual player in the game. Since it's replicated to all clients, it can be used directly for purposes like displaying other players' info on a score ranking UI.

Built-in properties and methods:

  • Player name: GetPlayerName()
  • Score: GetScore() / SetScore()
  • Ping: GetPingInMilliseconds()
  • Player ID: GetPlayerId()
  • Bot check: IsABot()

GameState's PlayerArray contains PlayerState instances. The server and each client can reference the state of all connected players through this array.

Information Flow Example

Let's see how the three classes work together through a concrete scenario.

When a player kills an enemy and earns score:

  1. The player's Pawn kills an enemy
  2. The PlayerController (or Pawn) notifies the server of the "enemy killed" event
  3. The GameMode on the server executes "add score" based on the rules
  4. GameMode retrieves the relevant player's PlayerState and updates the score
  5. The PlayerState's score variable is marked Replicated, so the server's change automatically propagates to all clients
  6. Each client's UI detects the PlayerState change and updates the score display

This is the fundamental design pattern: GameMode enforces rules server-side, and communicates results to all clients via GameState/PlayerState.

C++ Implementation Skeletons

Here are minimal implementation examples for customizing all three classes.

Custom GameMode

// MyGameMode.h
#pragma once
#include "GameFramework/GameModeBase.h"
#include "MyGameMode.generated.h"

UCLASS()
class AMyGameMode : public AGameModeBase
{
    GENERATED_BODY()

public:
    AMyGameMode();

    void OnPlayerScored(APlayerController* Scorer, int32 Points);

    virtual void PostLogin(APlayerController* NewPlayer) override;
    virtual void Logout(AController* Exiting) override;
};
// MyGameMode.cpp
#include "MyGameMode.h"
#include "MyGameState.h"
#include "MyPlayerState.h"

AMyGameMode::AMyGameMode()
{
    // Specify custom GameState and PlayerState classes
    GameStateClass = AMyGameState::StaticClass();
    PlayerStateClass = AMyPlayerState::StaticClass();
}

void AMyGameMode::OnPlayerScored(APlayerController* Scorer, int32 Points)
{
    if (AMyPlayerState* PS = Scorer->GetPlayerState<AMyPlayerState>())
    {
        PS->AddScore(Points);
    }
}

Setting GameStateClass and PlayerStateClass in the constructor tells UE to automatically instantiate your custom classes.

Custom GameState (with Replicated Variables)

// MyGameState.h
#pragma once
#include "GameFramework/GameStateBase.h"
#include "MyGameState.generated.h"

UCLASS()
class AMyGameState : public AGameStateBase
{
    GENERATED_BODY()

public:
    // Variables marked Replicated are automatically synced to all clients
    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"

void AMyGameState::GetLifetimeReplicatedProps(
    TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    // Register replicated variables with the DOREPLIFETIME macro
    DOREPLIFETIME(AMyGameState, TeamAScore);
    DOREPLIFETIME(AMyGameState, TeamBScore);
}

Variables marked with UPROPERTY(Replicated) must be registered with the DOREPLIFETIME macro in GetLifetimeReplicatedProps. Don't forget to include Net/UnrealNetwork.h.

Tip: Use DOREPLIFETIME_CONDITION for conditional replication (owner only, initial only, etc.) to save bandwidth.

Custom PlayerState

// 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)
{
    // Update score using APlayerState's built-in method
    SetScore(GetScore() + Points);
    KillCount++;
}

void AMyPlayerState::GetLifetimeReplicatedProps(
    TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(AMyPlayerState, KillCount);
}

APlayerState has built-in SetScore() / GetScore(). When adding custom variables (like KillCount here), use the same Replicated + DOREPLIFETIME pattern to register them.

RepNotify: To detect variable changes on the client side for UI updates, combine UPROPERTY(ReplicatedUsing=OnRep_KillCount) with UFUNCTION() void OnRep_KillCount(). When the value changes on the server, the OnRep_ callback is automatically called on clients — the standard place to put score display update logic.

For single-player games: When not targeting multiplayer, it's viable to skip GameState/PlayerState and consolidate most logic into GameMode. However, if there's any chance of multiplayer expansion in the future, separating into three classes from the start dramatically reduces migration cost.

Summary

  • GameMode = Rulebook. Exists only on the server, not replicated to clients
  • GameState = Scoreboard. Syncs overall game state to all clients
  • PlayerState = Personal record. Syncs per-player info to all clients
  • Set GameStateClass / PlayerStateClass in the GameMode constructor to use custom classes
  • Register replication variables with UPROPERTY(Replicated) + DOREPLIFETIME macro

Further Reading