События (Events/Actions) в Unity: Полное руководство

События (Events Actions) в Unity

События (Events) и Действия (Actions) — это механизм в C#, который позволяет объектам общаться друг с другом, не зная о существовании друг друга. Это называется слабой связностью (loose coupling).

Аналогия из жизни

Представьте рассылку новостей:

  • Издатель (Publisher): Журнал. Он просто рассылает выпуск. Ему всё равно, кто его читает.
  • Подписчик (Subscriber): Вы. Вы подписываетесь на журнал. Когда выходит выпуск, вы его получаете.
  • Связь: Журнал не хранит ваш номер телефона в коде, у него есть просто список подписчиков.

В игре:

  • Издатель: Менеджер ресурсов (золото изменилось).
  • Подписчики: UI (обновить цифру), Достижения (проверить условие), Аналитика (записать событие), Звук (проиграть монетку).

Базовый синтаксис (C# Action)

В Unity чаще всего используют делегат Action (не возвращает значение) или Func (возвращает значение).

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

C#
// Статическое событие (доступно отовсюду)
public static Action<int> OnGoldChanged; 

// Или событие экземпляра (безопаснее)
public event Action<int> OnGoldChanged;

Подписка (Subscribe)

C#
// Знак += означает "подписаться"
ResourceManager.OnGoldChanged += UpdateUI;

Отписка (Unsubscribe) — ⚠️ КРИТИЧНО ВАЖНО

C#
// Знак -= означает "отписаться"
// Обязательно делайте это при уничтожении объекта, иначе будет утечка памяти!
ResourceManager.OnGoldChanged -= UpdateUI;

Вызов события (Invoke)

C#
// Знак ?. нужен, чтобы проверить, есть ли подписчики (чтобы не было ошибки null)
OnGoldChanged?.Invoke(newGoldAmount);

Практический пример для стратегии (SLG)

Допустим, у вас есть Менеджер Ресурсов. Когда меняется золото, нужно:

  1. Обновить текст в UI.
  2. Проверить достижение «Богач».
  3. Отправить статистику.

Шаг 1: Издатель (ResourceManager)

C#
using System;
using UnityEngine;

public class ResourceManager : MonoBehaviour
{
    // Событие: передает новое количество золота
    public static Action<int> OnGoldChanged; 

    private int _gold = 0;

    public void AddGold(int amount)
    {
        _gold += amount;
        // Сообщаем всем подписчикам: "Золото изменилось!"
        OnGoldChanged?.Invoke(_gold);
    }

    public void SpendGold(int amount)
    {
        if (_gold >= amount)
        {
            _gold -= amount;
            OnGoldChanged?.Invoke(_gold);
        }
    }
}

Шаг 2: Подписчик 1 (UI Manager)

C#
using UnityEngine;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    [SerializeField] private Text _goldText;

    private void OnEnable()
    {
        // Подписываемся, когда объект активирован
        ResourceManager.OnGoldChanged += UpdateGoldText;
    }

    private void OnDisable()
    {
        // Отписываемся, когда объект деактивирован (важно для памяти!)
        ResourceManager.OnGoldChanged -= UpdateGoldText;
    }

    private void UpdateGoldText(int newGold)
    {
        _goldText.text = newGold.ToString();
    }
}

Шаг 3: Подписчик 2 (Achievement System)

C#
public class AchievementSystem : MonoBehaviour
{
    private void OnEnable()
    {
        ResourceManager.OnGoldChanged += CheckRichAchievement;
    }

    private void OnDisable()
    {
        ResourceManager.OnGoldChanged -= CheckRichAchievement;
    }

    private void CheckRichAchievement(int gold)
    {
        if (gold >= 10000)
        {
            Debug.Log("Достижение разблокировано: Золотой магнат!");
            // UnlockAchievement("rich_man");
        }
    }
}

Результат: ResourceManager ничего не знает про UI или Достижения. Вы можете удалить UI, и игра не сломается (просто не обновится цифра).

UnityEvent vs C# Action

В Unity есть два типа событий. Важно понимать разницу.

Характеристика
C# Action / Event
UnityEvent
Где настраивается
В коде
В Inspector (можно перетащить объект)
Производительность
⚡ Высокая (быстрее)
🐢 Ниже (использует рефлексию)
Гибкость
Можно передавать любые данные
Ограниченный набор типов
Для кого
Программисты
Дизайнеры уровней / новички
Пример
public static Action<int> OnHit;
public UnityEvent<int> OnHit;

Рекомендация: Для логики игры (экономика, бой) используйте C# Action. Для интерфейсов (кнопки, простые клики) можно использовать UnityEvent, чтобы дизайнеры могли настраивать реакции без кода.

Главные правила и ловушки

Ловушка 1: Утечка памяти (Memory Leak)

Если вы подписались на статическое событие, но не отписались при уничтожении объекта, объект не удалится из памяти (Garbage Collector не сможет его собрать).

  • Правило: Всегда отписывайтесь в OnDisable() или OnDestroy().
  • Правило: Подписывайтесь в OnEnable() или Start().

Ловушка 2: Статические события

Статические события (public static Action) живут всю жизнь приложения.

  • Риск: Если подписчик удалится, а событие останется, при вызове события произойдет ошибка (если не использовать ?.).
  • Совет: Для больших проектов лучше использовать Event Bus (патрон событий), а не кучу статических событий в разных классах.

Ловушка 3: Порядок выполнения

Неизвестно, какой подписчик получит событие первым.

  • Риск: Если логика зависит от порядка (сначала UI, потом Звук), события могут не подойти.
  • Решение: В таких случаях лучше прямой вызов или очередь событий.

Ловушка 4: Отписка во время вызова

Нельзя отписываться от события внутри метода, который это событие вызвал (ошибка Collection was modified).

  • Решение: Используйте флаг isDestroyed или отписывайтесь в следующем кадре.

Продвинутый уровень: Event Bus (Шина событий)

Для большой стратегии куча статических событий превратится в кашу. Лучше создать единый центр событий.

C#
public static class GameEvents
{
    // Экономика
    public static Action<int> OnGoldChanged;
    public static Action<int> OnFoodChanged;
    
    // Строительство
    public static Action<string> OnBuildingCompleted;
    
    // Бой
    public static Action<Vector3> OnEnemySpotted;
}

Подписка: GameEvents.OnGoldChanged += …
Это упорядочивает код и делает события видимыми в одном месте.

Когда НЕ стоит использовать события?

  1. Если связь однозначная: Если у Пули есть ссылка на Врага, и она должна нанести урон именно ему — используйте прямой вызов enemy.TakeDamage(). События тут избыточны.
  2. Если нужен ответ: События (Action) не возвращают значение. Если вам нужно спросить «Можно ли построить?» и получить «Да/Нет», используйте прямой запрос или Func.
  3. Если это сбивает с толку: Если новичок в команде не понимает, откуда вызывается функция — лучше использовать прямой код. Отладка событий сложнее (нельзя поставить breakpoint на вызове, если не знаешь подписчика).

Чек-лист для внедрения в игру

  1. Выделите глобальные события: (Ресурсы, Квесты, Уровень игрока). Создайте класс GameEvents.
  2. UI подписывается на данные: UI никогда не меняет данные сам, он только слушает события изменений.
  3. Следите за OnDestroy: Пройдитесь по всем скриптам с подпиской и убедитесь, что есть отписка.
  4. Используйте параметры: Передавайте в событие нужные данные (int amount, BuildingType type), чтобы подписчик не лез в базу данных снова.

События — это мощный инструмент для архитектуры. В экономической стратегии они помогут вам легко добавлять новые фичи (например, новое достижение), не переписывая старый код менеджера ресурсов.

Оцените статью
Unity Learn
Добавить комментарий