GDKB

Enums

Beginner15 Minutes readUpdated July 21, 20263 related concepts

An enum (enumeration) is a type whose values are restricted to a small, named, fixed set of options — `GameState.Menu`, `GameState.Playing`, `GameState.Paused` instead of the raw integers `0`, `1`, `2`. It exists to replace "magic numbers" and ad-hoc string constants with something the compiler can check, so an invalid state is caught at compile time instead of discovered at runtime.

On this page

Enums

Summary

An enum (enumeration) is a type whose values are restricted to a small, named, fixed set of options — GameState.Menu, GameState.Playing, GameState.Paused instead of the raw integers 0, 1, 2. It exists to replace "magic numbers" and ad-hoc string constants with something the compiler can check, so an invalid state is caught at compile time instead of discovered at runtime.

Mental Model

Think of an enum as a multiple-choice question with a fixed answer sheet. A variable of type int can hold any whole number, which is like asking an open-ended question — nothing stops someone from writing "42" as the game's state even though only three states make sense. An enum variable can only hold one of the options printed on the sheet: the compiler physically will not let GameState state = 42; compile, because 42 isn't one of the named choices.

Under the hood, each option is still just an integer (Menu = 0, Playing = 1, Paused = 2 by default) — the enum is a thin, named wrapper around that integer that exists purely to make invalid values unrepresentable and switch statements exhaustive-checkable.

Core Explanation

  • A closed set of named values. enum GameState { Menu, Playing, Paused } declares exactly three valid values; there is no way to construct a fourth one without editing the enum's own definition.
  • Backed by an integer by default. Each member gets an implicit integer value starting at 0 in declaration order, unless explicit values are assigned (enum Rarity { Common = 0, Rare = 10, Legendary = 100 }) — useful when values are serialized to save files or network messages and must stay stable even if new members are inserted later.
  • Pairs naturally with switch. Because the set of values is closed and known, a switch over an enum lets tools warn when a case is missing — a real safety net that a raw int or string cannot offer.
  • Flags enums for combinable options. Marking an enum [Flags] (C#) and giving members power-of-two values (1, 2, 4, 8, ...) allows bitwise combination — DamageType.Fire | DamageType.Poison — for cases where more than one option can be true simultaneously. This is a distinct use case from a plain enum's "exactly one of these" semantics.

Syntax / Examples

C# (Unity)

csharp
public enum GameState
{
    Menu,
    Playing,
    Paused,
    GameOver,
}

public class GameManager : MonoBehaviour
{
    public GameState CurrentState = GameState.Menu;

    public void SetState(GameState next)
    {
        switch (next)
        {
            case GameState.Menu:
                Time.timeScale = 0f;
                break;
            case GameState.Playing:
                Time.timeScale = 1f;
                break;
            case GameState.Paused:
                Time.timeScale = 0f;
                break;
            case GameState.GameOver:
                Time.timeScale = 0f;
                break;
        }
        CurrentState = next;
    }
}

C++ (Unreal)

cpp
UENUM(BlueprintType)
enum class EGameState : uint8
{
    Menu,
    Playing,
    Paused,
    GameOver
};

void AGameManager::SetState(EGameState Next)
{
    switch (Next)
    {
        case EGameState::Menu:
        case EGameState::Paused:
        case EGameState::GameOver:
            UGameplayStatics::SetGlobalTimeDilation(this, 0.0f);
            break;
        case EGameState::Playing:
            UGameplayStatics::SetGlobalTimeDilation(this, 1.0f);
            break;
    }
    CurrentState = Next;
}

Unreal Blueprint

A UENUM(BlueprintType) C++ enum (as above) becomes selectable as a variable type in Blueprint, complete with a Switch on EGameState node that auto-generates one execution pin per enum value — the visual equivalent of a switch statement's cases.

Practical Example

An item's rarity should control both its display color and its drop weight, without magic numbers scattered through the codebase.

csharp
public enum Rarity
{
    Common,
    Uncommon,
    Rare,
    Legendary,
}

public static class RarityInfo
{
    public static Color GetColor(Rarity rarity) => rarity switch
    {
        Rarity.Common => Color.white,
        Rarity.Uncommon => Color.green,
        Rarity.Rare => Color.blue,
        Rarity.Legendary => new Color(1f, 0.5f, 0f),
        _ => Color.magenta, // Visibly wrong — flags an unhandled case.
    };
}

RarityInfo.GetColor is exhaustive over every current Rarity value; if a fifth rarity is added later without updating this method, the fallback magenta makes the gap obvious in-game instead of silently defaulting to white.

Common Mistakes

  • Relying on an enum's underlying integer value directly (if (rarity == 2) instead of if (rarity == Rarity.Rare)) — it compiles, but reads like a magic number again and breaks silently if the enum's member order ever changes.
  • Reordering or inserting members in an enum whose values are already serialized (save files, network messages, PlayerPrefs). Inserting Uncommon before Rare shifts every value after it, silently corrupting any previously saved data that stored the old integer. Append new values at the end, or assign explicit stable values, instead.
  • Using an enum where a [Flags] combination was actually needed (or vice versa) — a plain enum represents exactly one choice; reaching for bitwise | on a non-flags enum produces a nonsensical value that isn't any of the declared members.
  • Forgetting a default/fallback case in a switch over an enum that might grow — without one, adding a new member later means every existing switch silently does nothing for it until each call site is manually revisited.

Best Practices

  • Give the first/default member a sensible default meaning (Menu rather than Playing as the C# default of 0) since an uninitialized enum field defaults to its zero-value member.
  • Assign explicit integer values to any enum whose values will be serialized, so future insertions can't silently shift and corrupt saved data.
  • Prefer a switch over an if/else if chain when branching on an enum — it documents that every case is being considered and lets tooling flag missing ones.
  • Reserve [Flags] enums for genuinely combinable options; if only one value is ever valid at a time, a plain enum is the correct — and more restrictive, therefore safer — choice.

Exercises

  1. Write a [Flags] enum for DamageType (Fire, Poison, Physical, Ice) and a method that returns true if a given combined value includes Fire. Explain why a plain (non-flags) enum could not express "this attack deals Fire and Poison damage."
  2. Take the GameState enum above and add a Loading state. Update the SetState switch and explain, from the compiler's behavior, how you'd notice if you forgot to handle it somewhere else in the codebase.
  3. Explain, in writing, why inserting a new member in the middle of an already-shipped enum is dangerous for a game that has existing player save files, and propose two different fixes.
  4. Convert a boolean pair (isPaused, isGameOver, both mutually exclusive with isPlaying) into a single GameState enum, and explain what invalid combination of booleans the enum makes impossible to represent.
  • Structs — enums are often stored as a field inside a struct or class to describe one aspect of that value's state.
  • Access Modifiers — enum members are implicitly public; understanding access modifiers clarifies why an enum's type can still be restricted even though its values cannot.
  • Static Members — a lookup table keyed by an enum (as in the RarityInfo example) is commonly implemented as a static dictionary/method.

References

Related Concepts