Всех приветствую. Сегодня я хочу рассказать о том, как можно реализовать игровые события, представить их, а также, как в два клика сделать вполне приемлемую систему игровых событий в Unity не прибегая к разным UnityAction's.
Примечание: Эта статья не истина истин. Это лишь моё представление и мой опыт.
нет времени делать картинки к статье, а надо! Поэтому картинка из гугла!
Приведу пример с лифтом. Что мы знаем о нём? Он может передвигаться вверх или вниз, зависимо от нажатия кнопки. Однако стоит помнить,
что также он открывает/закрывает двери, воспроизводит звук и может что нибудь ещё. Я уже давно привык рассматривать многие вещи абстрактно,
поэтому могу сказать, что игровые события можно разделить на два компонента. Первый - Обработчики. Второй - события.
Обработчики - скрипты, вызывающие игровые события. Я разделяю вызов событий на несколько вариантов:
  1. Линейный. Этот вариант означает, что N'ое кол-во событий выполняется после следующего.
  2. Параллельный. Вызов более одного события на одном обработчике.
  3. Смешанный. Это смесь первого и второго варианта.
Вернёмся к лифту. Можно сразу сказать, что лифт относится к варианту №3. Поясню. Когда мы нажимаем кнопку, срабатывает событие движения лифта. Далее, когда он находится на вашем этаже, открываются двери и, допустим, воспроизводится звук. Открытие дверей и звук происходят параллельно, однако после движения. Можно это сразу представить в реализации игры. Но позже.
Чтобы вы понимали, что я имею в виду под обработчиками, вот примеры: Триггеры, нажатые кнопки (клавиши клавиатуры), лучи (Raycast) и т.п.
Ну а под триггерами: Воспроизведение звуков, движение объектов и т.п.
Абстрактно, я создал следующую архитектуру:
Кажется вполне простой, не так ли? Даже в программном плане не нужно нагружать извилины (хотя придумать реализацию мне составило труда)
Вот как выглядит базовый класс всех событий:
using UnityEngine;
// System.Collections.Generics;
public abstract class CoreEvent : MonoBehaviour {

	public CoreEvent nextEvent; // Следующее событие
	// public List<CoreEvent> listEvents;

	public abstract void Action();
}
В комментариях я указал список событий, которые могут/должны сработать. небольшой пример:
using UnityEngine;

public class LookEvent : CoreEvent
{
    public override void Action()
    {
       //if(nextEvent != null) nextEvent.Action();
       //else print("Look");
    }
}
В этом примере я вызываю лишь одно следующее событие. Для списка/массива использовать цикл. Если должно сработать одно событие,
ничего не указываем в поле nextEvent и всё, т.к. в реализации мы уже позаботились об обработке на null'ое значение.
Один момент касательно реализации. Изначально, базовый класс событий не предназначался быть абстрактным. Объясню почему. Как вы и увидели
сами, условие на null'ое значение находится в одном из событий. Т.е. такое условие делается для каждого события. На самом деле это не обязательно, если вы не используете параллельную обработку. Изначально, класс был не абстрактным и метод Action() был виртуальным (virtual),
что позволяло сделать проверку в базовом классе, прописав один раз, а далее делать так:
public ovveride void Action()
{
base.Action();
//Какая-то функция
}
Однако, тут кому как удобнее. Я решил сделать его абстрактным так как это существенно уменьшило объём кода в базовом классе события + это,
с точки зрения программирования, более логично, если знать определение ключевого слова abstract.
Далее, обработчики, базовый класс:
public class HandlerEvents: MonoBehaviour
    {

        public List<CoreEvent> events; // Указываем базовый класс.

        public virtual void Launch()
        {
          
            for (byte countEvent = 0; countEvent < events.Count; countEvent++)
            {
                if (events[countEvent] != null) events[countEvent].Action(); // Вызываем только те события, которые указаны в листе (не null).
            }
        }
    }
Как вы видите, здесь я уже использую виртуальный метод. Объясняю. Не рационально делать циклы в каждом обработчике. Ещё и с вложенным условием. Теперь пример обработчика:
	public enum TriggerType { Enter, Stay, Exit }

    public class TriggerHandler: HandlerEvents
    {
        public TriggerType triggerType;

        private void OnTriggerEnter2D(Collider2D other)
        {
            if (triggerType == TriggerType.Enter && other.GetComponent<CoreEvent>()) Launch();
        }  

        private void OnTriggerExit2D(Collider2D other)
        {
            if (triggerType == TriggerType.Exit && other.GetComponent<CoreEvent>()) Launch();
        }

        private void OnTriggerStay2D(Collider2D other)
        {
            if (triggerType == TriggerType.Stay && other.GetComponent<CoreEvent>()) Launch();
        }

    }
Достаточно универсальный код, не так ли? Это очень универсальный подход, так как вы можете делать вызов одного события, который вызовет другие события и так до тех пор, пока вам не надоест. Я делал ассет с подобной технологией и она лежит там уже пол года, однако я решил поделится ею здесь, так как та версия устарела.
Вот вроде бы и всё. Если есть вопросы, задавайте!
В скором времени расскажу о многих интересных принципах, которые я использую, которые могут помочь при работе с Unity.
Ну там организация проекта, чтение с XML, организация кода, и т.д. Скоро!