GDKB

Static Members

Intermediate20 Minutes readUpdated July 21, 20263 related concepts

A static member belongs to the type itself rather than to any particular instance — there is exactly one copy of a static field, shared by every object of that class, and a static method can be called without creating an object at all. Static members exist for state and behavior that genuinely have nothing to do with "which instance," such as a shared counter, a utility function, or a single global manager.

On this page

Static Members

Summary

A static member belongs to the type itself rather than to any particular instance — there is exactly one copy of a static field, shared by every object of that class, and a static method can be called without creating an object at all. Static members exist for state and behavior that genuinely have nothing to do with "which instance," such as a shared counter, a utility function, or a single global manager.

Mental Model

If a class is a blueprint for a house, an instance field is something each individual house has its own copy of — its own furniture, its own mailbox contents. A static field is more like a shared amenity for the whole neighborhood: there's only one community pool, not one per house, and every house references the same pool rather than each getting an independent copy. Changing the pool's water level from one house's back yard changes it for every house, because there was only ever one pool to begin with.

This is exactly why a static field is dangerous when misused: instance fields are naturally isolated (each Player has its own health), but a static field is inherently shared, global state — every part of the codebase that touches it is touching the same value, whether that was intended or not.

Core Explanation

  • One copy per type, not per instance. static int enemyCount; exists exactly once regardless of how many Enemy objects are created — every instance reads and writes the same shared value.
  • Callable without an instance. MathUtils.Clamp(x, 0, 1) needs no MathUtils object to exist first — the method belongs to the type, and is invoked through the type name directly (Type.Method(), not instance.Method()).
  • Initialized once, lives for the program's duration. A static field's memory is allocated when the type is first used and persists for the entire lifetime of the program (or, in Unity, typically for the entire play session unless explicitly reset) — it does not get created and destroyed alongside individual instances the way instance fields do.
  • A static class cannot be instantiated at all. Marking an entire class static (C#) — commonly used for pure utility/helper classes — makes every member implicitly static and prevents new MathUtils() entirely, signaling "this type has no meaningful instance state."

Syntax / Examples

C# (Unity)

csharp
public class Enemy : MonoBehaviour
{
    private static int activeCount = 0; // Shared by every Enemy instance.

    void OnEnable()  => activeCount++;
    void OnDisable() => activeCount--;

    public static int ActiveCount => activeCount; // Read without an instance.
}
csharp
public static class MathUtils
{
    public static float Clamp01(float value) => Mathf.Clamp(value, 0f, 1f);
}
csharp
// Called via the type name — no MathUtils instance exists or is needed.
float t = MathUtils.Clamp01(rawValue);
Debug.Log($"Enemies alive: {Enemy.ActiveCount}");

C++ (Unreal)

cpp
class AEnemy : public ACharacter
{
public:
    AEnemy() { ++ActiveCount; }
    virtual ~AEnemy() { --ActiveCount; }

    static int32 GetActiveCount() { return ActiveCount; }

private:
    static int32 ActiveCount; // Declared here, defined once in the .cpp file.
};

// AEnemy.cpp
int32 AEnemy::ActiveCount = 0;

C++ requires a static member's storage to be defined exactly once outside the class body (as shown), in addition to being declared inside it — the in-class line is only a declaration.

Unreal Blueprint

Blueprint has no direct static-variable equivalent for gameplay classes; the closest analog is a Function Library (a UBlueprintFunctionLibrary subclass) exposing static C++ functions as callable nodes with no target object pin required — matching MathUtils.Clamp01 above.

Practical Example

A settings system needs exactly one shared configuration accessible from anywhere, without every system needing a reference passed to it.

csharp
public static class GameSettings
{
    public static float MasterVolume { get; set; } = 1f;
    public static bool ShowSubtitles { get; set; } = false;
}
csharp
public class AudioSource2D : MonoBehaviour
{
    void PlaySound(AudioClip clip)
    {
        audioSource.volume = baseVolume * GameSettings.MasterVolume;
        audioSource.PlayOneShot(clip);
    }
}

Every AudioSource2D in the game reads the same GameSettings.MasterVolume without needing a reference to a settings object passed down through every constructor or method call — the trade-off, made deliberately here rather than by accident, is that GameSettings is now global, shared, mutable state (see Common Mistakes).

Common Mistakes

  • Using static fields for per-instance state. static float health; on an Enemy class means every enemy shares one health value — damage one enemy and they all lose health. This is rarely intended; instance fields exist precisely to avoid this.
  • Reaching for static state as a shortcut instead of passing references/dependencies explicitly. It's tempting to make a manager static purely to avoid wiring up a reference — but this makes the dependency invisible (nothing about a class's public API reveals it secretly reads global state) and makes the code far harder to test in isolation.
  • Not resetting static state between play sessions in the Unity Editor. Static fields survive across "Play" sessions in some Editor configurations (domain reload disabled) — a static counter that isn't explicitly reset in OnEnable/Awake can carry a stale value from the previous session into the next.
  • Mutating shared static state from multiple systems without any clear owner, making it unclear which system is responsible for keeping it correct — the same problem a public mutable field on an instance has (see [[CON-0020]]), amplified because every instance and every system shares the one copy.

Best Practices

  • Use static members for things that are genuinely type-level — pure utility functions, constants, or a truly singular piece of shared state (not merely "state I don't want to pass around").
  • Prefer passing a reference/dependency explicitly over reaching for static/global access, especially for anything that needs to be substituted or isolated in tests.
  • If static state must exist, keep exactly one system responsible for writing to it, and treat every other system as a read-only consumer.
  • In Unity specifically, explicitly reset static state that represents "session" data (counters, caches) in Awake/OnEnable rather than assuming it starts fresh every play session.

Exercises

  1. Take the Enemy.activeCount example and deliberately introduce the "shared health" bug by making an enemy's health field static instead of instance-level. Damage one enemy and observe what happens to the others; explain why in terms of "one copy per type."
  2. Convert GameSettings into an instance-based settings object passed explicitly to each system that needs it, and describe one concrete advantage this has for testing AudioSource2D in isolation.
  3. Explain why C++ requires a static member to be defined once outside the class body in addition to being declared inside it, and what would happen (conceptually) if that rule didn't exist and every including file got its own copy.
  4. Identify a piece of global/static state in a hypothetical project (a score, a difficulty setting, a debug flag) and argue whether it's a legitimate use of static state or should be refactored to an explicitly passed dependency.
  • Access Modifiers — static members are just as subject to public/private/protected visibility rules as instance members.
  • Enums — a static lookup table or method keyed by an enum value (e.g. mapping a Rarity to a display color) is one of the most common uses of static members.
  • Objects — understanding what makes an instance distinct is the necessary contrast for understanding what makes a static member different from one.

References

Related Concepts