Реализовать Singleton для Unity
Условие
Реализуйте универсальный базовый класс Singleton для Unity, который можно использовать для создания менеджеров игры.
Требования
- Создайте generic класс Singleton<T> : MonoBehaviour
- Singleton должен автоматически находить или создавать экземпляр при первом обращении
- Реализуйте опцию DontDestroyOnLoad
- Обработайте ситуацию когда в сцене случайно оказалось несколько экземпляров
- Instance должен быть потокобезопасным
Пример использования
public class GameManager : Singleton<GameManager>
{
public int Score { get; private set; }
public void AddScore(int points)
{
Score += points;
}
}
// Использование
GameManager.Instance.AddScore(100);
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Что такое 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 для:
- Контроллеров персонажа (их может быть много)
- Логики врагов (их может быть много)
- Случайных вспомогательных классов (лучше использовать инъекцию зависимостей)
Эта реализация обеспечивает надёжность, потокобезопасность и удобство в игровых проектах.