🧩 Делегаты и события в Unity: полное руководство


На главную > Блог > Категория > 🧩 Делегаты и события в Unity: полное руководство

Делегаты и события в Unity

C# Unity делегаты события паттерны архитектура

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

В этой статье мы разберём всё, что нужно знать о делегатах и событиях в Unity: от базового синтаксиса до продвинутых паттернов Action, UnityAction и лучших практик.

1. 🧵 Что такое делегат (Delegate)

Делегат — это ссылка на метод. Простыми словами: это «контейнер», в который ты можешь положить любой метод с подходящей сигнатурой и вызвать его, даже не зная, какой именно метод вызываешь.

Делегат определяет тип метода:

Синтаксис объявления делегата:


// Объявление делегата
public delegate void MyDelegate(string message);

// Использование
public MyDelegate OnSomethingHappened;
💡 Аналогия из жизни: Делегат — это как номер телефона. Ты даёшь кому-то номер, и тот человек звонит по нему, даже не зная, кто на другом конце провода. Главное, что в трубке ответят и что-то скажут.

Пример с делегатом:


public class SimpleDelegateExample : MonoBehaviour
{
    public delegate void GreetingDelegate(string name);
    private GreetingDelegate _greeting;

    void Start()
    {
        // Подписываем методы
        _greeting = SayHello;
        _greeting += SayGoodbye;

        // Вызываем всех подписчиков
        if (_greeting != null)
            _greeting("Игрок");
    }

    void SayHello(string name) => Debug.Log($"Привет, {name}!");
    void SayGoodbye(string name) => Debug.Log($"Пока, {name}!");
}

2. 📢 События (Events)

Событие — это делегат с ограничениями. Он позволяет классу оповестить внешний мир о том, что что-то произошло, но запрещает внешнему коду вызывать это событие — только сам класс может это сделать.

🛡️ Ключевое отличие от делегата: Событие нельзя вызвать извне. Только класс, в котором оно объявлено, может его запустить. Делегат же можно вызвать откуда угодно, если он публичный.

Объявление события:


public class Player : MonoBehaviour
{
    // Объявляем событие
    public event Action OnPlayerDeath;

    public void TakeDamage(int damage)
    {
        // ... логика получения урона
        if (health <= 0)
        {
            // Вызываем событие (ТОЛЬКО ИЗ ЭТОГО КЛАССА!)
            OnPlayerDeath?.Invoke();
        }
    }
}
⚠️ Важно: Событие можно вызвать только из класса-владельца (в примере — из Player). Внешний код не сможет сделать player.OnPlayerDeath.Invoke() — компилятор выдаст ошибку.

3. 🧰 Встроенные делегаты: Action, Func, Predicate

В C# уже есть готовые, обобщённые делегаты, чтобы ты не писал свои каждый раз.

Тип Описание Пример
Action Не возвращает значение (void), может иметь до 16 параметров Action<int, string>
Func<T> Возвращает значение T, может иметь до 16 параметров Func<int, bool> — принимает int, возвращает bool
Predicate<T> Возвращает bool, принимает один параметр Predicate<int> — проверяет условие для int

Пример с Action:


public class ActionExample : MonoBehaviour
{
    public event Action OnHealthChanged;
    public event Action<int, int> OnDamageTaken; // (текущее, максимальное)

    void TakeDamage(int damage)
    {
        // ... логика
        OnHealthChanged?.Invoke();
        OnDamageTaken?.Invoke(currentHealth, maxHealth);
    }
}

4. 🎮 UnityAction (из UnityEngine.Events)

Unity предоставляет свой делегат — UnityAction и UnityAction<T>. Главное преимущество: он сериализуется в инспекторе.


using UnityEngine.Events;

public class UnityActionExample : MonoBehaviour
{
    public UnityAction OnJump;
    public UnityAction<float, float> OnHealthChanged;

    void Jump()
    {
        OnJump?.Invoke();
    }
}
💡 Когда использовать:
  • Action / Func — для чистого C# кода, без привязки к инспектору.
  • UnityAction / UnityEvent — если хочешь видеть событие в инспекторе и привязывать методы через интерфейс Unity.

5. 🔗 Подписка и отписка от событий

Это один из самых частых источников ошибок. Всегда отписывайся от событий в OnDisable или OnDestroy, чтобы избежать утечек памяти.


public class Subscriber : MonoBehaviour
{
    [SerializeField] private Player _player;

    void OnEnable()
    {
        // Подписываемся
        _player.OnPlayerDeath += HandleDeath;
        _player.OnDamageTaken += OnDamageTakenHandler;
    }

    void OnDisable()
    {
        // Отписываемся!
        _player.OnPlayerDeath -= HandleDeath;
        _player.OnDamageTaken -= OnDamageTakenHandler;
    }

    void HandleDeath()
    {
        Debug.Log("Игрок умер!");
    }

    void OnDamageTakenHandler(int current, int max)
    {
        Debug.Log($"Здоровье: {current}/{max}");
    }
}
⚠️ Критически важно: Если забыть отписаться, подписанный объект не удалится из памяти (даже если он уже уничтожен). Это приведёт к утечкам памяти. Всегда отписывайся в OnDisable.

6. 🧠 UnityEvent (инспекторный вариант)

UnityEvent — это сериализуемое событие, которое отображается в инспекторе. Ты можешь привязать методы прямо в редакторе, не писав код.


using UnityEngine.Events;

public class UnityEventExample : MonoBehaviour
{
    // Отображается в инспекторе
    public UnityEvent OnJump;
    public UnityEvent<float> OnScoreChanged;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            OnJump?.Invoke();
            OnScoreChanged?.Invoke(100);
        }
    }
}
📌 Плюсы UnityEvent:
  • Виден в инспекторе — можно привязать методы других объектов без кода.
  • Поддерживает несколько подписчиков.
  • Не требует ручной отписки (Unity следит за объектами).
📌 Минусы:
  • Медленнее, чем обычные делегаты (но для большинства случаев это не критично).
  • Меньше контроля над подпиской/отпиской.

7. 🆚 Сравнение делегатов и событий

Характеристика Делегат Событие (event)
Можно вызвать извне? ✅ Да ❌ Только изнутри класса
Можно подписаться извне? ✅ Да ✅ Да
Сериализация в инспекторе ❌ Нет ❌ Нет (кроме UnityEvent)
Безопасность 🟡 Средняя ✅ Высокая (защита от вызова)

8. 🚀 Продвинутые паттерны

Событие с аргументами (кастомный класс)


public class GameEventArgs
{
    public int Score;
    public string PlayerName;
}

public class GameManager : MonoBehaviour
{
    public event Action<GameEventArgs> OnGameOver;

    public void EndGame(int score, string name)
    {
        var args = new GameEventArgs
        {
            Score = score,
            PlayerName = name
        };
        OnGameOver?.Invoke(args);
    }
}

Асинхронное событие (для загрузки)


public class AsyncEventExample : MonoBehaviour
{
    public event Action OnDownloadComplete;

    public async void DownloadData()
    {
        await Task.Delay(2000);
        OnDownloadComplete?.Invoke();
    }
}

9. ❌ Частые ошибки

10. 🎯 Когда что использовать

🏆 Золотое правило: Делегаты — для внутренней логики, события — для коммуникации между объектами.

🎯 Итог

Делегаты и события — это фундамент любой архитектуры в Unity. Они позволяют создавать слабо связанные системы, легко расширять код и избегать спагетти-кода.

Главные выводы:

 

Дата размещения статьи: 24-06-2026 в 08:20:25