|
Monkey Разработка игр на движке Monkey |
17.08.2016, 08:23
|
#1
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
MonkeyBehaviour
Всем привет.
Я тут написал простенькую модель проги (фреймворк) по типу юнити,
c классами GameObject, Component, MonkeyBehaviour, Transform, Sprite (и др).
И меня заинтересовал вопрос про оптимизацию LateUpdate - понравился мне этот метод, хоть он нужен довольно редко.
нужна ли оптимизация - не понятно, но в академических целях - почему нет.
Обработка стартует со сцены (Scene), у которой есть коневой GameObject, и далее вглубь по всем дочерним объектам. В каждом объекте - проход по всем компонентам.
Получается, надо прогнать 2 цикла по всему - сначала Update, затем LateUpdate.
Я придумал решение - через рефлексию. Пробежать по всем компонентам, и проверить наличие метода LateUpdate, если переопределён хоть в одном классе, значит ок, будем пробегать второй раз.
Проверку предполагается делать при старте/создании сцены, после юнитевского аналога Start().
Но что, если этот метод есть всего в одном скрипте?
Такое размышление приводит к мысли, что можно хранить все скрипты в едином списке.
Это позволит к тому же легко задать приоритет выполнения простой сортировкой списка, а также избавит от необходимости пробегать по всем дочерним элементам.
Например, делаем два списка - 1й для тех кто содержит Update, 2й для LateUpdate.
Конечно, если компоненты добавляются / удаляются динамически по ходу работы проги, то хз как оно будет по скорости. Можно в обёртке для скрипта помимо приоритета хранить ссылку на узел в списке, тогда быстро будет.
Но динамически рефлексию не хотелось бы юзать. Да и вообще, сомнительное решение получилось.
|
(Offline)
|
|
17.08.2016, 09:51
|
#2
|
ПроЭктировщик
Регистрация: 01.01.2016
Сообщений: 148
Написано 76 полезных сообщений (для 171 пользователей)
|
Ответ: MonkeyBehaviour
А зачем он нужен? Пост-обновление можно реализовать и в самом методе обновления... но если уж так приспичило, то считаю оптимальным вариантом с отдельным списком объектов, которые имеют метод LateUpdate(dt#). В случае с динамическим подходом - то перед перебором всех объектов сцены, очищаем список и во время перебора добавляем к нему объекты нуждающиеся в пост-обработке. После чего, перебираем получившийся список. Ах, да... если тебе важна скорость... то, не стоит генерить линки на лету, достаточно их хранить в объекте, добавляя их в список.
|
(Offline)
|
|
Сообщение было полезно следующим пользователям:
|
|
17.08.2016, 11:35
|
#3
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
Ответ: MonkeyBehaviour
LateUpdate любого (всех) компонента должен выполняться после всех Update компонентов, так что в самом методе обновления не сделаешь.
Применение - например следящая за объектом камера, которая движется после того как нужный объект подвигался. Аналогично - мобы с ИИ.
Кстати, можно без рефлексии сделать, путём вычленения LateUpdate в отдельный интерфейс.
Тогда можно будет формировать список через проверку на instanceof:
1. Для скриптов с LateUpdate наследуемся через
extends MonkeyBehaviour implements ILateUpdate
2. В GameObject в функции AddComponent делать проверку и добавлять в список (это внутренности, которые программист не видит/не трогает)
Local late := ILateUpdate(component) 'если null значит не содержит этот интерфейс If (late <> Null) Then Updater.AddLate(Self, component)
Аналогично можно раскидать любые методы типа Update, Draw, чтобы сформировать для них списки.
Не нужно рисование - не наследуешь IDraw, и т.п.
Тоже на изврат похоже.
|
(Offline)
|
|
17.08.2016, 12:15
|
#4
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
Ответ: MonkeyBehaviour
Кому интересно взглянуть на код - репозиторий.
|
(Offline)
|
|
Сообщение было полезно следующим пользователям:
|
|
17.08.2016, 12:15
|
#5
|
Гигант индустрии
Регистрация: 13.09.2008
Сообщений: 2,893
Написано 1,185 полезных сообщений (для 3,298 пользователей)
|
Ответ: MonkeyBehaviour
А что если сделать примерно так. Написать свой Behaviour где LastUpdate (и прочие функции) будут определены сразу как virtual и игрок при написании своего скрипта наследуется от Behaviour и переопределяет нужные ему функции. А в самом Behaviour все эти стандартные функции вызываются в любой ситуации.
Вроде в XNA что то подобное.
|
(Offline)
|
|
17.08.2016, 14:16
|
#6
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
Ответ: MonkeyBehaviour
Nex, сейчас так и сделано:
Interface IBehaviour
Method Awake:Void()
Method Start:Void()
Method Update:Void()
Method LateUpdate:Void()
Method Draw:Void()
End
Class MonkeyBehavior Extends Component Implements IBehaviour Abstract
Field enabled:Bool = True
Method Awake:Void()
End
Method Start:Void()
End
Method Update:Void()
End
Method LateUpdate:Void()
End
Method Draw:Void()
End
End
То есть мы имеем стандартную реализацию методов - пустышки, чтобы можно было переназначать только то, что требуется.
Вопрос как раз в том, чтобы не вызывать ВСЕ эти методы, а только при наличии переопределённых в коде юзера.
|
(Offline)
|
|
17.08.2016, 16:48
|
#7
|
Гигант индустрии
Регистрация: 13.09.2008
Сообщений: 2,893
Написано 1,185 полезных сообщений (для 3,298 пользователей)
|
Ответ: MonkeyBehaviour
Тогда как когда то pax рекомендовал делать через рефлексию и проверять используется ли функция в скрипте игрока. Так даже можно делать без virtual/override.
Просто при старте игры проверять код на наличие нужных функций и если есть, то делать из найденных функций делегат, который работает быстро в отличии от рефлексии. А там уже например в лист сохраняешь делегаты и вызываешь.
Я такое пробовал использовать на c# + 2д движек. И даже без делегатов на небольших сценках все быстро работало.
Сообщение от Nex
Научите, пожалуйста, как делать методы подобно Start() или Update(), которые уже где то в Юнити объявлены, но при этом их можно использовать/не использовать без override.
Я пробовал несколько способов, но не удачно.
1) Интерфейс требует что бы было объявлено и обязательно public.
2) Если объявлено выше по иерархии, то надо перезаписать.
3) Абстрактный метод как и интерфейс - нужно объявлять.
Какие еще способы есть?
|
Сообщение от pax
Все просто, это рефлексия
obj.GetType().GetMethod("MyMethod").Invoke(obj, arguments[])
Пара ссылок
https://msdn.microsoft.com/ru-ru/lib...eflection.aspx
https://msdn.microsoft.com/ru-ru/lib...v=vs.110).aspx
https://msdn.microsoft.com/ru-ru/lib...v=vs.110).aspx
Правда в Unity не просто рефлексия, а там несколько сложнее. Но принцип один, в Unity ничего не объявлено, все ищется по по именам.
Вот вам пример:
using UnityEngine;
using System.Reflection;
/// <summary>
/// Аналог SendMessage с более продвинутыми функциями.
/// Посылает сообщения даже выключенным объектам.
/// </summary>
public static class Message
{
public static void InvokeMethod(this GameObject gameObject, string methodName)
{
foreach (Component c in gameObject.GetComponents<Component>())
{
MethodInfo m = c.GetType().GetMethod(methodName);
if (m != null)
m.Invoke(c, null);
}
}
public static void InvokeMethod(this GameObject gameObject, string methodName, params object[] parameters)
{
foreach (Component c in gameObject.GetComponents<Component>())
{
MethodInfo m = c.GetType().GetMethod(methodName);
if (m != null)
m.Invoke(c, parameters);
}
}
public static void InvokeMethodUpwards(this GameObject gameObject, string methodName)
{
Transform t = gameObject.transform;
while (t)
{
foreach (Component c in t.gameObject.GetComponents<Component>())
{
MethodInfo m = c.GetType().GetMethod(methodName);
if (m != null)
m.Invoke(c, null);
}
t = t.parent;
}
}
public static void InvokeMethodUpwards(this GameObject gameObject, string methodName, params object[] parameters)
{
Transform t = gameObject.transform;
while (t)
{
foreach (Component c in t.gameObject.GetComponents<Component>())
{
MethodInfo m = c.GetType().GetMethod(methodName);
if (m != null)
m.Invoke(c, parameters);
}
t = t.parent;
}
}
public static void BroadcastMethod(this GameObject gameObject, string methodName)
{
foreach (Component c in gameObject.GetComponentsInChildren<Component>())
{
MethodInfo m = c.GetType().GetMethod(methodName);
if (m != null)
m.Invoke(c, null);
}
}
public static void BroadcastMethod(this GameObject gameObject, string methodName, params object[] parameters)
{
foreach (Component c in gameObject.GetComponentsInChildren<Component>())
{
MethodInfo m = c.GetType().GetMethod(methodName);
if (m != null)
m.Invoke(c, parameters);
}
}
}
|
|
|
(Offline)
|
|
18.08.2016, 06:14
|
#8
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
Ответ: MonkeyBehaviour
Просто при старте игры проверять код на наличие нужных функций и если есть, то делать из найденных функций делегат
|
а куда денется исходный метод, он же останется.
например, если завернуть Update в делегат.
или ты про другой случай? какой?
Также можно расширить MonoBehaviour или Component своими функциями и использовать их.
Как вариант - навешивать на корневой объект сцены некий менеджер скриптов, который пробежит по всем дочерним объектам и компонентам, соберёт все экземпляры твоего переопределённого Behaviour'а, и менеджер будет в юнитевских методах Start, Update и прочих пробегать по спискам и вызывать делегаты.
По части Update получается модель привычного gameloop'a, где все логики перебираются и выполняются.
Однако, тогда не получим фушку с очередностью выполнения скриптов. Но можно наверное и с ней заморочиться.
Это всё теория, даст ли оно профит, чтоб стоило с этим извращаться.)
Пример с классом Message - тормозное решение, конечно.
Да и вызов через строковое имя метода опасен - отрефакторишь имя функции и "привет".
Последний раз редактировалось Жека, 18.08.2016 в 07:53.
|
(Offline)
|
|
19.08.2016, 15:36
|
#9
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
Ответ: MonkeyBehaviour
К знатокам юнити и прочих движков.
Как лучше считать глобальную позицию и масштаб объектов?
Например, хочу сделать свойства localPosition и position.
Local возвращает просто вектор, а не-local делает проход по всем родителям и вычисляет позицию с учётом родительских позиций, масштабов, поворотов.
Так обычно делают или нет?
UPD: второй вариант - пересчитывать трансформы дочерних элементов при изменении свойств в родителе.
Для манки проблематично юзать такой подход, т.к. здесь все структуры - это классы, и нельзя запретить
изменение переменных внутри структуры.
То есть:
Если в юнити написать
transform.position.x = 200;
мы получим ошибку при компиляции.
За счёт этого можно перехватывать назначение переменной transform.position (а не поля "x" в структуре Vector) - и делать необходимые пересчёты.
В манки так сделать нельзя из-за ограничений языка.
Надо глянуть в сторону продвинутого Monkey2.
Последний раз редактировалось Жека, 20.08.2016 в 09:46.
|
(Offline)
|
|
20.08.2016, 10:20
|
#10
|
ПроЭктировщик
Регистрация: 01.01.2016
Сообщений: 148
Написано 76 полезных сообщений (для 171 пользователей)
|
Ответ: MonkeyBehaviour
Эм... в любом компоненте, обязательно должны быть только 3-4 метода... каждый называет их по своему, но я их назову так : Load(), Free(), Loop(dt#) и Draw() - думаю смысл их понятен. Все остальное - детали движка, которые легко реализовываются при помощи вышеописанных методов. Хотите LateUpdate() ? - Создайте хук, прицепите к нему нужный объект в методе Load() и вызывайте этот хук после всех обновлений... и выглядеть будет красиво и с ООП не придется заниматься сексом.
насчет второго вопроса... ну как бэ название говорит само за себя... localPosition - позиция объекта в локальной системе координат своего родителя. соотв. position - позиция относительно мировой системы координат. И для того, что бы узнать глобальное свойство без перебора всех родителей не обойтись ибо каждый родитель влияет на результат.
|
(Offline)
|
|
Сообщение было полезно следующим пользователям:
|
|
20.08.2016, 14:36
|
#11
|
Социал-сычевист
Регистрация: 24.06.2011
Сообщений: 611
Написано 342 полезных сообщений (для 1,359 пользователей)
|
Ответ: MonkeyBehaviour
Сообщение от Жека
К знатокам юнити и прочих движков.
Как лучше считать глобальную позицию и масштаб объектов?
Например, хочу сделать свойства localPosition и position.
Local возвращает просто вектор, а не-local делает проход по всем родителям и вычисляет позицию с учётом родительских позиций, масштабов, поворотов.
Так обычно делают или нет?
|
Это очень интересный вопрос. Однако, скажу что у меня сделано так:
У каждого объекта хранится полная трансформация в виде матрицы, а переменные позиций и углов находятся в локальных координатах. Дети обновляются только если изменилась трансформация родителя.
https://github.com/clashbyte/spriteb...orld/Entity.cs
|
(Offline)
|
|
Сообщение было полезно следующим пользователям:
|
|
20.08.2016, 16:58
|
#12
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
Ответ: MonkeyBehaviour
Хотите LateUpdate() ? - Создайте хук, прицепите к нему нужный объект в методе Load() и вызывайте этот хук после всех обновлений.
|
Можешь пояснить подробнее?
Псевдокод или в рамках юнити сущностей.
|
(Offline)
|
|
20.08.2016, 17:12
|
#13
|
Дэвелопер
Регистрация: 04.09.2005
Адрес: Красноярск
Сообщений: 1,376
Написано 491 полезных сообщений (для 886 пользователей)
|
Ответ: MonkeyBehaviour
Кирпи4, в твоём коде есть Vector3 и Vec3, зачем так.
|
(Offline)
|
|
20.08.2016, 17:15
|
#14
|
Социал-сычевист
Регистрация: 24.06.2011
Сообщений: 611
Написано 342 полезных сообщений (для 1,359 пользователей)
|
Ответ: MonkeyBehaviour
Сообщение от Жека
Кирпи4, в твоём коде есть Vector3 и Vec3, зачем так.
|
Vector3 - это тип OpenTK, а Vec3 - мой тип, юзается при пришивании библиотеки к редактору, чтобы от OpenTK зависел только этот проект
|
(Offline)
|
|
Сообщение было полезно следующим пользователям:
|
|
20.08.2016, 22:05
|
#15
|
ПроЭктировщик
Регистрация: 01.01.2016
Сообщений: 148
Написано 76 полезных сообщений (для 171 пользователей)
|
Ответ: MonkeyBehaviour
Модуль BlitzMax BRL.Hook :
Private
Type THook
Field succ:THook
Field priority
Field func:Object( id,data:Object,context:Object )
Field context:Object
End Type
Global hooks:THook[256]
Public
Function AllocHookId()
Global id=-1
id:+1
If id>255 Throw "Too many hook ids"
Return id
End Function
Function AddHook( id,func:Object( id,data:Object,context:Object ),context:Object=Null,priority=0 )
Local t:THook=New THook
t.priority=priority
t.func=func
t.context=context
Local pred:THook
Local hook:THook=hooks[id]
While hook
If priority>hook.priority Exit
pred=hook
hook=hook.succ
Wend
If pred
t.succ=pred.succ
pred.succ=t
Else
t.succ=hooks[id]
hooks[id]=t
EndIf
End Function
Function RunHooks:Object( id,data:Object )
Local hook:THook=hooks[id]
While hook
data=hook.Func( id,data,hook.context )
hook=hook.succ
Wend
Return data
End Function
1) Создаем переменную LateUpdateHook:Int = AllocHookId()
2) Вставляем объект и функцию-обработчик с помощью AddHook(LateUpdateHook, func, object, priority)
3) Ну и обработка всего этого дела - RunHooks(LateUpdateHook)
|
(Offline)
|
|
Ваши права в разделе
|
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения
HTML код Выкл.
|
|
|
Часовой пояс GMT +4, время: 11:58.
|