Menu
CoddyTech

События C# в Unity

Часть раздела Архитектура игр в Unity путешествия по Unity на Coddy. Урок 12 из 64.

Когда счёт меняется, это затрагивает сразу несколько вещей: текст со счётом, звук, достижение. Если ScoreKeeper вызывает каждую из них, ему нужно поле для каждой, а каждое новое свойство требует его редактировать. Событие меняет подход: ScoreKeeper объявляет об изменении, а те, кого оно касается, подписываются.

Для этого нужны два слова из C#. Action<int> — это тип метода, который принимает один int и ничего не возвращает. event такого типа — это список таких методов: другой код добавляет свой метод с помощью +=, а владелец вызывает их все с помощью Invoke. На обычном C#:

using System;

class Alarm
{
    public event Action<int> Rang;

    public void Ring(int hour)
    {
        Rang?.Invoke(hour);
    }
}

class Sleeper
{
    public void WakeUp(int hour)
    {
        Console.WriteLine("Up at " + hour);
    }
}

// в другом месте
Alarm alarm = new Alarm();
Sleeper sleeper = new Sleeper();
alarm.Rang += sleeper.WakeUp;
alarm.Ring(7);   // Подъём в 7

В Unity издатель и его подписчики — это компоненты, обычно расположенные на разных GameObject:

using System;
using UnityEngine;

public class ScoreKeeper : MonoBehaviour
{
    public event Action<int> OnScoreChanged;
    private int score;

    public void AddPoints(int points)
    {
        score += points;
        OnScoreChanged?.Invoke(score);
    }
}
// ScoreLabel.cs, на текстовом элементе HUD
public ScoreKeeper keeper;
public TMP_Text label;

void Start()
{
    keeper.OnScoreChanged += ShowScore;
}

void ShowScore(int score)
{
    label.text = "Score: " + score;
}

Action<int> означает, что каждый подписчик — это метод, принимающий один int, в данном случае новый счёт. Invoke вызывает все подписанные методы в порядке их подписки. Если подписчиков нет, значение события равно null, поэтому обычный вызов OnScoreChanged(score) вызвал бы исключение NullReferenceException, а ?. вместо этого пропускает вызов. Ключевое слово event ограничивает другие скрипты операциями += и -=: в ScoreLabel выражения keeper.OnScoreChanged(5) и keeper.OnScoreChanged = null не скомпилируются.

У ScoreKeeper нет поля для метки, и он не знает о её существовании. Счётчик комбо или проигрыватель звука может подписаться точно так же, и ScoreKeeper останется без изменений; тестовая сцена без HUD будет работать без ошибок. Подписчик находит издателя через ссылку из Inspector, как здесь, или любым известным тебе способом, например с помощью GetComponent или синглтона. Удалять подписчика с помощью -= важно, когда объекты уничтожаются; этому посвящён следующий урок.

challenge icon

Задание

Легко

Пусть боец объявляет свою серию ударов.

  1. В Fighter.cs добавь using System; и public event Action<int> OnComboChanged;.
  2. Замени строки, в которых используются comboText и flame, на OnComboChanged?.Invoke(combo); и удали оба поля.
  3. В Start() компонентов ComboText и ComboFlame напиши fighter.OnComboChanged += Show; и += Grow;.

Нажми «Играть» и нажми пробел несколько раз: серия ударов увеличивается, а затем сбрасывается. Затем нажми «Проверить».

Попробуйте сами

using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;

public class Fighter : MonoBehaviour
{
    public float comboWindow = 1f;
    public TMP_Text comboText;
    public Transform flame;
    private int combo;
    private float sinceHit;

    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame)
        {
            combo++;
            sinceHit = 0f;
            comboText.text = "Combo x" + combo;
            flame.localScale = Vector3.one * (0.5f + 0.25f * combo);
        }
        else if (combo > 0)
        {
            sinceHit += Time.deltaTime;
            if (sinceHit > comboWindow)
            {
                combo = 0;
                comboText.text = "";
                flame.localScale = Vector3.one * 0.5f;
            }
        }
    }
}

Все уроки раздела Архитектура игр в Unity

Потренируйтесь самостоятельно: Песочница Unity