Пользователь
0,0
рейтинг
10 февраля 2014 в 13:28

Разработка → Методы организации взаимодействия между скриптами в Unity3D из песочницы

Вступление


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

Подход 1. Назначение через редактор Unity3D


Пусть у нас в проекте есть два скрипта. Первый скрип отвечает за начисление очков в игре, а второй за пользовательский интерфейс, который, отображает количество набранных очков на экране игры.
Назовем оба скрипта менеджерами: ScoresManager и HUDManager.
Каким же образом менеджеру, отвечающему за меню экрана можно получить текущее количество очков от менеджера, отвечающего за начисление очков?
Предполагается, что в иерархии объектов(Hierarchy) сцены существуют два объекта, на один из которых назначен скрипт ScoresManager, а на другой скрипт HUDManager.
Один из подходов, содержит следующий принцип:
В скрипте UIManager определяем переменную типа ScoresManager:

public class HUDManager : MonoBehaviour
{
	public ScoresManager ScoresManager;
}

Но переменную ScoresManager необходимо еще инициализировать экземпляром класса. Для этого выберем в иерархии объектов объект, на который назначен скрипт HUDManager и в настройках объекта увидим переменную ScoresManager со значением None.

image

Далее, из окна иерархии перетаскиваем объект, содержащий скрипт ScoresManager в область, где написано None и назначаем его объявленной переменной:

image

После чего, у нас появляется возможность из кода HUDManager обращаться к скрипту ScoresManager, таким образом:

public class HUDManager : MonoBehaviour
{
	public ScoresManager ScoresManager;

	public void Update ()
	{
		ShowScores(ScoresManager.Scores);
	}
}

Все просто, но игра, не ограничивается одними набранными очками, HUD может отображать текущие жизни игрока, меню доступных действия игрока, информацию о уровне и многое другое. Игра может насчитывать в себе десятки и сотни различных скриптов, которым нужно получать информацию друг от друга.
Чтобы получить в одном скрипте данные из другого скрипта нам каждый раз придется описывать переменную в одном скрипте и назначать (перетаскивать вручную) ее с помощью редактора, что само по себе нудная работа, которую легко можно забыть сделать и потом долго искать какая из переменных не инициализирована.
Если мы захотим что-то отрефакторить, переименовать скрипт, то все старые инициализации в иерархии объектов, связанные с переименованным скриптом, сбросятся и придется их назначать снова.
В то же время, такой механизм не работает для префабов (prefab) — динамического создания объектов из шаблона. Если какому-либо префабу нужно обращаться к менеджеру, расположенному в иерархии объектов, то вы не сможете назначить самому префабу элемент из иерархии, а придется сначала создать объект из префаба и после этого программно присвоить экземпляр менеджера переменной только что созданного объекта. Не нужная работа, не нужный код, дополнительная связанность.
Следующий подход решает все эти проблемы.

Подход 2. «Синглтоны»


Применим упрощенную классификацию возможных скриптов, которые используются при создании игры. Первый тип скриптов: «скрипты-менеджеры», второй: «скрипты-игровые-объекты».
Основное отличие одних от других в том, что «скрипты-менеджеры» всегда имеют единственный экземпляр в игре, в то время как «скрипты-игровые-объекты» могут иметь количество экземпляров больше единицы.

Примеры


Как правило, в единственном экземпляре существуют скрипты, отвечающие за общую логику пользовательского интерфейса, за проигрывание музыки, за отслеживание условий завершения уровня, за управление системой заданий, за отображение спецэффектов и так далее.
В то же время, скрипты игровых объектов существуют в большом количестве экземпляров: каждая птичка из «Angry Birds» управляется экземпляром скрипта птички со своим уникальным состоянием; для любого юнита в стратегии создается экземпляр скрипта юнита, содержащий его текущее количество жизней, позицию на поле и личную цель; поведение пяти разных иконок обеспечивается различными экземплярами одних и тех же скриптов, отвечающих за это поведение.
В примере из предыдущего шага скрипты HUDManager и ScoresManager всегда существуют в единственном экземпляре. Для их взаимодействия друг с другом применим паттерн «синглтон» (Singleton, он же одиночка).
В классе ScoresManager опишем статическое свойство типа ScoresManager, в котором будет храниться единственный экземпляр менеджера очков:

public class ScoresManager : MonoBehaviour
{
	public static ScoresManager Instance { get; private set; }
	public int Scores;
}

Осталось инициализировать свойство Instance экземпляром класса, который создает среда Unity3D. Так как ScoresManager наследник MonoBehaviour, то он участвует в жизненном цикле всех активных скриптов в сцене и во время инициализации скрипта у него вызывается метод Awake. В этот метод мы и поместить код инициализации свойства Instance:

public class ScoresManager : MonoBehaviour
{
	public static ScoresManager Instance { get; private set; }
	public int Scores;

	public void Awake()
	{
		Instance = this;
	}
}

После чего, использовать ScoresManager из других скриптов можно следующим образом:

public class HUDManager : MonoBehaviour
{	
	public void Update ()
	{
		ShowScores(ScoresManager.Instance.Scores);
	}
}

Теперь нет необходимости в HUDManager описывать поле типа ScoresManager и назначать его в редакторе Unity3D, любой «скрипт-менеджер» может предоставлять доступ к себе через статическое свойство Instance, которое будет инициализировать в функции Awake.

Плюсы


— нет необходимости описывать поле скрипта и назначать его через редактор Unity3D.
— можно смело рефакторить код, если что и отвалится, то компилятор даст знать.
— к другим «скриптам-менеджерам» теперь можно обращаться из префабов, через свойство Instance.

Минусы


— подход обеспечивает доступ только к «скриптам-менеджерам», существующим в единственном экземпляре.
— сильная связанность.
На последнем «минусе» остановимся подробнее.
Пусть мы разрабатываем игру, в которой есть персонажи (unit) и эти персонажи могут погибать (die).
Где-то находится участок кода, который проверяет не погиб ли наш персонаж:

public class Unit : MonoBehaviour
{
	public int LifePoints;

	public void TakeDamage(int damage)
	{
		LifePoints -= damage;
		if (LifePoints <= 0)
			Die();
	}
}

Каким образом игра может отреагировать на смерть персонажа? Множеством разнообразных реакций! Приведу несколько вариантов:
— надо удалить персонажа из сцены игры, чтобы он больше не отображался на ней.
— в игре начисляются очки за каждого погибшего персонажа, нужно их начислить и обновить значение на экране.
— на специальной панели отображаются все персонажи в игре, где мы можем выбрать конкретного персонажа. При смерти персонажа, нам нужно обновить панель, либо убрать персонажа с нее, либо отобразить что он мертв.
— нужно проиграть звуковой эффект смерти персонажа.
— нужно проиграть визуальный эффект смерти персонажа (взрыв, брызги крови).
— система достижений игры имеет достижение, которое считает общее число убитых персонажей за все время. Нужно добавить к счетчику только что умершего персонажа.
— система аналитики игры отправляет на внешний сервер факт смерти персонажа, нам этот факт важен для отслеживания прогресса игрока.
Учитывая все вышеперечисленное, функция Die может выглядеть следующим образом:

private void Die()
{
	DeleteFromScene();
	ScoresManager.Instance.OnUnitDied(this);
	LevelConditionManager.Instance.OnUnitDied(this);
	UnitsPanel.Instance.RemoveUnit(this);
	SoundsManager.Instance.PlayUnitDieSound();
	EffectsManager.Instance.PlaySmallExplosion();
	AchivementsManager.Instance.OnUnitDied(this);
	AnaliticsManager.Instance.SendUnitDiedEvent(this);
}

Получается, что персонаж после совей смерти должен разослать всем компонентам, которые в ней заинтересованы этот печальный факт, он должен знать о существовании этих компонентов и должен знать, что они им интересуются. Не слишком ли много знаний, для маленького юнита?
Так как игра, по логике, очень связанная структура, то и события происходящие в других компонентах интересуют третьи, юнит тут ничем не особенный.
Примеры таких событий (далеко не все):
— Условие прохождение уровня зависит от количества набранных очков, набрали 1000 очков – прошли уровень (LevelConditionManager связан с ScoresManager).
— Когда набираем 500 очков, достигаем важную стадию прохождения уровня, нужно проиграть веселую мелодию и визуальный эффект (ScoresManager связан с EffectsManager и SoundsManager).
— Когда персонаж восстанавливает здоровье, нужно проиграть эффект лечения над картинкой персонажа в панели персонажа (UnitsPanel связан с EffectsManager).
— и так далее.
В результате таких связей мы приходим к картине похожей на следующую, где все про всех все знают:

image

Пример со смертью персонажа немного преувеличен, сообщать о смерти (или другом событии) шести разным компонентам не так часто приходится. Но варианты, когда при каком-то событии в игре, функция, в которой произошло событие, сообщает об этом 2-3 другим компонентам встречается сплошь и рядом по всему коду.
Следующий подход пытается решает эту проблему.

Подход 3. Мировой эфир (Event Aggregator)


Введем специальный компонент «EventAggregator», основная функция которого хранить список событий, происходящих в игре.
Событие в игре — это функционал, предоставляющий любому другому компоненту возможность как подписаться на себя, так и опубликовать факт совершения этого события. Реализация функционала события может быть любой на вкус разработчика, можно использовать стандартные решения языка или написать свою реализацию.
Пример простой реализации события из прошлого примера (о смерти юнита):

public class UnitDiedEvent
{
private readonly List<Action<Unit>> _callbacks = new List<Action<Unit>>(); 

public void Subscribe(Action<Unit> callback)
{
_callbacks.Add(callback);
}

public void Publish(Unit unit)
{
foreach (Action<Unit> callback in _callbacks)
callback(unit);
}
}

Добавляем это событие в «EventAggregator»:

public class EventAggregator
{
        public static UnitDiedEvent UnitDied;
}

Теперь, функция Die из предыдущего примера с восемью строчками преобразуется в функцию с одной строчкой кода. Нам нет необходимости сообщать о том, что юнит умер всем заинтересованным компонентам и знать о этих заинтересованных. Мы просто публикуем факт свершения события:

private void Die()
{
EventAggregator.UnitDied.Publish(this);
}

А любой компонент, которому интересно это событие, может отреагировать на него следующим образом (на примере менеджера отвечающего за количество набранных очков):

public class ScoresManager : MonoBehaviour
{
public int Scores;

	public void Awake()
	{
		EventAggregator.UnitDied.Subscribe(OnUnitDied);
	}

	private void OnUnitDied(Unit unit)
	{
		Scores += CalculateScores(unit);
	}	
}

В функции Awake менеджер подписывается на событие и передает делегат, отвечающий за обработку этого события. Сам же обработчик события, принимает в качестве параметра экземпляр умершего юнита и добавляет количество очков в зависимости от типа этого юнита.
Таким же образом, все другие компоненты, кому интересно событие смерти юнита, могут подписаться на него и обработать, когда событие произойдет.
В результате, диаграмма связей между компонентами, когда каждая компонента знала друг о друге, превращается в диаграмму, когда компоненты знают только о событиях, которые происходят в игре (только о интересующих их событиях), но им все равно, от куда эти события пришли. Новая диаграмма будет выглядеть следующим образом:

image

Я же люблю другую интерпретацию: представьте, что прямоугольник «EventAggregator» растянулся во все стороны и захватил внутрь себя все остальные прямоугольники, превратившись в границы мира. В моей голове, на этой диаграмме «EventAggregator» вообще отсутствует. «EventAggregator» это просто мир игры, некий «игровой эфир», куда различные части игры кричат «Эй, народ! Юнит такой-то умер!», и все прослушивают эфир и если какое-то из услышанных событий их заинтересует, они на него отреагируют. Таким образом — связей нет, каждый компонент независим.
Если я компонент и отвечаю за публикацию какого-то события, то я кричу в эфир мол этот умер, этот получил уровень, снаряд врезался в танк. И мне наплевать интересно кому-нибудь об этом. Возможно, никто не слушает это событие сейчас, а может на него подписана сотня других объектов. Меня, как автора события, это ни грамма не волнует, я про них ничего не знаю и знать не хочу.
Такой подход позволяет легко вводить новый функционал без изменения старого. Допустим, в готовую игру мы решили добавить систему достижений. Мы создаем новую компоненту системы достижений и подписываемся на все интересующие нас события. Никакой другой код не меняется. Не надо ходить по другим компонентам и из них вызывать систему достижений и говорить ей мол и мое событие посчитай пожалуйста. К тому же, все кто публикуют события в мире ничего не знают о системе достижений, даже о факте ее существования.

Замечание


Говоря, что никакой другой код не меняется, я конечно немножко лукавлю. Может оказаться так, что систему достижений интересуют события, которые ранее просто не публиковались в игре, потому как ни одну другую систему до этого не интересовали. И в этом случае, нам нужно будет решить какие новые события добавить в игру и кто будет их публиковать. Но в идеальной игре уже все возможные события есть и эфир наполнен ими по полной.

Плюсы


— не связанность компонентов, мне достаточно просто опубликовать событие, а кого оно интересует не имеет значение.
— не связанность компонентов, я просто подписываюсь на нужные мне события.
— можно добавлять отдельные модули без изменения в существующем функционале.

Минусы


— нужно постоянно описывать новые события и добавлять их в мир.
— нарушение функциональной атомарности.

Последний минус рассмотрим более детально


Представим, что у нас есть объект «ObjectA», в котором вызывается метод «MethodA». Метод «MethodA», состоит из трех шагов и вызывает внутри себя три других метода, которые выполняют эти шаги последовательно («MethodA1», «MethodA2» и «MethodA3»). Во втором методе «MethodA2» происходит публикация какого-то события. И тут происходит следующее: все кто подписан на это событие начнут его обрабатывать, выполняя какую-то свою логику. В этой логике тоже может произойти публикация других событий, обработка которых также может привести к публикации новых событий и так далее. Дерево публикаций и реакции в отдельных случаях может очень сильно разрастись. Такие длинные цепочки крайне тяжело отлаживать.
Но самая страшная проблема, которая тут может произойти, это когда одна из веток цепочки приводит обратно в «ObjectA» и начинает обрабатывать событие путем вызова какого-то другого метода «MethodB». Получается, что метод «MethodA» у нас еще не выполнил все шаги, так как был прерван на втором шаге, и содержит сейчас в себе не валидное состояние (в шаге 1 и 2 мы изменили состояние объекта, но последнее изменение из шага 3 еще не сделали) и при этом начинается выполняться «MethodB» в этом же объекте, имея это не валидное состояние. Такие ситуации порождают ошибки, очень сложно отлавливаются, приводят к тому, что надо контролировать порядок вызова методов и публикации событий, когда по логике этого делать нет необходимости и вводят дополнительную сложность, которую хотелось бы избежать.

Решение


Решить описанную проблему не сложно, достаточно добавить функционал отложенной реакции на событие. В качестве простой реализации такого функционала мы можем завести хранилище, в которое будем складывать произошедшие события. Когда событие произошло, мы не выполняем его немедленно, а просто сохраняем где-то у себя. И в момент наступления очереди выполнения функционала какой-то компоненты в игре (в методе Update, например) мы проверяем на наличие произошедших событий и выполняем обработку, если есть такие события.
Таким образом, при выполнении метода «MethodA» не происходит его прерывание, а опубликованное событие все заинтересованные записывают себе в специальное хранилище. И только после того как к заинтересованным подписчикам дойдет очередь, они достанут из хранилища событие и обработают его. В этот момент весь «MethodA» будет завершен и «ObjectA» будет иметь валидное состояние.

Заключение


Компьютерная игра это сложная структура с большим количеством компонентов, которые тесно взаимодействуют друг с другом. Можно придумать множество механизмов организации этого взаимодействия, я же предпочитаю механизм, описанный мною, основанный на событиях и к которому я пришел эволюционным путем прохода по всевозможным граблям. Надеюсь кому-нибудь он тоже понравится и моя статья внесет ясность и будет полезной.
Олег Андрушко @nubick
карма
7,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (34)

  • 0
    Я может не увидел по тексту… но зачем синглтон скрещивать с компонентом (MonoBehavior)?
    «Мухи отдельно, а котлеты отдельно.»
    Чем вам чем вас обычный мультипоточный синглтон не угодил? Положили его в отдельный каталог (напр., в Helpers, как я это сделал), добавили пространство имен в скрипт и используйте на здоровье.
    • 0
      >> Singleton -> Минусы
      Добавьте тогда уж, что будет, если этот скрипт через редактор навесят на несколько объектов.
      Только не говорите нечто типа «Юзер так делать не будет». Это человеческий фактор.
      • 0
        Этот факт можно легко отследить добавив в функцию инициализации синглтона проверку на то не был ли он инициализирован ранее и выводить предупреждение в дебаг, к примеру. Таким образом можно немного обезопасить себя от ошибок невнимательности.
        • 0
          Я неуверен, что там под капотом у Unity3D с многопоточностью, но возможно, что MonoBehavior инициализируются асинхронно (быть може на пулле потоков). Тогда есть вероятность, что такой вот синглтон проинициализируется дважды, трижды, стопиццотжды…

          >> Этот факт можно легко отследить добавив в функцию инициализации синглтона проверку на то не был ли он инициализирован ранее и выводить предупреждение в дебаг

          Вопрос мой, собственно, в том и состоит зачем тогда его вообще наследовать от MonoBehavior? Чтобы потом вот такие костыли наворачивать? Я, видимо, профит некий упускаю из внимания.
          • 0
            Я неуверен, что там под капотом у Unity3D с многопоточностью, но возможно, что MonoBehavior инициализируются асинхронно (быть може на пулле потоков). Тогда есть вероятность, что такой вот синглтон проинициализируется дважды, трижды, стопиццотжды…

            Нет, жизненый цикл MonoBehaviour гарантирует, что методы Awake и Start вызываются в конкретное время и один раз. С жизненым циклом строго и на нем логика игры строится.

            Вопрос мой, собственно, в том и состоит зачем тогда его вообще наследовать от MonoBehavior? Чтобы потом вот такие костыли наворачивать? Я, видимо, профит некий упускаю из внимания.

            Потому что изначально у нас есть MonoBehaviour. Если мы не будем наследовать класс от него, то класс не будет являться скриптом и не сможет быть добавлен в объект на сцену.
            • 0
              >Нет, жизненый цикл MonoBehaviour гарантирует, что методы Awake и Start вызываются в конкретное время и один раз. С жизненым циклом строго и на нем логика игры строится.

              Метод Awake и Start вызываются один раз, но не в конкретное время, только когда объект активирован. И никто Вам не гарантирует последовательность их вызовов в различных объектах. Если будет ситуация, когда один объект обращается к другом при инициализации, а того еще нет, придется городить костыли с проверками.

              > Потому что изначально у нас есть MonoBehaviour. Если мы не будем наследовать класс от него, то класс не будет являться скриптом и не сможет быть добавлен в объект на сцену.

              Я не говорю, что не нужны системы сообщений, синглтоны. Это не нужно в Юнити. Идеология другая. Будете писать движок, — тогда эти вещи пригодятся. Да и Юнити не для больших проектов.
              new НекоторыйКласс, не? Вполне работает и не нужно быть для этого наследником MonoBehaviour. Для синглтона в самый раз.

              По поводу эфиров сообщений, синглтонов.

              Юнити предоставляет удобную компонентную систему, где перетаскиванием объекта в иерархии, можно сэкономить кучу времени и решить задачу максимально красивым и приемлемым способом для Юнити.

              Синглтон — дурной тон. Им пользуются, когда не знают как решить задачу иначе. Вы пишите код для самоподдержки некоторого объекта, контролирующего единственность своего экземпляра. А что будет, когда нужно будет иметь 2 таких объекта, решите сделать свою игру сетевой, например?
              Если хотите сами не путаться в нагромождениях вызовов зависимостей, пишите код так, чтоб объекты минимально знали о существовании других.

              SomeClass().Instance().var — вот только в Юнити этого не надо, да и нигде не надо. Если уж не обойтись без синглтона, то прячьте его за статической функцией. Не нужно никому знать, что объект синглтон.

              Зачем система сообщений, на отладку которой вы убьете кучу времени, которая будет делать кучу вызовов каждый фрейм, когда количество объектов, которым нужно знать о событии ограничено. Это не win32api. Да и с учетом байткодовости всех таких систем, они будут не особо эффективны, в отличии от того же win32api.

              Юнити решил все задачи за Вас и дал Вам среду, где максимально быстро и красиво можно решить задачу. Вся низкоуровневая работа сделана разработчиками Юнити на C++.
              Может Вы еще будете из текстовых файлов грузить конфиги? У меня были когда-то такие мысли, но убедился, что ту же конфигурацию намного быстрее забить в объект на сцене или префаб в удобной среде, чем редактировать файлы.

              Пользуйтесь тем, что Вам дал Юнити, и не давайте вредных советов. Если Вашу статью кто-то сочтет авторитетной, то он пойдет по ложному пути. Вы сами это осознаете, и людей за собой поведете.
              • 0
                Я прочитал ваш комментарий, но не хочу отвечать на такой тон.
                Ступайте с миром!
                • 0
                  Извините меня. Я хотел бы выслушать ваши аргументы. Просто вижу те же самые грабли, которые встретил сам, наступил много раз, и пошел дальше. Но я не пытался свои заблуждения выставлять в роль руководства.
                  • +1
                    Агрументы по поводу чего?
                    Утверждение, что синглтоны дурной звучит для меня странно и как-то категорично. Подозреваю тут дело вкуса, но вкус обсужать не хочу, да и статье не про синглтоны.

                    Задайте конкретный вопрос, если что-то интересует.
                    • 0
                      Познакомьтесь с паттернами проектирования. Тогда вы сможете разглядеть их реализацию в Юнити. И тогда поймете, как в Юнити можно получить самый простой, лаконичный и эффективный результат. Существует не только синглтон как паттерн. Есть мнение по поводу частого использования синглтонов, я его тоже разделяю, так как убедился в минусах подобного подхода на своем опыте. Предложенные Вами методы достаточно сложны для парадигмы Юнити, так как задачи можно решить проще, затратив меньше времени на реализацию, отладку и внедрение предлагаемых вами методов.

                      К чему я это все, я не имел целью Вас оскорблять своим тоном. Вы сами выбираете, будет у Вас проект жизни, которые рискует быть не законченным, либо у Вас будет готовая игра.
                      • 0
                        Опишите свой вариант, пожалуйста.

                        Я вот сейчас только начинаю разбираться с Unity и столкнулся с уже упоминавшейся в комментариях проблемой — подавляющее большинство уроков, туториалов и книг идут по одной схеме — вот мы нарисовали почву, вот мы натыкали деревья и покрасили травку (в книгах главы две могут этим заниматься), вот камера от первого лица… А у меня, например, в мыслях прототип изометрии с непрямым управлением, и мне надо как-то хранить информацию о массивах тайлов пола и стен, отслеживать клики по ним, вести список задач, давать юнитам забирать какую-то задачу себе и при этом не давать другим юнитам в параллели брать задачу, которую кто-то уже занял, но не успел обновить статус задачи. И я пока не могу найти нормального описания реализации подобного взаимодействия.
                        • 0
                          Начните с более простых проектов, которые точно удастся завершить. По ходу дела станет понятно, какие возможности дает Unity, и что Вы можете организовать на их основе. Сможете поиграться с различными вариантами ваших задумок.

                          Большинство проектов, как известно, не доходят до конца. Да и с каждым проектом будет больше опыта. Сразу не бросайтесь на сложные задачи. Их решение займет много времени. Заодно выработаете свои персональные привычки организации работы в Юнити. Разберите код готовых проектов опытных программистов. Потом через некоторое время будете с удивлением смотреть на свои велосипеды.

                          А в остальном — обычное программирование. Применимы паттерны и все проверенные временем подходы. Тут уже к среде нет привязки. C# — такой же C# как у MS и не все должно быть MonoBehaviour'ом. Тем более, если некоторым объектам не нужно находиться в мировом пространстве, то им необязательно быть потомками MonoBehaviour'а и получать Update каждый кадр.

                          Легко можете применить DI, организовать систему сообщений. Слабое связывание позволит переносить наработки между проектами. Задачи повторяются.

                          Что касается вашего вопроса, то тут не должно быть привязки к среде, языку. Это вопрос архитектуры. И зачастую более сложный вопрос, чем набросать GameObject'ов со скриптами на сцену.

                          Если ваше мышление не связывает конкретный язык программирования, то ответ надо искать в других местах. Где конкретно тема обсуждается. Можете взглянуть на проекты Soomla, которые, возможно, удовлетворят ваши потребности и легко интегрируются в Unity. Имеют открытый код.

                          Выбирая между «правильным программированием» и законченным проектом, стоит не упускать из виду возможности Unity. Например, новый UI, позволяет очень быстро на сцену набросать GUI и HUD. Отличный проект и законченный проект не всегда одно и то же. Лучше иметь в магазинах игру, которая приносит деньги, чем не иметь.
              • 0
                SomeClass().Instance().var — вот только в Юнити этого не надо, да и нигде не надо. Если уж не обойтись без синглтона, то прячьте его за статической функцией. Не нужно никому знать, что объект синглтон.


                Статические функции увеличивают связность, а передача объектов — уменьшает. В идеале («если уж не обойтись без синглтона»), зависимость от синглтона должна пробрасываться в параметрах конструктора или функций настройки. То есть клиентский код использует синглтон как `_instance.SomeMethod()`, не зная, что _instance — синглтон. Об этом будет знать только внешний код, где осуществляется настройка: `var x = new Client(SomeClass.Instance)`. При необходимости, можно абстрагировать синглтон за интерфейсом, тогда появится возможность протаскивать в клиенты в поле _instance не сам синглтон (логгер, подсистему звуковых эффектов, etc.), а его моки или обёртки. А зависимость от статической функции (а-ля service locator) жёсткая, тяжело отторгается.
              • 0
                а почему 32api? что в 64х разрядной уже ушли от очереди сообшений? Челове всё правильно правильно описал, в более мение крупном проекте система событий будет хорошим решением.
        • 0
          Неужели нет инструмента от разработчиков Unity3D, VS или ReSharper, который позволяет при переименовании/перемещении файла скрипта или его перемещении сохранить связи этого скрипта с объектами, которым он назначен? Ведь весь секрет в том, чтобы при переименовании/перемещении скрипта переименовать/переместить соответственно и одноименный *.meta файл, что создается в проекте вместе с самим скриптом. А если такой инструмент есть, то при рефакторинге приложения не должна рушиться его консистентность, а значит и не нужны такие костыли, что описал автор. Откоментируйте меня, если я не прав )
          • 0
            Я не знаю такого инструмента. Было бы здорово, если он появится, решилось бы много проблем.
            Но наличие такого инструмента не решает проблемы, описываемые в статье. Например, не устраняет жесткую связанность между разными частями приложения.
            • 0
              Так как сохранность состояния сцены для меня один из больших приоритетов, то я провел исследование и нашел это плагин:
              www.visualstudio.com/en-us/features/unitytools-vs.aspx

              В связке VisualStudio+UnityPlugin+Resharper я смог наладить рабочий процесс, в котором не приходится постоянно следить за хвостами. Думаю, на сегодняшний день это самое оптимальное рабочее окружение для разработки.

              Буду рад, если вам это поможет.
              • 0
                Спасибо! Отличный плагин!
    • 0
      Я может не увидел по тексту… но зачем синглтон скрещивать с компонентом (MonoBehavior)?

      Потому что в статье рассказывается о способак взаимодействия именно скриптов Unity (наследников MonoBehviour), которые живут в жизненном цикле сцены и могут использовать в своей логике факты вызовов таких функций как (Awake, Start, Update и множество других).
      • 0
        Вы хотите сказать, что скрестили синглтон с MonoBehavior потому, что автор выбрал именно такой заголовок в статье?
        Взаимодействие между скриптами можно было реализовать и без наследования от MonoBehavior.

        Вариант с месседжами снимается
        Можно вообще воспользоваться стандартными средствами Unity3D — пробросом сообщений (напр., SendMessageUpward)
  • 0
    А чем плохи встроенные в юнити события (мессаджи: SendMessage и BroadcastMessage)? Документация клянется, что это работает очень быстро.
    • +1
      Работает только с MonoBehavior, «магические строчки» вместо имени метода не поддающиеся рефакторингу, и ограничение в один параметр. Ну и работает явно медленнее стандартных евентов.
    • 0
      Врёт. У нас были фризы в какие-то моменты времени, искали долго в чём проблема, я потом подумал на SendMessage, переписали без SendMessage — фризы исчезли. Сча очень редко их пользуем.
    • 0
      Плохи производительностью, их использовать — табу.

      Мы пользуемся Messenger:
      gist.github.com/anonymous/8923677
    • 0
      Помимо вышеперечисленного хардкода имен методов, ограничения по параметрам и производительности, возникает неоднозначность в случаях, когда объект, у которого вызывается SendMessage содержит в себе много разных скриптов. Нужно думать, контролировать, а вдруг еще какая-то компонента, кроме предпологаемого получателя содержит в себе метод с таким же именем, что будет в таком случае? А если наш объект содержит несколько копий одного скрипта на себе, но с разными параметрами, что будет в таком случае и как контролировать такие вызовы?
    • 0
      Спасибо всем, кто отписался, очень полезно!
  • 0
    Спасибо за статью, про третий метод не знал, буду впредь использовать.
  • 0
    Спасибо за статью, как раз таких статей по юнити и не хватает, как прикрутить физику, модельки, UI полно, а вот как заставить это все работать вместе — очень мало. А могли бы вы выложить, например на gist, пример реализации пула евентов.
  • 0
    Паттерны проектирования.
    Наблюдатель
    Собственно, проблемы связывания всех компонентов и кода в Unity нет. Проблема в головах тех, кто пытается делать что-то в этом редакторе, не понимая базовых вещей. Отсюда и обвинения в сторону редактора. Отсюда и «дошёл до азов в процессе эволюции». Теория — не ваш враг. Она ваш помощник. И её знание — не трата времени, а сильный инструмент в ваших руках и голове.
  • 0
    Извините, но я не понял, чем ваша реализация событий лучше стандартных событий C#? Вы делаете в принципе то же самое, но в вашей реализации приходиться дополнительно реализовывать Event Aggregator, постоянно пополняя его новыми событиями, а при использовании стандартных событий он не нужен.
    Оставлю, пожалуй, ссылку на официальный урок по событиям от сайта Unity.
    • 0
      Принципиальное отличие от стандартных событий C# в том, что стандартное событие выполняется сразу, а в моей реализации выполнение откладывается до момента, когда MonoBehaviour выполняет свой Update.
  • 0
    Ну а что мешает дополнить событийную модель особенными ивентами которые стартуют команды и\или последовательности команд? то как бы удобное решение проблемы когда надо сделать цепочку действий.
  • 0
    Скажите а где инстанцируется UnitDiedEvent?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.