Pull to refresh

Comments 29

Среди разработчиков бытует мнение, что «магические» методы (Update, Start, OnSceneGUI и другие) реализованы по средствам System.Reflection в C#, однако есть информация, что это не так, и за их работу стоит благодарить C++ ядро Unity3D


Странное какое-то получается мнение о том, что Unity не использует рефлексию для методов Start и etc., а неким интересным способом Mono получает эту информацию и кэширует. Причем способ явно не описан и данное мнение ничем не подтверждается. Получить данные о приватном методе в C# можно только через рефлексию. То что Unity кэширует ссылки на найденные методы это понятно без слов. Я надеюсь, что никто не думает, что каждый вызов Update происходит через поиск MethodInfo по имени.

PS: при компиляции проекта под VS (Windows Store платформа), там тоже Mono выдает каким-то образом информацию о приватных методах?
Методы-события не обязательно должны быть приватными. Unity на самом деле по барабану как вы их объявите до тех пор пока они имеют правильное название и сигнатуру.

>Получить данные о приватном методе в C# можно только через рефлексию.
Из C#. Когда у вас ядро на плюсах, а шарп — скрипты, то проблема решается иначе.

А учитывая то, что на многих платформах теперь вообще ILtoCPP, то рефлекшн там и не может использоваться. Равно как он не используется, судя по всему, ядром Unity. Я могу конечно ошибаться, но насколько я понимаю, из C++ кода вы отражения .NET никак не можете использовать.
Методы-события не обязательно должны быть приватными.

В этом то и суть) по барабану может быть только из-за рефлексии.

Из C#. Когда у вас ядро на плюсах, а шарп — скрипты, то проблема решается иначе.

Объясните механизм, как получить доступ к методу (публичные не берем в счет) из C++, при как вы называете C# скриптах. Вы же понимаете, что C# скрипты, это не скрипты в понимании настоящем (аля Lua), когда компиляция идет на этапе исполнения. В Unity все скрипты компилируются автоматически при изменении оных, до того как вы что-либо запустите, иначе у вас ни инспектор поля не отобразит да и в принципе ничего работать не будет.

А учитывая то, что на многих платформах теперь вообще ILtoCPP, то рефлекшн там и не может использоваться. Равно как он не используется, судя по всему, ядром Unity. Я могу конечно ошибаться, но насколько я понимаю, из C++ кода вы отражения .NET никак не можете использовать.


IL2CPP это плюсовый интерпретатор C# кода по сути, то что делает CLR (точне CIL/MSIL) в виндовз. Цель простая С++ код работает быстрее. При этом использование рефлексии в коде С# дозволено даже при использовании IL2CPP.
IL2CPP использует AOT компиляцию, тогда как рефлексии требуется JIT. Так что скорее в этом замешан C++
Вы хотите сказать, что если я заюзаю IL2CPP, то мой код рефлексии не будет работать? Или я не совсем все понял, что вы имели ввиду?
Будет работать. Это к вопросу о том, что там скорее всего не C# рефлексия используется, а как-то обходится через с++. И скорее всего с Update/Start подобная техника используется.
Например при сборке проекта с рефлексией под iOS проект очень сильно раздуется, потому что сама платформа не может JIT. Тоже самое верно и с Generic'ами
Допустим это появилось в IL2CPP, а как это работало до него в 3 и 4 юнити, где было чисто Mono. Как бы можно было бы понять это всего дело, но C# код это не скрипты, он не разбирается на части через строки, так как это делается с lua. C# код компилируется и при сборке проекта запихивается весь в dll-ку CLI сборка. Из этой dll уже можно конечно вытащить методы, но я не совсем понимаю, как вытащить приватные метод из класса завернутого в dll. Если вы знаете механизм, буду рад услышать. Просто уже реально интересно.
http://stackoverflow.com/questions/30334178/using-mono-to-call-c-sharp-from-c-c
Как вариант. Как вы предлагаете рефлекшн использоваться из плюсов в шарп? Вам в любом случае рано или поздно придётся иначе как-то вызывать ваш код. Если погуглите, то найдёте порядка 5 методов, вроде P/Invoke.

AOT появился не в IL2CPP, эти требования были уже давно на iOS. Например, вы не можете генерить код в рантайме, это не будет работать совсем.
А рефлекшн работает там совсем иначе опять же, от того, как уже говорили, проекты сильно раздуваются.

>как вытащить приватные метод из класса завернутого в dll.
Так же как публичные, плюсам неважно какие у вас там методы.
Так же как публичные, плюсам неважно какие у вас там методы.

а можно пример? если не затруднит, давно на С++ кодил, лет 10 назад.
Я так-то ссылку привёл, как вариант. Метод ещё назвал. И если вы погуглите немного относительно интероп'а C# и C++, то много такого добра найдёте. Я тоже на плюсах уже давно ничего не писал, но знать обязан :D
Получить данные о приватном методе в C# можно только через рефлексию

Кстати, никогда не понимал, зачем там рефлексия? Почему нельзя было обьявить пустые виртуальные методы в классе-предке (MonoBehavior, он все-авно необходим), а тут делать им оверрайд (только тем, которые должны иметь поведение)? Все было бы статически и, при этом, приблизительно с тем же синтаксисом.
представьте что у вас 1000 объектов в сцене, и только 100 из них оверрайдят метод Update. Вызов пустых методов для 90% объектов, несмотря на то, что они пустые, приведет к ощутимой потере производительности. Собственно оно и сейчас не рекомендуется (я лично по рукам бью) оставлять пустые методы Start и Update в скриптах.
Какая-то пустая и не очень интересная статья. Три с половиной метода которые можно в документации легко найти. Ни картинок, ничего. Не поймёшь о чем речь, если не в курсе. Картинок бы, пример по реальнее. Про CustomEditor — забыли рассказать, зачем он, как его использовать, про OnInspectorGUI — ни слова, про ярчайший пример Handles — тоже ни слова.
image
Вот, например, редактор mesh-а внутри unity3d написанный с нуля при помощи описанных в статье техник а также небольшой магии OnInspectorGUI и Handles.
Я не совсем понял смысл, а зачем перехватывать события в данном случае? Обрабатывать события ввода с мыши можно и без таких конструкций. Лучше бы была бы где-нибудь статья, как в редакторе юнити настроить кастомные хоткей Вот это не так просто делается, если я правильно помню то, по какому принципу отрабатывает OnSceneGUI и OnInspectorGUI. Когда-то мне было проще сделать редактор просто отдельной сценой и запускать, чтобы с хоткеями работать (хотя это немного костыльненько, но удобно, в случае когда редактируемые объекты хранятся в виде файлов никак не связанных с Unity)
Кастомные хоткеи на свои функции делаются через атрибут MenuItem например:
// Alt+Shift+G, # - shift, & - alt, % - ctrl/cmd
[MenuItem("Edit/Test #&g")]
static public void Abc() {
	Debug.Log("Pressed!");
}

А вот если нужно, например, при удержании какой-то клавиши, выполнять какие-то изменения на сцене, и желательно прямо во время update — то тут подойдёт описанный в статье способ. Например, когда хочется собственный handles написать.
Я видел в документации такое, но работало оно как-то странно. И хоткеи мне нужны были, чтобы соединять на сцене точку А с точкой Б верёвкой, которая генерировалась скриптом (ну то есть был метод, который принимал параметрами трансформ начала и трансформ конца, и по данной информации генерировалась верёвка между ними) Update не работает в Editor моде, а OnInspectorGUI() и OnSceneGUI() работает довольно странно (вроде там завязано на том, когда мышь движется внутри гуя инспектора или сцены) Использовать [ExecuteInEditor] не хотелось уже не помню почему, но в данном случае Update() вроде тоже работает как OnSceneGUI(), но вот тут я могу ошибаться, не помню точно.

Поэтому я сделал просто сцену с редактором, где Update() работает в каждом кадре и создал класс Keyboard, в котором была скрыта логика работы событий ввода и явно описана логика работы необходимых методов. Так же хоткеи мне нужны были не для меню итемов, а для созданных мной интерфейсов, которые были в виде EditorWindow для левелдизайна и отвечали за инстанцирования префабов на сцену (просто менюшка с кнопочками). Причём префабы брались рекурсивно из папок (чтобы не прописывать каждый раз новый созданный объект) и кнопки создавались динамически. А тот вариант, который предложили вы хороший, но для простейших задач.
Сцену с редактором — имеется ввиду сцену с редактором уровней, которую просто запускал. А получившийся уровень у меня сохранялся то ли в формате XML, то ли в YAML. Поэтому апдейт и прочее отрабатывало в каждом кадре.
Вот указанный в статье метод как раз вам и нужен был, чтобы события отлавливать. Потому что OnScene (по ощущениям) вызывается по несколько раз за кадр, на каждое событие. А чтобы в редакторе было нечто подобное Update, нужно подписаться на события вот так:
// .. Editor class
// Built-in method
public void OnEnable() { 
	SceneView.onSceneGUIDelegate = scene.UpdateScene; // Точно не помню что это за переменная, но у меня в этом методе вся отрисовка сцены написана
	EditorApplication.update = Redraw; // Вот это событие вызывается около 20 раз в секунду
}
// Your methods
public void UpdateScene(SceneView sceneView) {}
public void Redraw() {
	// Принудительно вызовет методы OnInspectorGUI
	EditorUtility.SetDirty(target);
	Repaint();
}

И тогда появится подобие Update, примерно 20FPS
Где-то в недрах документации было, что OnScene как и OnEditorGUI вызовутся только когда что-то измениться на SceneView и Editor соответственно. По этому они и не работают как Update, а сделано так исключительно для экономии ресурсов.
// Принудительно вызовет методы OnInspectorGUI
	EditorUtility.SetDirty(target);

Сейчас кстати данный метод может не сработать, так как поменялась система внутренней сериализации в Unity.
Отсюда и вытекает экономия ресурсов. Если бы onSceneGUI/OnInspectorGUI вызывались по 60 раз за кадр, то столько бы раз происходила сериализация редактируемого объекта во внутренний формат Unity, что крайне сильно может ударить по производительности.
А от EditorUtility.SetDirty(target); советуют переходить к использованию SerializedProperty и даже грозят сделать SetDirty depricated
OnInsectorGUI/OnSceneGUI вызывается каждый раз когда проводишь мышкой над параметрами, handles, кликаешь по инспектору и т.д. Т.е. довольно часто, сомневаюсь что в этот момент происходит сериализация/десериализация, но полная перерисовка — конечно происходит. На этом
видео отрисовки видно что здесь не 60 кадров а гораздо меньше (около 20, окошко справа).

SerializedObject, Undo.RecordObject, etc — это конечно здорово, но муторно — когда пишешь какое-то быстрое решение, то лучше взять технический долг, а потом его компенсировать, когда будет готовое представление того как всё должно работать.

Вообще метод не об этом, и он костыльный, конечно (EditorUtility.SetDirty(target); — вызывается чтобы принудительно вызвать перерисовку а не для сохранения). Но пока другого не придумал и не нашел. Если есть, об этом, было бы интересно почитать.
За такое как SerializedObject надо бы по рукам бить, ибо имя переменной указывается строкой и легко допустить ошибку.
Не понимаю зачем они отказались от EditorUtility.SetDirty. Это удар ниже пояса.
Сами юнити, кстати, во всю используют SerializedObject т.к. это единственный способ получить доступ к приватному полю.
Во первых, конечно же, как вы заметили, получить доступ к переменной если она приватная — не получится (через Reflections можно).
Во вторых, например, у вас есть скачиваемый AssetBundle со ScriptableObject. в первой версии этого объекта были поля А и Б. Во второй добавились еще В и Г. Третья версия предложила именовать поле А как Я (при этом не забыв указать параметр FormerlySerializedAs), а тип Б поменяла с int на float. Если бы мы представляли это не текстом, а классом, то все наши приложения падали при десериализации отличной версии от той что содержат в скомпилированном виде.
В общем стоит просто понять что все MonoBehaviour и ScriptableObject хранятся в десериализованном виде, грубо говоря в виде текста, и по большому счёту SerializedObject создано чтобы их связывать между собой. А тексту свойственно сохранять всё так как задумал разработчик когда-то. Больше плюсов, чем просто ошибка с именем переменной. Можно построить редактор который учитывает такие вещи.
Вообще, ИМХО дико неудобно получилось с этими SerializedProperty/SerializedObject работать >____<.
От EditorUtility.SetDirty отказались из-за не очевидного поведения, когда редактируется несколько сцен одновременно то в старой реализации постоянно выскакивало сообщение «Хотите сохранить сцену?», теперь изменения просто игнорируются если саму сцену не сохранить перед переходом\выходом. В мануале подробно об этом написано, в каких случаях это работает а в каких нет. Для ScriptableObject — ничего не изменилось, и для него можно спокойно использовать EditorUtility.SetDirty.
В последний версиях C# появился оператор nameof, который был бы очень полезным в нашем случае.
Unity обещают скоро обновить компилятор, может скоро сможем его заюзать.

Вообще мне кажется сериализация и редактор никак не связаны. FormerlySerializedAs должен работать вполне независимо от, использую ли я в редакторе SerializedObject или нет.

Я не пойму, а что хорошего в том, что изменения просто игнорируются? С несколькими сценами еще не работал, поэтому тоже не пойму, как эта фича повлияла на SetDirty.
Без сериализации редактор просто невозможен. Нужно как-то хранить данные, загружать, обновлять. Хранить дамп памяти? Так разработка на ПК/Мак а работать будет на андроид/айос.
В документации подробно описано как это повлияло на SetDiry.
Тот факт, что изменения игнорируются используется еще и в Play Mode. Когда все данные сериализуются, вы можете изменять их пока включен Player Mode, а после выключения все изменения пропадают, так как объекты серилиализуются заново. В этом есть свое удобство или фича.
Расширение функционала Scene View может понадобиться для создания собственного редактора уровней, редактирования mesh’а, создания собственных gizmos и много другого.
Вот можно побольше пример на эту тему
Для примера редактора уровней внутри Scene View можно рассмотреть редактор для 2d изометрии. Генерировать gameobject для поля, разбитый на тайлы, как на картинке

отслеживать клики внутри тайлов и «рисовать» на них нужные спрайты или объекты

Что касается создания собственных gizmo, то в качестве примера можно посмотреть статью где автор создал собственные gizmo для удобства перемещения объектов
В каких случаях Event.current.GetTypeForControl(controlId) и Event.current.type могут иметь разные значения?
Всегда использовал просто Event.current.type и работало.

Вы понимаете как работает данный код?
HandleUtility.AddDefaultControl( GUIUtility.GetControlID( FocusType.Passive ) );
Давно использую этот код, чтобы отключить переключение кликом мыши на другой объект, но как это работает так и не понял.
Sign up to leave a comment.

Articles