← Назад к вопросам

Реализовать Singleton для Unity

1.0 Junior🔥 281 комментариев
#C# и ООП#Unity Core#Асинхронность и многопоточность#Паттерны проектирования#Управление памятью

Условие

Реализуйте универсальный базовый класс Singleton для Unity, который можно использовать для создания менеджеров игры.

Требования

  1. Создайте generic класс Singleton<T> : MonoBehaviour
  2. Singleton должен автоматически находить или создавать экземпляр при первом обращении
  3. Реализуйте опцию DontDestroyOnLoad
  4. Обработайте ситуацию когда в сцене случайно оказалось несколько экземпляров
  5. Instance должен быть потокобезопасным

Пример использования

public class GameManager : Singleton<GameManager>
{
    public int Score { get; private set; }
    
    public void AddScore(int points)
    {
        Score += points;
    }
}

// Использование
GameManager.Instance.AddScore(100);

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Что такое Singleton в Unity?

Singleton — паттерн проектирования, который гарантирует, что класс имеет только один экземпляр и предоставляет глобальную точку доступа к нему. В игровых проектах Singleton используется для менеджеров (GameManager, AudioManager, UIManager и т.д.), которые должны быть уникальными и доступны из любой части кода. Правильная реализация должна быть потокобезопасной и обрабатывать специфику Unity.

Реализация Singleton<T>

using UnityEngine;
using System;

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;
    private static readonly object lockObject = new object();
    private static bool applicationIsQuitting = false;
    
    /// <summary>Свойство для получения/создания singleton экземпляра</summary>
    public static T Instance
    {
        get
        {
            // Если приложение выключается, не создаём новые singletons
            if (applicationIsQuitting)
            {
                Debug.LogWarning(
                    $"Попытка обратиться к {typeof(T).Name} после выключения приложения"
                );
                return null;
            }
            
            // Потокобезопасная ленивая инициализация
            if (instance == null)
            {
                lock (lockObject)
                {
                    // Double-check locking pattern
                    if (instance == null)
                    {
                        // Ищем существующий экземпляр в сцене
                        instance = FindObjectOfType<T>();
                        
                        if (instance == null)
                        {
                            // Если не найден, создаём новый объект
                            var singletonObject = new GameObject(
                                typeof(T).Name,
                                typeof(T)
                            );
                            instance = singletonObject.GetComponent<T>();
                        }
                        else if (FindObjectsOfType<T>().Length > 1)
                        {
                            // Обработка множественных экземпляров
                            Debug.LogError(
                                $"В сцене найдено несколько экземпляров {typeof(T).Name}. "
                                + "Это нарушает паттерн Singleton!"
                            );
                        }
                    }
                }
            }
            
            return instance;
        }
    }
    
    /// <summary>Проверить существует ли singleton</summary>
    public static bool HasInstance => instance != null;
    
    protected virtual void Awake()
    {
        // Проверяем является ли это единственным экземпляром
        if (instance == null)
        {
            instance = GetComponent<T>();
        }
        else if (instance != GetComponent<T>())
        {
            // Если это не наш экземпляр, удаляем дублик
            Debug.LogWarning(
                $"Обнаружен дублик {typeof(T).Name}. Удаляю лишний экземпляр."
            );
            Destroy(gameObject);
            return;
        }
    }
    
    protected virtual void OnDestroy()
    {
        if (instance == GetComponent<T>())
        {
            instance = null;
        }
    }
    
    private void OnApplicationQuit()
    {
        applicationIsQuitting = true;
    }
}

/// <summary>Singleton с автоматическим DontDestroyOnLoad</summary>
public class PersistentSingleton<T> : Singleton<T> where T : MonoBehaviour
{
    protected override void Awake()
    {
        base.Awake();
        
        // Помечаем объект для сохранения между сценами
        if (gameObject.scene.name != null)
        {
            DontDestroyOnLoad(gameObject);
        }
    }
}

Примеры использования

Обычный Singleton:

public class GameManager : Singleton<GameManager>
{
    [SerializeField] private int initialScore = 0;
    private int score;
    
    public int Score => score;
    
    protected override void Awake()
    {
        base.Awake();
        score = initialScore;
    }
    
    public void AddScore(int points)
    {
        if (points < 0)
        {
            Debug.LogWarning("Score не может быть отрицательным");
            return;
        }
        
        score += points;
        Debug.Log($"Набрано очков: {points}. Всего: {score}");
    }
    
    public void ResetScore()
    {
        score = initialScore;
    }
}

// Использование
GameManager.Instance.AddScore(100);
int currentScore = GameManager.Instance.Score;

Persistent Singleton (сохраняется между сценами):

public class AudioManager : PersistentSingleton<AudioManager>
{
    [SerializeField] private AudioSource audioSource;
    [SerializeField] private float masterVolume = 0.8f;
    
    public void PlaySound(AudioClip clip, float pitch = 1f)
    {
        audioSource.pitch = pitch;
        audioSource.PlayOneShot(clip, masterVolume);
    }
    
    public void SetVolume(float volume)
    {
        masterVolume = Mathf.Clamp01(volume);
        audioSource.volume = masterVolume;
    }
}

// Использование
AudioManager.Instance.PlaySound(explosionSound);

Обработка проблем

1. Множественные экземпляры — если в сцене случайно окажется несколько экземпляров, скрипт автоматически удалит дублики и оставит только первый.

2. Выключение приложения — флаг applicationIsQuitting предотвращает создание новых singleton'ов после начала выключения (OnApplicationQuit).

3. Потокобезопасность — double-check locking гарантирует корректность при доступе из разных потоков.

4. Ленивая инициализация — singleton создаётся только при первом обращении, что экономит ресурсы.

Рекомендации по использованию

Используй Singleton для:

  • GameManager (управление состоянием игры)
  • AudioManager (проигрывание звуков)
  • UIManager (управление интерфейсом)
  • SaveManager (сохранение данных)
  • NetworkManager (сетевые операции)

Избегай Singleton для:

  • Контроллеров персонажа (их может быть много)
  • Логики врагов (их может быть много)
  • Случайных вспомогательных классов (лучше использовать инъекцию зависимостей)

Эта реализация обеспечивает надёжность, потокобезопасность и удобство в игровых проектах.