Некоторое время назад я начал работать с Unity и до сих пор борюсь с проблемой тесно связанных скриптов. Как я могу структурировать свой код, чтобы избежать этой проблемы?
Например:
Я хочу иметь системы здоровья и смерти в отдельных сценариях. Я также хочу иметь различные взаимозаменяемые сценарии ходьбы, которые позволяют мне изменять способ перемещения персонажа игрока (основанные на физике, инерционные элементы управления, как в Mario, по сравнению с жесткими, дергающимися элементами управления, как в Super Meat Boy). Сценарий Health должен содержать ссылку на скрипт Death, чтобы он мог вызывать метод Die (), когда здоровье игроков достигает 0. В скрипте Death должна содержаться некоторая ссылка на используемый скрипт прогулки, чтобы отключить хождение на смерть (I устал от зомби).
Я обычно создавать интерфейсы, как IWalking
, IHealth
и IDeath
, таким образом , что я могу изменить на наитию эти элементы , не нарушая остальную часть моего кода. Скажем, я бы настроил их с помощью отдельного сценария на объекте player PlayerScriptDependancyInjector
. Возможно , что сценарий будет иметь общественные IWalking
, IHealth
и IDeath
атрибуты, так что зависимости может быть установлен конструктором уровня от инспектора, перетаскивая соответствующие сценарии.
Это позволило бы мне просто добавить поведение в игровые объекты и не беспокоиться о жестко закодированных зависимостях.
Проблема в единстве
Проблема в том , что в Unity я не могу выставить интерфейсы в инспекторе, и если я пишу свои собственные инспекторы, ссылки не получите сериализовать, и это очень много ненужной работы. Вот почему я остался с написанием тесно связанного кода. Мой Death
сценарий предоставляет ссылку на InertiveWalking
сценарий. Но если я решу, что хочу, чтобы персонаж игрока жестко контролировал, я не могу просто перетащить TightWalking
сценарий, мне нужно изменить Death
сценарий. Это отстой. Я могу с этим справиться, но моя душа плачет каждый раз, когда я делаю что-то подобное.
Какая предпочтительная альтернатива интерфейсам в Unity? Как мне решить эту проблему? Я нашел это , но оно говорит мне то, что я уже знаю, и не говорит мне, как это сделать в Unity! Здесь также обсуждается, что нужно делать, а не как, это не решает проблему тесной связи между сценариями.
В целом, я чувствую, что они написаны для людей, которые пришли в Unity с опытом разработки игр, которые просто учатся программировать, и у Unity очень мало ресурсов для обычных разработчиков. Есть ли какой-нибудь стандартный способ структурировать ваш код в Unity, или мне нужно найти свой собственный метод?
The problem is that in Unity I can't expose interfaces in the inspector
Я не думаю, что понимаю, что вы подразумеваете под «разоблачить интерфейс в инспекторе», потому что моей первой мыслью было «почему бы и нет?»Ответы:
Есть несколько способов избежать жесткой связи скриптов. Внутренним для Unity является функция SendMessage, которая при нацеливании на объект GameObject монобиха отправляет это сообщение всем объектам игры. Таким образом, у вас может быть что-то вроде этого в вашем объекте здоровья:
Это действительно упрощено, но должно точно показать, к чему я клоню. Свойство, выбрасывающее сообщение, как и событие. Затем ваш сценарий смерти перехватит это сообщение (и если ничего не будет перехватывать, SendMessageOptions.DontRequireReceiver гарантирует, что вы не получите исключение) и выполнит действия на основе нового значения работоспособности после его установки. Вот так:
Однако в этом нет гарантии порядка. По моему опыту, он всегда идет от самого верхнего сценария в GameObject к нижнему большинству сценариев, но я бы не стал рассчитывать на то, что вы сами не сможете наблюдать.
Есть и другие решения, которые вы могли бы исследовать, - это создание пользовательской функции GameObject, которая получает компоненты и возвращает только те компоненты, которые имеют определенный интерфейс вместо типа. Это займет немного больше времени, но может хорошо послужить вашим целям.
Кроме того, вы можете написать собственный редактор, который проверяет объект, назначаемый на позицию в редакторе для этого интерфейса, перед фактическим подтверждением назначения (в этом случае вы будете назначать скрипт как обычно, позволяя его сериализовать, но выдавая ошибку если он не использовал правильный интерфейс и отказ в назначении). Я делал это раньше и могу создать этот код, но сначала мне понадобится время, чтобы найти его.
источник
Я лично НИКОГДА не использую SendMessage. Между вашими компонентами по-прежнему существует зависимость от SendMessage, она очень плохо показана и ее легко сломать. Использование интерфейсов и / или делегатов действительно устраняет необходимость когда-либо использовать SendMessage (что медленнее, хотя это не должно вызывать беспокойства, пока это не нужно).
http://forum.unity3d.com/threads/free-vfw-full-set-of-drawers-savesystem-serialize-interfaces-generics-auto-props-delegates.266165/
Используй вещи этого парня. Он предоставляет кучу редакторов, таких как открытые интерфейсы и делегаты. Есть куча вещей вроде этого, или вы можете даже сделать свой собственный. Что бы вы ни делали, НЕ ставьте под угрозу свой дизайн из-за отсутствия поддержки сценариев в Unity. Эти вещи должны быть в Unity по умолчанию.
Если это не вариант, перетащите вместо этого монобезопасные классы и динамически приведите их к требуемому интерфейсу. Это больше работы и менее элегантно, но это делает работу.
источник
Подход Unity для меня заключается в том, чтобы сначала
GetComponents<MonoBehaviour>()
получить список сценариев, а затем преобразовать эти сценарии в частные переменные для определенных интерфейсов. Вы хотите сделать это в Start () в скрипте инжектора зависимостей. Что-то типа:Фактически, с помощью этого метода вы могли бы даже заставить отдельные компоненты сообщать сценарию внедрения зависимостей, каковы их зависимости, используя команду typeof () и тип «Тип» . Другими словами, компоненты предоставляют инжектору зависимостей список типов, а затем инжектор зависимостей возвращает список объектов этих типов.
В качестве альтернативы вы можете просто использовать абстрактные классы вместо интерфейсов. Абстрактный класс может выполнять почти ту же задачу, что и интерфейс, и вы можете ссылаться на него в Инспекторе.
источник
GetComponent<IMyInterface>()
иGetComponents<IMyInterface>()
напрямую, который возвращает компонент (ы), который его реализует.Исходя из моего собственного опыта, игровой разработчик традиционно использует более прагматичный подход, чем промышленный разработчик (с меньшим количеством уровней абстракции). Частично потому, что вы хотите отдать предпочтение производительности, а не удобству сопровождения, а также потому, что вы с меньшей вероятностью будете повторно использовать свой код в других контекстах (обычно существует сильная внутренняя связь между графикой и игровой логикой)
Я полностью понимаю вашу озабоченность, особенно потому, что проблема производительности не так важна для последних устройств, но я чувствую, что вам придется разрабатывать свои собственные решения, если вы хотите применить лучшие отраслевые методы ООП к разработке игр Unity (такие как внедрение зависимостей, так далее...).
источник
Взгляните на IUnified ( http://u3d.as/content/wounded-wolf/iunified/5H1 ), который позволяет вам предоставлять интерфейсы в редакторе, как вы упомянули. По сравнению с обычным объявлением переменных нужно сделать немного больше, вот и все.
Кроме того, как уже упоминалось в других ответах, использование SendMessage не удаляет связь. Вместо этого он делает это не ошибка во время компиляции. Очень плохо - когда вы расширяете свой проект, его обслуживание может стать кошмаром.
Если вы также хотите отделить свой код без использования интерфейса в редакторе, вы можете использовать строго типизированную систему, основанную на событиях (или даже на самом деле слабо типизированную, но, опять же, сложную в обслуживании). Я основал свой пост на этом посте , который очень удобен в качестве отправной точки. Будьте внимательны, создавая кучу событий, так как это может вызвать GC. Вместо этого я решил проблему с пулом объектов, но в основном потому, что я работаю с мобильными устройствами.
источник
Источник: http://www.udellgames.com/posts/ultra-useful-unity-snippet-developers-use-interfaces/
источник
Я использую несколько разных стратегий, чтобы обойти упомянутые вами ограничения.
Одна из тех, о которых я не упомянул, - это использование
MonoBehavior
доступа к различным реализациям данного интерфейса. Например, у меня есть интерфейс, который определяет, как реализованы Raycasts, названныйIRaycastStrategy
Затем я реализую его в разных классах с такими классами, как
LinecastRaycastStrategy
и,SphereRaycastStrategy
и реализую MonoBehavior,RaycastStrategySelector
который предоставляет один экземпляр каждого типа. Нечто подобноеRaycastStrategySelectionBehavior
, в котором реализовано что-то вроде:Если реализации могут ссылаться на функции Unity в своих конструкциях (или просто быть в безопасности, я просто забыл об этом при наборе этого примера) использовать,
Awake
чтобы избежать проблем с вызовом конструкторов или обращением к функциям Unity в редакторе или вне основного нитьЭта стратегия имеет некоторые недостатки, особенно когда вам приходится управлять множеством различных реализаций, которые быстро меняются. Проблемы с отсутствием проверки во время компиляции могут быть решены с помощью специального редактора, так как значение enum может быть сериализовано без какой-либо специальной работы (хотя я обычно воздерживаюсь от этого, поскольку мои реализации, как правило, не так многочисленны).
Я использую эту стратегию из-за гибкости, которую она дает вам, основываясь на MonoBehaviors, фактически не заставляя вас реализовывать интерфейсы с использованием MonoBehaviors. Это дает вам «лучшее из обоих миров», поскольку доступ к Селектору можно получить с помощью
GetComponent
, все сериализации обрабатываются Unity, и Селектор может применять логику во время выполнения для автоматического переключения реализаций на основе определенных факторов.источник