Menu
CoddyTech

События C# в Unity

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

При изменении счёта это важно для нескольких вещей: текста счёта, звука, достижения. Если ScoreKeeper вызывает каждую из них, ему нужно поле для каждой, а добавление каждой новой возможности требует его редактировать. Событие меняет подход на противоположный: ScoreKeeper объявляет об изменении, а все, кому это важно, подписываются. Ты знаешь event и Action из C#. В 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 или singleton. Удаление слушателя с помощью -= важно, когда объекты уничтожаются; об этом следующий урок.

challenge icon

Задание

Легко

Комбо бойца увеличивается с каждым нажатием Space и сбрасывается до 0 через comboWindow (1) секунду без ударов. Сейчас Fighter.cs сам задаёт текст комбо и размер пламени, поэтому ему нужно поле для каждого из них. Измени это с помощью события:

  • В Fighter.cs объяви открытое событие с именем OnComboChanged, которое передаёт комбо (int). Вызывай его с новым значением комбо каждый раз, когда оно меняется, в том числе при сбросе до 0, и убедись, что при ударе не возникает ошибки, если никто не подписан на событие.
  • Удали поля comboText и flame из Fighter и строки, в которых они используются: Fighter ничего не знает о HUD.
  • В ComboText.cs и ComboFlame.cs подпиши их методы Show и Grow на событие бойца в Start(). У обоих есть поле fighter, связанное в сцене.

Нажми Play, несколько раз ударь по мешку клавишей Space, подожди секунду, затем нажми Check.

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

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;
            }
        }
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

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