Pull to refresh

Comments 67

А не думали для этих нужд заюзать ScriptableObject вместо MonoBehavior? Я не гуру Юнити и только недавно начал постигать полезность SO, но по-моему, если нужно сделать что-то, для чего не нужно визуальное отображение (т.е. как раз таки настройки чего-либо), SO подойдут в самый раз.
Тут как раз возникает проблема невозможности редактирования public-полей и констант ScriptableObject без открывания кода. То есть такие классы смогут обрабатывать свою логику и хранить какие-то данные, что удобно — но гейм-дизайнер не сможет их настраивать из редактора Unity.
Можно было бы использовать ScriptableObject для тех объектов, настройкой которых гейм-дизайнер занимать не будет — но тогда у нас будут две практически одинаковые сущности (SO и синглтоны), которые нужно будет поддерживать отдельно — я принял решение упростить эту систему. Может быть, есть решение оптимальнее, но я пока что его не придумал (:
Почему же нельзя редактировать поля без открывания кода? Наследуемся от SO, заводим нужные поля, создаём ассет с этим SO, жмём по ассету, инспектор показывает поля. И не нужно создавать лишние GameObject'ы, которые, например, меня очень сильно раздражают и травмируют мою неокрепшую психику.
Вот, например...

… мои потуги в реализации BehaviorTree — инспектор вполне себе показывает поля SO, который на самом деле вообще внутри другого ассета лежит.
Любопытно. Спасибо, надо покопаться!
А вы лучше напишите статью, как вы рендерите подобные деревья? через UnityEditor, или через GL. Вопрос занимательный весьма)
Напишу, когда доделаю, чтобы работали, собственно, сами деревья =)) А то я начал, и так увлёкся Editor'ом, что всё это работает пока что только в теории.
Но вкратце, ноды рисуются при помощи GUI.Window, а кривые — Handles.DrawBezier. Всё вот так вот просто =)
Ясно, тогда вам будет полезна инфа, что как-то только у вас размеры вылезут за 2-3 экрана подобного дерева, начнутся просто дичайшие лаги. Я потом и спросил, потому что мы сейчас ищем способ кастом рендеринга виджетов, через тот же GL.
Возможно тогда вам следует для своего редактора (если он сложный и лагает) использовать встроенный в юнити хромиум. И создавать свой редактор с помощью веб.
Спасибо за информацию. Значит, меня ожидают те же проблемы. Хотя, может быть мне хватит деревьев такого размера, т.к. никакого сложного AI я делать не собираюсь, и деревья тут только для того, чтобы были )
Визуализация графа хороша только на количестве нод < 10-15, дальше начинается ужас. В результате было решено дропнуть визуализацию и собирать все в коде, как-то так: https://github.com/Leopotam/LeopotamGroupLibraryUnity/blob/master/Assets/LeopotamGroup.Examples/Events/BehaviourTreeTest.cs
Singleton-ы не всегда являются лучшим решением. Как вы смотрите на использование в Unity Dependency Injection?
Например, вот это https://github.com/Disturbing/UnitySuice работает очень неплохо и решает проблему более удобно.
Спасибо, в ближайшее время посмотрю и отпишусь о результатах исследования (:
Начинать использовать DI нужно сразу с чего нибудь классического и проверенного временем, даже в юнити. http://www.ninject.org Отлично работает при компиляции с MONO. По вашей ссылке неизвестный проект с 20 коммитами в год.
Основной проект там вот этот https://github.com/Disturbing/suice
Коммитов мало, но это — урезанный клон google guice
Работает тоже отлично. Я его использовал.
1. Как оно по производительности по сравнению с классическим подходом?
2. Как оно работает с il2cpp?
Специально производительность не замерял. Но заметного эффекта не оказывает.
Про il2cpp могу сказать что под WebGL проект, основанный на Suice собирался и работал без проблем. Значит, наверное, работает.
Если идет интенсивное обращение к методам / свойствам синглтона — без локального кеширования инстанса оно будет работать соизмеримо с вот этим?
Почему нет? Если там и есть какой-то overhead то только в момент создания bean-ов и внедрения зависимостей (то есть — один раз на каждый bean). Потом они точно также существуют и могут использоваться. Или я не понял ваш вопрос?
Я про время инициализации, саму ленивую инициализацию, возможность проверки, что инстанс существует, чтобы не вызывать повторно ленивую загрузку в OnDestroy, например (это вызывает пробивание песочницы и объект может остаться в редакторе после выхода из playmode). Как оно работает с GC, есть ли дополнительные выделения памяти при инициализации? Просто раз пользуетесь, то должны знать об основных особенностях.
Про ленивую инициализацию ничего сказать не могу, поскольку мы пользовались первой версией suice, где все было Eager.
Но даже и в этом случае у нас не возникло желания измерять это время, поскольку заметным оно не было.
Что касается GC, то по крайней мере в первой версии bean-ы жили все время жизни приложения.
Посмотрел реализацию по ссылке возникло несколько вопросов.

1. Что произойдет если кто — то уничтожит объект одиночки и _instanceCreated created будет true?
2. При смене сцен точно произойдет уничтожение объекта, но так как у нас класс статический будет то же, что в 1.
3. Как быть с модульными тестами (с 5.3 они не работают в плей моде)?
4. В методе Awake будут уничтожены все повторные копии, это не приводило к сложно диогностируемым ошибкам?
5. Как боретесь с проблемой утечки памяти, когда кто — то повесил ссылку на одиночку в кеше?
6. Вы используете одиночку не только в контексте наследования (зачем _instance = this as T если этот awake только у прямых потомков и у нас произойдет стандартное неявное приведение ссылочного типа потомка к родителю)?
7. Какой прирост производительности даёт этот способ над сравнением при переопределенной операции ==?
8. Была ли ситуации когда == был BottleNeck?

Заранее спасибо за ответ и уделенное время.)
1. Почему кто-то должен хотеть уничтожать инстанс подсистемы, которая предоставляет shared доступ к какому-то апи / данным?
2. Необязательно, в OnConstruct (который вызывается только у одного валидного инстанса и один раз) можно сделать DontDestroyOnLoad(gameObject), например, как тут: ScreenManager
3. Для тестов придется прогонять их в рантайме в отдельной сцене — ведь тестировать нужно практически всегда рантайм-апи / данные, а не редактор.
4. Awake нельзя использовать, вместо него нужно всю инициализацию перенести в OnConstruct. Так же запрещено использовать доступ к синглтону из Awake внешних компонентов (делать все в OnEnable / Start и тп). Если следовать этим правилам — волосы будут мягкими и шелковистыми.
5. Что значит кто-то повесил в кеше? Кеш нужен очень редко, доступ достаточно оптимизирован. Используется только как кеш внутри одного метода при массовых использованиях апи синглтона, но тут тоже нет проблемы — оно все одноптоточно и в принципе не сможет умереть до конца метода.
6. Не совсем понял вопрос. Синглтон можно повесить как обычный компонент в редакторе и настроить его свойства (дизы танцуют и поют) — после запуска playmode выживет только один (если их будет много) и только у него отработает OnConstruct.
7. 2х, подобный подход используется для патча трансформа вот тут, тут можно погонять глупый, но какой-никакой тест
8. Оно зависит от использования — иногда требуется максимально снизить нагрузку на множественное использование апи синглтона без локального кеширования. Ну и написать один раз и потом лениво копировать код по проектам :)
Кстати по поводу ссылки в 7 Transform это класс, думаю можно убрать дополнительную переменную флаг.
Время доступа к Transform значительно превысит операции вида ((System.Object)transform == null).
Точнее сказать можно по результатам теста.
Не превысит — transform — это Getter со всеми вытекающими (поиск по компонентам внутри судя по времени отработки).
З.Ы. Понял про что речь, сравню, отпишусь
Да я это и говорю, что операция == для System.Object (я сделал явное приведение) значительно быстрее чем метод Get и вызов кода движка. Поэтому можно убрать дополнительную переменную и так код будет читабельные.

Ну это мое скромное мнение)
Сравнил — результаты соизмеримы с флаговой реализацией в пределах погрешности, откатил флаг на кастинг.
Еще забыл.

9. Что будет если кто — то попросит ссылку на одиночку в своем Awake до того как там пропишется наш объект?
10. После присваивание в Awake мы не ставим флаг создания ссылки в true, у нас будет повторный поиск объекта на сцене?

Еще раз спасибо за уделенное время.
Большинство вопросов сводится, а если 10 — й программист с недосыпа нахалтурит)
Кстати бывает часто, а ловить такие ошибки бывает сложно.

1. Специально уничтожать его ни кто не будет. Просто это может привести к сложно диагностируемой ошибке.
2. Да я знаю DontDestroyOnLoad(gameObject). Но в этой реализации его нет и я бы предпочел возлагать такую ответственность не на программиста реализующего наследника, а на себя.
3. По возможности я стараюсь обходится минимум тестов раинтайма, это медленнее и черновато ошибками.
Модульное тестирование на сцене в основном требуется для объектов с физикой и анимацией и редко когда встречал другие случаи. Но за это я плачу увеличением кода и тд.
4. Я имел ввиду если у нас несколько одинаковых объектов добавлены на сцену по ошибке)
5. Если реализация это позволяет, а проект большой как показывает практика это происходит.
6. Просто если мы используем подход class Test = Signaleton <>, то у нас нет необходимости _instance = this as T,
а можно _instance = this.
7. Спасибо огромное ознакомлюсь. Если сильно важна производительность можно делать и без Одиночки.
Статик классы и ScriptableObjects могут гарантировать одну ссылку.
Конечно все зависит от задач все сделать и есть вещи которые они не покрывают.
8. Кстати под подобные вещи сделал отдельную библиотеку, чтоб при копировании не ошибаться)
9. Я имел ввиду ситуацию когда объект не наследник одиночки, а просто Mono в своём Awake затребует ссылку.
Конечно можно запретить это делать всем, но кто — то обязательно не удержится).
Например у нас менеджер звука и один объект на сцене хочет взять его параметры в Awake вместо Start.
10. Я имел в виду у нас добавлен на сцену один объект наследник Одиночки.
Он запускает Awake определенный вами и прописывает ссылку в _instance.
Объект на сцене обращается к методу get instance.
Так как флаг _instanceCreated не поставлен в true происходит тяжелый поиск на сцене по типу.

Огромное спасибо за ответ.
1. Если оно умрет, то при следующем обращении будет создано снова. Вообще, можно потратить какое-то время, найти виноватого и жестоко покарать его, например, спиливанием пары зубов напильником. Синглтон подразумевает что его никто не создает и никто не убивает, он просто есть. Для принудительного создания есть пустой метод Validate, который служит исключительно для прогрева инстанса.
2. Так это же абстрактный класс, он сам из себя ничего не представляет, чтобы что-то дергать в синглтоне — это нужно реализовать в наследнике и запечатать его если потребуется. Ну и тут получается свобода — можно делать локальные для сцены синглтоны, а можно глобальные для всех сцен. Каждая реализация должна решить как она себя должна вести. Флаг глобального инстанса кстати был в одной из промежуточных версий, но потом был выпилен за ненадобностью и проблемами с поведением в дальнейшем.
4. Ну они все помрут и останется только один, как горец. У него будет вызван OnConstruct. Есть одна тонкость — синглтоны должны висеть на отдельных ГО — если один из них придумает помирать, то утянет весь ГО с собой, а при множественных инстансах повешении нескольких компонентов на один ГО это чревато. Но это уже из области стиля сборки кубиков логики в сцене — должна быть договоренность и правила в команде.
5. Кеширования быть не должно — написать на обоях кровью джуниора, чтобы остальные устрашились.
6. Так это UnitySingleton, исключительно для компонентов MonoBehaviour, для pure-C# классов оно пишется вообще как «static readonly T Instance = new T();» свойство без всего прочего мусора и работает как надо.
7. Если нужна реакция на события MonoBehaviour — так просто не обойтись, а тут получается вполне просто и без дураков.
9. см п.1.
10. Да, фейл, поправлено.
2. Кстати для локальных и глобальных одиночек у меня два разных базовых класса.
В моей абстракции тут нет связи является.
5. В этом пункте возможен фаил при использовании стороннего кода)
6. Кончено реализация Pure c# мне известна, я просто говорю, что нет случая когда неявное приведение вызовет исключение и в 51 строчке класса одиночке не требуется использовать оператор as.
9. Да помрет. Но может привести к ошибочным данным по умолчанию в классе вызвавшем ссылку при инициализации в Awake. А поиск ошибки виновного после 20к кода дело не простое.
5. Так сторонний код должен же как-то узнать про синглтон, те быть интегрирован в текущий код, а значит это можно сделать правильно.
6. Все-равно не совсем понял, про что речь. Приведение типа требует компилятор, причем мягкого.
9. Так чтобы оно произошло — должны произойти 2 вещи: убийство должно быть жестоким (через DestroyImmediate, потому что Destroy вызывается после текущего фрейма чтобы все отработало как есть) и должен быть локальный кеш во внешнем коде, чего быть не должно. Если разраб настолько глуп, что может совместить эти 2 условия, то ССЗБ, идеального антивандального решения со всеми фишками событий MonoBehaviour просто не существует.
6. Согласен мой косяк вычислял в голове забыл один пункт приведения обобщенных типов все верно прошу прощения.)
9. Я имею ввиду другое допустим у нас есть класс ScoreCollector на сцене он одиночка и подсчитывает очки.
В него сериализуемы настройки конкретного уровня по стоимости и тд.
Есть класс которому для начала работы нужны данные из одиночки в Awake.
Когда создается объект вызывается его метод Awake и порядок их вызова не определен (если не указать, что есть зло).
Он запросит данные раньше чем объект присутствующий на сцене пропишет себя (порядок вызова Awake не определен) и будет создан объект одиночки с данными по умолчанию.
Объект получает неправильные данные по умолчанию и работает некорректно.
Далее исходный объект одиночка чистит ссылку.
А программисты ищут несколько часов в чем фаил и только потом задумаются об одиночке.

Если что пример надуманный важна его суть.)

Ну если есть работа из Awake, то это уже должно наводить на мысль где фейл. Если это реально требуется (а это практически всегда не так, можно задействовать OnEnable, например), то можно в OnConstruct синглтона делать подгрузку данных из какого-то стора (csv, префаба, SO, любого ассета) и убирать его из сцены вообще — он лениво загрузится из кода.
З.Ы. Что еще не так с этим кодом? :)
Остальное чисто эстетические соображения, больше связанные с конкретными решениями задач и архитектурой))
В любом случае эта реализация нравится гораздо больше чем у автора статьи.
9. По этой причине я почти полностью отказался от варианта создания ссылки если исходный объект не найден.
Либо объект есть на сцене либо это ошибка. Это решает почти все проблемы которые рассмотрены нами.
Кончено если нам не требуется отложенная инициализация, что как я думаю бывает редко.
Как раз ленивая загрузка — одна из самых востребованных, тк дизам проще дать крутить циферки в гуглодоках и потом их забирать в проект + крутить циферки в префабах + редко когда напрямую в сцене, а все остальное делать лениво из кода — сцены получаются легкими как по весу, так и по diff-у в репе.
Да но для таких случаев я не использую mono signaleton а предпочитаю другие реализации.
Это только пока не нужны эвенты MonoBehaviour — без них всегда рациональнее использовать pure C#. Этот костыль был выстроган исключительно для обертки над MonoBehaviour.
Да да да. Поэтому всегда стараюсь разделять по возможности хранение данных и сигналетоны исполнители.
Просто когда вижу недоработанные сигналетоны где попало это вгоняет в уныние. А многие копируют код из статей.

Спасибо за продуктивную дискуссию.
Есть еще одна неуказанная неоднозначность — что будет если в OnDisable / OnDestroy пощупать апи синглтона допустим для отписки? Из-за неопределенности порядка вызовов это становится большой проблемой — синглтон к этому времени может самоубиться и при щупании его инстанса произойдет ленивая загрузка — инстанс создастся по-новой, чего юнити категорически не любит и что вызывает пробивает песочницы редактора — инстанс может пережить остановку и остаться в сцене как полноценный объект (багу минимум 3 года, никто не торопится править). Поэтому существует статик метод проверки существования инстанса, в вызов которого нужно оборачивать все обращения к синглтону в OnDisable / OnDestroy:
if (NetworkManager.IsInstanceCreated ()) {
    NetworkManager.Instance.OnConnectionClosed -= OnNetworkDisconnected;
}
Хочу добавить некоторые замечания.

1.Проверка ссылки на null в юнити дял UnityEngine.Object перегружена и вернет будет true даже если объект существует.
Если не брать это во внимание возможна утечка памяти (удалили одиночку, создали, а ссылки повисли в других объектах).

2. Если использовать Unity Test Tools с версии 5.3, то тесты проводятся на той же сцене,
что открыта в текущий момент и возможны серьезные проблемы при тестировании.

3. Для статических объектов геим дизайнеру совсем не обязательно лезть в код. Все зависит от того, как будут храниться данные.
Можно сделать отдельный сериализуемый класс данных и EditorWindow с любыми изысками в GUI. И по моему мнению для работы с подсистемами Editor Windows более удобны (не нужен лишний хлам на сценах).

4. В данной реализации Одиночки дважды запускается тяжелые функции FindObjectsOfType(typeof(T)) и FindObjectOfType(typeof(T)),
хотя достаточно первой из них.

5. Использовать в Одиночке Awake таким образом очень опасно.
Если порядок вызова Awake не определен, то кто — то может запросить Одиночку,
до того как там пропишется объект со сцены => гарантированный еррор.
Дочерний класс может захотеть иметь свой Awake.
Код получения ссылки дублирует код Awake.
Достаточно вместо тяжелого GetComponent написать this.

6. В качестве базы для одиночки всегда следует рассмотреть ScriptableObject. Получить две копии одного и того же объекта по моей памяти невозможно + он не тянет хлам в виде трансформа и тд.

Спасибо за внимание)
Огромное спасибо, обязательно всё это учту!
Не думаю, что буду серьёзно менять имеющуюся архитектуру в текущем проекте, но переписать что-то для последующих очень вероятно (:
.Проверка ссылки на null в юнити дял UnityEngine.Object перегружена и вернет будет true

Это справедливо только для редактора, для того, чтобы иметь возможность в Unity проверять ссылки на их объекты. В режиме билда, будет самый null. Это известный прикол уже)
Ну статья про то, как сделать работу геим дизанера проще, я думаю он работает в редакторе.
А не очевидная логика работы в редакторе прилагается 100 процентов.
Если быть точным я говорил только про случай когда на наш сигналетон есть ссылки из других объектов.

Еще раз проверил этот факт в юнити 5.4 и создал два скрипта Mono.
Первый с полем public MonoBehaviour testObject и Destroy(testObject) по нажатию кнопки.
После чего в билде смотрел вывод до и после уничтожения выражений

testObject == null
(System.Object)testObject == null и
!testObject.

До уничтожения объекта вывод false false false.
После удаления вывод true false true.
Так что все сказанное о сторонних ссылках и проверке верно и для билда. Юнити уничтожает внутреннее представление объекта из пула,
а так как у нас есть ссылка на объект GC не помечает объект подлежащий сбору мусора. В результате имеем мемори лик.
И это справедливо не только для редактора.

Если вы это имели ввиду.
Не это, то что вы описали верно, но в моем понимании синглетон которые доступен через Instance не должен уничтожаться в принципе за время жизни приложения.
К сожалению данная реализация ни как не защищает от удаления.
А значит это произойдет по чей — то ошибке.
При закрытии приложения объекты будут уничтожаться в произвольном порядке.
И тут при данной реализации можно получить опять не предсказуемую работу приложения.
Не за что.
Если будет возможность поправите код.
И хорошо будет отметить, что код if (!_instance) равноправен (_instance == null) с теми же оговорками о перегрузке.

Удачи. Еще раз спасибо за внимание)
если вы поставите [Header()] перед приватным полем, то ругаться Unity не станет, но никакого заголовка не отобразит

Если сделать [SerializeField], то отобразит =) Вместе с полем, кстати. Тоже очень удобный аттрибут, когда нужно предоставить возможность редактировать поле из редактора, а в коде, скажем, сделать публичное свойство с необходимыми геттерами-сеттерами.
Директива #if UNITY_EDITOR разрешает скрипты касмного редактора размещать не в папке «Editor» или про это (что всю кастомизацию редактора нужно описывать в специальной папке) ограничение просто не написали? Сам на днях начал менять редактор и некоторое время был в смущении, почему же ничего не работает.
Я код кастомных инспекторов пишу в том же файле, в котором находится сам класс, для удобства. И да — в случае использования этой директивы код можно оставлять где угодно.
Спасибо! Полезно! Как раз начинаем работать над первой игрой и уже успели столкнуться с проблемой, которую вы решили с помощью Singletone. До этого решали проблему прямым указанием скрипта-менеджера через поле инспектора объекта, для которого нужен менеджер. Скрипты-менеджеры лежал в кучу в специальном даммике на сцене. Не очень удобно — часто забывали указывать менеджеры.

Ваш вариант решения выглядит намного более удобным… Да и прочие штуки выглядят вкусно. Будем внедрять по мере надобности. Ещё раз спасибо!
На практике вместо HideInInspector на открытых переменных, лучше использовать [SerializeField] на приватных. Когда много классов потом не приходится смотреть можно ли менять ее напрямую, все делать через аксессоры доступа.

Ну а в целом по архитектуре — выводить параметры геймплея в инспектор нормально для не очень больших игр. Но когда количество настроек начинает сильно увеличиваться — это смотрится ужасно. Мы предпочитаем хранить настройки в ресурсных файлах и писать интерфейсы к ним на основе EditorWindow, а в силу того что выделенного тулсет-программиста нет, то часто и не пишем ничего, а работаем напрямую с файлами.
Хм. В тексте есть картинка скрипта WeaponScriptController с параметрами анимации, в том числе и скорости. Вопрос, как контролируйте скоростные параметры анимации коли не секрет?
Вроде бы можно изменять скорость воспроизведения анимации через параметры компонента Animator (:
Но у нас часть «анимации» производится из кода, как раз чтобы можно было гибко менять параметры анимации — скорость замаха, время отдыха после удара…
Так-то все можно, возникает вопрос как все это правильно контролировать из кода? Как-то не хочется городить огород костылей.
Вариант animation[«Animation name»].speed = 0.5f; остался в прошлом (работает только для легаси анимации). Можно выставить что-нибудь в стиле animator.speed = 0.5f; однако это скорость работы самого аниматора, а не какого-то клипа или состояния (у AnimatorStateInfo для свойства speed доступен только геттер, хотя в редакторе аниматора ручками править его таки можно).
А у AnimationClip такого параметра как скорость вообще нет, есть правда метод AnimationClip.SampleAnimation(GameObject go, time t); Однако наличие параметра GameObject наводит на неприятные размышления, да в доках указано, что работает эта штука медленно, юзайте как лучше Animation… Окей, лезем дальше и после путешествий по гиперссылкам обнаруживаем:
image
Хм… видимо доки обновляют какие-то индусы, причем одни индусы вкурсе, а другие индусы не в курсе что Animation где-то там с 4.3 версии has been deprecated… Не, ну можно переключиться через Debug mode на Легаси-анимацию…

Вот теперь меня и интересует, как дальше жить? Не с мешаниной же легаси кода. Вот поэтому и поинтересовался, как вы там анимацией управляйте. Unity Scripting API Reference мягко говоря противоречивый
Скорость анимации можно менять через float-параметр, так же как и переходы между состояниями анимации (anim.SetFloat(«shootspeed», shotspeed)). А в инспекторе для конкретного клипа говорите, что используете multiplier для скорости и в качестве параметра выбираете заданную переменную.
Ну да, такая возможность в 5-ой версии как раз появилась, правда все равно это похоже на какие-то танцы с бубном (что бы что-то поюзать из кода, приходиться что-то дополнительно прикручивать ручками в редакторе, при этом когда-то для этого был реализован понятный и доступный интерфейс). А еще как можно по извращаться? Вообще вопрос анимации в Юнити таки очень интересен…

Меня вот еще что интересует, как в Юнити реализована работа таких вот функций в стиле SetFloat(string name), GetComponent() [когда-то можно было получить свойство gameObject.rigidbody, теперь вот надо использовать GetComponent()] и прочие Find(string name) и т.п. Уж случаем не рефлексию ли они используют или еще какое темное колдунство?
Как уже заметили, проще для дата-классов использовать ScriptableObject. Урок от Unity как им пользоваться как раз для таких задач.
Вообще, в больших проектах дизайнерам проще работать с Excel'ем. Забивать туда сотни и тысячи item'ов, высчитывать по странным формулам цену, графики рисовать и прочее. Потом всё это экспортится в xml или json. Хотя можно прямо из xls читать. Некоторые вообще по сети из google spreadsheet конфиги тянут.
да это весьма удобно, использовали такой подход (я про спридшит из гуглдокс).
Некоторые вообще по сети из google spreadsheet конфиги тянут.


Как и всю остальную инфу + конвертить ее после чтения в нужные типы данных один раз + оборачивать в синглтон.
Спасибо за статью, даже не задумывался обо всём этом.
Самое ужасное, что и мы почти год жили в уже коммерческом проекте без таких прекрасных вещей (:
Вопрос такой. Предположим я сделал как вы описали. Написал кастомные редакторы для юнитов, дал геймдизайнеру вбивать числа в поля. И тут возникает необходимость получить доступ к этим параметрам не имея инста юнита?? Например у меня есть окно покупки юнитов, где должно указываться какие характеристики будут у юнита после покупки. Откуда мне взять эти числа при таком подходе к архитектуре как у вас?
Я не совсем представляю как это устроенно именно у вас, но в моём случае было бы примерно так. Характеристики юнита указаны в префабе юнита. Окно покупки может получить доступ к префабу для того чтобы выдернуть эту информацию оттуда — должно ведь оно потом каким-то образом вызвать Instantiate этого юнита?
Sign up to leave a comment.