1 | ||||||||||||||||
[Unity, лайфхак] Как сделать адекватный синглтон (singleton или уникальный для сцены) компонент05.03.2017, 20:27. Показов 9061. Ответов 13
Метки нет (Все метки)
Что такое singleton – я надеюсь знают все.
Какие цели лично я преследую при использовании синглтона? Во-первых – легкий доступ к экземпляру класса в коде любого компонента. Во-вторых – запрет на добавление нескольких компонентов в сцену, если по логике этот компонент должен быть уникальным (настройки, BestScore…..). Причем запрет должен действовать и на уровне редактора, и на уровне кода. В-третьих – все должно быть понятно, поведение компонента естественно и ожидаемо, логика синглтона не должна мешать нормальной работе компонента и не должна сказываться на производительности готового приложения. В интернете достаточно много примеров по реализации синглтона на Unity. С ленивой инициализацией, с инициализацией в Awake или Reset, с кэшированием ссылок и т.д. и т.п. Приведу пример своего компонента: Код на С#
Все это, конечно, прекрасно. И пока не сильно отличается от того, что можно найти на первой странице гугла по запросу «синглтон в юнити». Но я не увидел, чтобы авторы статей рассматривали 2 проблемы, с которыми столкнулся я: 1) При пересборке проекта (читай – при ЛЮБОМ изменении в любой части кода) коллекция _instances отчищается, т.к. класс UniqueComponentAtScene не является сериализуемым. А быть он таким не может, потому что нам заранее неизвестно, насколько сложные будут наследующие классы. Возможно их нельзя будет сделать сериализуемыми в принципе. И «ближайший» вызов метода Awake, где _instances заполняется, произойдет только в случае запуска проекта в редакторе. Т.е. поведение компонента UniqueComponentAtScene – НЕ ОЖИДАЕМО, и заставляет каждый раз жмакать кнопку Play в редакторе. Можно забыть про это и словить внезапную ошибку NullReferenceException. 2) Если наследующий UniqueComponentAtScene класс1 будет иметь атрибут RequireComponent с указанием на класс2, который тоже является наследником UniqueComponentAtScene – то произойдет ошибка при добавлении дубля компонента класса1 на сцену.
Ну и после такого долгого введения перейдем непосредственно к лайфхаку Для решения первой проблемы я использую такой хитрый атрибут, как DidReloadScripts. Упоминаний про него я нигде не встречал и вообще наткнулся совершенно случайно. Он позволяет пометить статичный метод, который будет вызван в редакторе после пересборки проекта или при первой загрузке редактора. После этого нам надо найти всех наследников UniqueComponentAtScene с помощью рефлексии и найти экземпляры этих классов на сцене. Когда все экземпляры, или компоненты, или синглтоны, найдены – вызываем для них Awake. Правда здесь тоже есть нюанс – почему-то метод, расположенный в абстрактном генерик классе и помеченный атрибутом DidReloadScripts, вызывает ошибку "TypeLoadException: A type load exception has occurred". Толи баг, толи моих знаний языка не хватает для понимания… В общем, необходимо добавить прямо в файл с кодом UniqueComponentAtScene скриптуемый объект, в котором уже прописать нужный метод с атрибутом DidReloadScripts. Т.е. полный код файла UniqueComponentAtScene.cs будет выглядеть так: Полный код примера на С#
Со второй проблемой способа борьбы я не придумал. Разве только словесно запретить использовать RequireComponent с указанием на наследников UniqueComponentAtScene Если вы знаете, что делать со второй проблемой – с радостью выслушаю ваше решение. Ну и вообще замечания, предложения, пожелания приветствуются! _______________________________________ site: ocp.onl e-mail: hello@ocp.onl
0
|
05.03.2017, 20:27 | |
Ответы с готовыми решениями:
13
Unity как сделать чтобы при старте игры работало 2 сцены одновременно. Типо на первой сцене меню и главные скрипты Можно ли сделать так, чтобы при запуске сцены unity сам нажимал на нужную кнопку? Unity 2d как рандомно загружать заготовленные сцены Компонент для создания двухмерной сцены |
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
|
|
05.03.2017, 20:57 | 2 |
А для чего такие сложности? Массивы синглтонов + наследование от синглтонов? Зачем?
0
|
05.03.2017, 21:48 [ТС] | 3 |
Ну есть несколько причин:
1) Чтобы отделить мух от котлет. Механизм синглтонов отдельно, механизм игровой логики отдельно Вся их связь в одной строчке - base.Awake в наследнике. 2) Для уменьшения повторяемости кода. Если несколько синглтонов - то для каждого придется писать заново обработку. А т.к. в Unity конструкторы классов, наследных от MonoBehaviour, недоступны - то придется все запихивать в Awake. И читаемость это не улучшит. 3) По наследованию от UniqueComponentAtScene можно понимать, что компонент должен быть уникальным. С другой стороны, если просто дописывать св-во Instance в каждый такой класс - это будет совсем не наглядно. А как вы используете синглтоны в юнити? Может покажете пример?
0
|
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
|
||||||
05.03.2017, 22:25 | 4 | |||||
Синглтон - это архитектура, а не игровая логика.
Синглтон на то и синглтон, чтобы быть единственным и уникальным. Это, можно сказать, альтернативная реализация статического класса. Смотри выше )) Вот так, например, можно, чтобы не искать компонент везде, где нужна локализация:
0
|
05.03.2017, 23:22 [ТС] | 5 | |||||
В англоязычном сообществе принято использовать термин singletone для обозначения уникального компонента, т.е. именно ваш пример
Давайте без прописных истин, всем это прекрасно известно Я это и делал Вы привели пример с менеджером локализации. Хорошо, давайте разберемся. 1) Если вам еще нужно сделать менеджер ввода, менеджер настроек уровня, еще три менеджера и четыре других каких-то уникальных компонента? В каждый вы будете дописывать
2) Смотрим дальше. Например, нерадивый геймдизайнер случайно добавил на сцену три менеджера локализации и забыл про это. Позже, потратив на настройку одного из них много-много времени - он решил проверить результат. И запустил сцену. И вот тут совсем не обязательно, что глупый Юнити не удалит настроенный компонент, оставив пустой в качестве синглтона. Соответственно надо пометить компонент ExecuteInEditMode, и запоминать, какой из компонентов добавлен первым (_isFirst). Чтобы удалять остальные, но не трогать его. Посмотрите, пожалуйста, какие цели были поставлены - запретить повторное добавление компонента в сцену, если такой уже есть. Стандартная реализация из гугла этому не препятствует.
0
|
295 / 244 / 128
Регистрация: 24.12.2014
Сообщений: 708
|
|||||||||||
06.03.2017, 03:58 | 6 | ||||||||||
Что вам мешает сделать классы без наследования MonoBehaviour или вообще без любого наследования? Нужную инициализацию можно сделать и в статическом конструкторе и сам класс сделать статическим, тогда доступ к синглтону будет ещё короче - без свойства Instance. Приведу пример с той же локализацией:
Правда не знаю как будет вести себя производительность, возможно приложение будет дольше грузится или выбрасывать исключения, поскольку Unity не всегда дружит со статическими конструкторами, которые работают с компонентами юнити. Но с файлами проблем не возникало. Механизм синглтонов это и подразумевает, чтобы для каждого такого объекта был единый статический экземпляр класса. Если я ошибаюсь, то напишите каким вы видите процесс доступа к синглтонам. Судя по вашим идеям вы ходите сделать что-то типа менеджера синглтонов Чтобы было примерно так: МЕНЕДЖЕР.[ТИП_ИЛИ_НАЗВАНИЕ_КОНКРЕТНОГО_СИНГЛОНА].[ВОЗМОЖНО_ЕЩЁ_INSTANCE].МЕТОД_СИНГЛОТА или для примера Setting<Localization>.Instance.SetLang( "ru" ); Неужели такой вид будет легче читать и писать, чем например этот: Localization.SetLang( "ru" ); Учитывая, что вы хотите ещё сделать какое-то наследование, то скорее всего "Чтобы отделить мух от котлет" превратится в кашу, причём густую Пусть будет несколько менеджеров, но ими будет легче управлять и риск наткнуться на NullReferenceException будет минимальным.
0
|
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
|
|
06.03.2017, 07:52 | 7 |
Ну так тут код ни причём. Геймдизу надо по рукам надавать.
Экономия на спичках? Да ещё с Linq'ом?
0
|
06.03.2017, 13:30 [ТС] | 8 |
Вот это очень правильный вопрос! Чтобы была возможность работать с компонентом в редакторе Unity, как с любым другим. Вообще, судя по отзывам здесь и на геймдеве, что-то я засомневался в правильности именования "компонента, который должен быть единственным на сцене" - синглтоном )) Ну и ладно, главное что я определил задачи, которые мне надо решить - и уже от этого отталкивался
У меня так и есть Просто доступ идет через [ClassName].Instance.[fieldOrPropertyOrMethod] Менеджера, как такового нет. Может и, скорее всего, будет. Но это не плохо. Такая ошибка ловится легко на этапе разработки. И единственная причина, почему вылезает такой эксепшен - потому что компонент не добавлен в сцену. Можно ему даже сделать обработку: при получении NullReferenceException - добавляем новый GO с единственным компонентом в корень сцены. Добавлено через 3 часа 16 минут Не совсем понимаю, что вы имеете против Linq ? Ну при большом желании можно развернуть в более классический код. Интересное замечание. Этот метод подходит для большой компании. Когда у программистов есть время и возможность написать регламент по использованию движка/фреймворка. А после его поддерживать в актуальном состоянии. Если кто-то не прочитал инструкцию и запорол проект - это его проблемы, из ЗП вычтут. Я же рассматриваю ситуацию, когда команда состоит из буквально нескольких человек. Тем самым "нерадивым геймдизайнером" могу быть и я сам, через пару недель после написания компонента и добавления его в сцену. И если кто-то в команде потратил время зря, и ему придется заново настраивать компонент - то и получение готового продукта откладывается на это самое время. Соответственно, можно сказать, что Я несу издержки. А мне оно надо ? Да и к тому же все мы люди. Человеческий фактор - самый непредсказуемый. А в общем то вы согласны, что ТАКАЯ проблемная ситуация может произойти?
0
|
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
|
|||||||||||
06.03.2017, 21:25 | 9 | ||||||||||
Начиная от кучи мусора после него (что заставляет чаще срабатывать GC) и до его медлительности при частых вызовах.
Вот этот код
Нет, Linq допустим только в методах НЕ КАЖДЫЙ кадр, а в идеале только там, где без него вообще никак. В маленькой команде и небольшом проекте можно текущую архитектуру держать на виду, техники прототипирования и реализации на текущей архитектуре проекта. Документация излишня, не на продажу же фреймворк )))
0
|
06.03.2017, 22:48 [ТС] | 10 | ||||||||||
За пол минуты без IDE (в с решарпером в два клика) это разворачивается в
Ох беда... Вы, походу, не понимаете смысл генерик листа? В этом списке лежат ссылки на экземпляры любого класса, наследника UniqueComponent - т.е. любого уникального компонента. Кэширование, чтобы не просматривать объекты на сцене каждый раз. При вызове Instance мы приводим элемент из списка к нужному типу. Использование выглядит примерно так:
0
|
295 / 244 / 128
Регистрация: 24.12.2014
Сообщений: 708
|
||||||||||||||||
07.03.2017, 17:47 | 11 | |||||||||||||||
Возможно вы не поверите, но если использовать в update или достаточно часто таким образом Instance, то производительность существенно падает. Может это не заметно современных машинах, но моём старом ведре разница заметная. Предлагаю вам в место списка использовать словарь типа этого:
Ключи в словарях должны быть всегда уникальными, по-этому это можно использовать для того, чтобы контролировать уникальность. При запуске приложения можно легко отловить место, где происходит добавление дубликата. Но так как ваша цель сделать это на уровне редактора, то вряд ли эта цель достижима, разве что через какие-то костыли )
0
|
753 / 600 / 204
Регистрация: 06.08.2015
Сообщений: 2,432
|
|
07.03.2017, 22:49 | 12 |
Именно поэтому я и привёл в виде примера локализатор, а не контроллер управления.
0
|
10.03.2017, 01:01 [ТС] | 13 |
Ну есть Unity wiki. Какой никакой, а источник стандартизации информации, пускай и сообществом. http://wiki.unity3d.com/index.php/Singleton
Статические классы нельзя редактировать в редакторе (каламбурчик). Ну либо писать для КАЖДОГО такого класса новый инспектор, что тоже геморрой славный, согласитесь? За словарь спасибо, что-то не подумал про него. Вообще я согласен, раз это игра - за оптимизацией надо следить. Все-таки (n) раз в секунду метод вызывается. Использовать массивы вместо листов, for вместо foreach, кэшировать ссылки вместо FindObject и т.д. и т.п. Это все скорее монотонный труд и дело привычки. Т.е. никак не относится к подобной, как я понял, нестандартной задаче
0
|
295 / 244 / 128
Регистрация: 24.12.2014
Сообщений: 708
|
|
10.03.2017, 02:20 | 14 |
Ну, там паттерн синглтона соблюдается. Не знаю только почему Вы сюда это написали.
Я навёл код только в качестве альтернативы вашего "синглтона", то есть без использования компонентов как таковых. Не все же классы делать компонентами )
0
|
10.03.2017, 02:20 | |
10.03.2017, 02:20 | |
Помогаю со студенческими работами здесь
14
Как записать звук со сцены в Unity, а затем сохранить его на рабочий стол? Unity сцены. Unity lifecycle Как сделать анимацию двери в авто на Unity 4 или 5? Как сделать уникальный личный кабинет, авторизацию и баланс для каждого пользователя? Ищется компонент или как сделать Как сделать компонент Panel в виде выгнутой или вогнутой формы? Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |