События 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. Удаление слушателя с помощью -= важно, когда объекты уничтожаются; об этом следующий урок.
Задание
ЛегкоКомбо бойца увеличивается с каждым нажатием 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;
}
}
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Архитектура игр в Unity
1Компоненты, а не иерархии
Небольшие компонентыБазовый скрипт врагаАбстрактные компонентыСовместная работа компонентовПовторение: модульная турель4Сериализованные данные
Сериализуемые классыСписки в InspectorПеречисления в InspectorПовторение: таблица лута2Интерфейсы в Unity
IDamageableИнтерфейсы и TryGetComponentGetComponentsПовторение: клавиша взаимодействия5ScriptableObjects
Ресурсы данныхХарактеристики оружияОбщие настройкиВолны как данныеПовторение: база предметов3События
События C# в UnityПодписка в OnEnableПоля UnityEventСобытия управляют интерфейсомПовторение: всплывающее окно достижения6Сохранение с помощью JSON
JsonUtilityСохранение игрыЗагрузка и значения по умолчаниюПовторение: слоты сохранения