Менеджеры, сцены и переходы между уровнями

Кто хранит общий счёт, состояние игры, номер уровня? Для этого делают менеджеров. А переходы между экранами — это загрузка сцен.

Суть: менеджер (например GameManager) — объект, который хранит общее состояние игры: счёт, текущий уровень, статус (играем/пауза/проигрыш). Часто его делают синглтоном — единственным экземпляром с глобальным доступом. Переход между экранами — это загрузка другой сцены через SceneManager.LoadScene.

Объекты приходят и уходят, а общий счёт и прогресс должны где-то жить. Эту роль берёт менеджер — обычно один объект GameManager на всю игру. Он хранит состояние и управляет ходом игры: начать, поставить на паузу, завершить.

Чтобы к менеджеру было удобно обращаться откуда угодно, его часто делают синглтоном — гарантируют, что он существует в единственном экземпляре, и дают к нему глобальную точку доступа GameManager.Instance.

Как работает под капотом

Структура управления игрой:

  GameManager (синглтон, живёт всю игру)
     |  score, currentLevel, state (Play/Pause/GameOver)
     |
     +-- управляет переходами сцен:
            SceneManager.LoadScene("Level2")

  Сцены:  [MainMenu] --Старт--> [Level1] --финиш--> [Level2] --...--> [Win]

  DontDestroyOnLoad(GameManager) -> переживает смену сцен.

Каждый уровень или экран — отдельная сцена. Переход — это SceneManager.LoadScene("имя"). Но при смене сцены обычные объекты уничтожаются! Чтобы менеджер пережил переход и сохранил счёт, его помечают DontDestroyOnLoad.

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }
    public int Score { get; private set; }

    void Awake()
    {
        if (Instance != null) { Destroy(gameObject); return; }
        Instance = this;
        DontDestroyOnLoad(gameObject);   // переживёт смену сцен
    }

    public void AddScore(int amount) { Score += amount; }
    public void NextLevel(string sceneName) { SceneManager.LoadScene(sceneName); }
}

Смоделируем синглтон-менеджер и переход по сценам на Python:

# Менеджер-синглтон: один на всю игру, хранит счёт и уровень
class GameManager:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.score = 0
            cls._instance.level = 1
        return cls._instance      # всегда тот же объект

def load_scene(gm, name):
    print(f"Загружаем сцену '{name}' (счёт сохранён: {gm.score})")

a = GameManager()
a.score += 50
b = GameManager()                 # это ТОТ ЖЕ объект
print("a и b — один объект?", a is b, "| счёт:", b.score)

load_scene(a, "Level2")           # счёт переживает переход
b.level = 2
print("Текущий уровень:", a.level)

Та же логика на Python ▶ — синглтон гарантирует, что a и b это один и тот же объект, поэтому счёт общий. При «загрузке сцены» состояние менеджера сохраняется — именно для этого в Unity нужен DontDestroyOnLoad.

Частые ошибки

  • Не добавить сцену в Build Settings. LoadScene загрузит сцену, только если она в списке сборки. Иначе — ошибка.
  • Два менеджера после перехода. Без проверки if (Instance != null) Destroy при возврате на сцену появятся дубликаты синглтона.
  • Хранить всё в синглтоне. Синглтон удобен, но превращать его в «бога-объект» со всем подряд — путь к мешанине. Храни только действительно глобальное.
  • Забыть DontDestroyOnLoad. Тогда менеджер уничтожится при смене сцены и счёт обнулится.

Best practices

  • Делай менеджер синглтоном с защитой от дублей и DontDestroyOnLoad.
  • Добавляй все сцены в Build Settings заранее и грузи по имени.
  • Держи в менеджере только глобальное состояние (счёт, прогресс, настройки), а не всё подряд.

Итоги: менеджер (часто синглтон) хранит общее состояние игры и управляет её ходом. Переходы между экранами — это загрузка сцен через SceneManager.LoadScene; чтобы менеджер пережил переход, нужен DontDestroyOnLoad. Сцены должны быть в Build Settings, а синглтон — защищён от дублей.

Аддитивная загрузка сцен

Мы грузили сцены так, что новая полностью заменяет старую. Но LoadScene умеет и второй режим — Additive: новая сцена не выкидывает текущую, а добавляется поверх неё. Зачем? Так строят большие миры из кусков: основная сцена держит игрока и менеджеры, а куски уровня подгружаются и выгружаются вокруг него по мере движения — игрок не видит загрузочных экранов, мир кажется бесшовным. Аддитивный режим удобен и для UI: общий слой меню можно держать отдельной сценой, не дублируя его в каждом уровне. Для первой игры хватит обычной замены сцен, но знание про аддитивную загрузку объясняет, как устроены игры без видимых загрузок между локациями. Это естественное продолжение идеи менеджера, переживающего смену сцен: одни части мира постоянны, другие приходят и уходят.

Проверьте себя
1. Что гарантирует паттерн «синглтон» для GameManager?
AЧто объектов будет много
BЧто существует единственный экземпляр с глобальным доступом
CЧто игра быстрее
DЧто сцена не загрузится
2. Зачем менеджеру нужен DontDestroyOnLoad?
AЧтобы он рендерился ярче
BЧтобы он пережил смену сцены и сохранил счёт и прогресс
CЧтобы ускорить загрузку
DЧтобы удалить его при переходе