forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   Monkey (http://forum.boolean.name/forumdisplay.php?f=163)
-   -   MonkeyBehaviour (http://forum.boolean.name/showthread.php?t=20378)

Жека 17.08.2016 08:23

MonkeyBehaviour
 
Всем привет.
Я тут написал простенькую модель проги (фреймворк) по типу юнити,
c классами GameObject, Component, MonkeyBehaviour, Transform, Sprite (и др).

И меня заинтересовал вопрос про оптимизацию LateUpdate - понравился мне этот метод, хоть он нужен довольно редко.
нужна ли оптимизация - не понятно, но в академических целях - почему нет.

Обработка стартует со сцены (Scene), у которой есть коневой GameObject, и далее вглубь по всем дочерним объектам. В каждом объекте - проход по всем компонентам.

Получается, надо прогнать 2 цикла по всему - сначала Update, затем LateUpdate.

Я придумал решение - через рефлексию. Пробежать по всем компонентам, и проверить наличие метода LateUpdate, если переопределён хоть в одном классе, значит ок, будем пробегать второй раз.
Проверку предполагается делать при старте/создании сцены, после юнитевского аналога Start().

Но что, если этот метод есть всего в одном скрипте?
Такое размышление приводит к мысли, что можно хранить все скрипты в едином списке.
Это позволит к тому же легко задать приоритет выполнения простой сортировкой списка, а также избавит от необходимости пробегать по всем дочерним элементам.
Например, делаем два списка - 1й для тех кто содержит Update, 2й для LateUpdate.

Конечно, если компоненты добавляются / удаляются динамически по ходу работы проги, то хз как оно будет по скорости. Можно в обёртке для скрипта помимо приоритета хранить ссылку на узел в списке, тогда быстро будет.

Но динамически рефлексию не хотелось бы юзать. Да и вообще, сомнительное решение получилось.:-)

mingw 17.08.2016 09:51

Ответ: MonkeyBehaviour
 
А зачем он нужен? Пост-обновление можно реализовать и в самом методе обновления... но если уж так приспичило, то считаю оптимальным вариантом с отдельным списком объектов, которые имеют метод LateUpdate(dt#). В случае с динамическим подходом - то перед перебором всех объектов сцены, очищаем список и во время перебора добавляем к нему объекты нуждающиеся в пост-обработке. После чего, перебираем получившийся список. Ах, да... если тебе важна скорость... то, не стоит генерить линки на лету, достаточно их хранить в объекте, добавляя их в список.

Жека 17.08.2016 11:35

Ответ: MonkeyBehaviour
 
LateUpdate любого (всех) компонента должен выполняться после всех Update компонентов, так что в самом методе обновления не сделаешь.

Применение - например следящая за объектом камера, которая движется после того как нужный объект подвигался. Аналогично - мобы с ИИ.

Кстати, можно без рефлексии сделать, путём вычленения LateUpdate в отдельный интерфейс.
Тогда можно будет формировать список через проверку на instanceof:
1. Для скриптов с LateUpdate наследуемся через
Код:

extends MonkeyBehaviour implements ILateUpdate
2. В GameObject в функции AddComponent делать проверку и добавлять в список (это внутренности, которые программист не видит/не трогает)
PHP код:

Local late := ILateUpdate(component'если null значит не содержит этот интерфейс
If (late <> Null) Then Updater.AddLate(Self, component) 

Аналогично можно раскидать любые методы типа Update, Draw, чтобы сформировать для них списки.
Не нужно рисование - не наследуешь IDraw, и т.п.
Тоже на изврат похоже. :)

Жека 17.08.2016 12:15

Ответ: MonkeyBehaviour
 
Кому интересно взглянуть на код - репозиторий.

Nex 17.08.2016 12:15

Ответ: MonkeyBehaviour
 
А что если сделать примерно так. Написать свой Behaviour где LastUpdate (и прочие функции) будут определены сразу как virtual и игрок при написании своего скрипта наследуется от Behaviour и переопределяет нужные ему функции. А в самом Behaviour все эти стандартные функции вызываются в любой ситуации.

Вроде в XNA что то подобное.

Жека 17.08.2016 14:16

Ответ: MonkeyBehaviour
 
Nex, сейчас так и сделано:
PHP код:

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 

То есть мы имеем стандартную реализацию методов - пустышки, чтобы можно было переназначать только то, что требуется.

Вопрос как раз в том, чтобы не вызывать ВСЕ эти методы, а только при наличии переопределённых в коде юзера.

Nex 17.08.2016 16:48

Ответ: MonkeyBehaviour
 
Тогда как когда то pax рекомендовал делать через рефлексию и проверять используется ли функция в скрипте игрока. Так даже можно делать без virtual/override.
Просто при старте игры проверять код на наличие нужных функций и если есть, то делать из найденных функций делегат, который работает быстро в отличии от рефлексии. А там уже например в лист сохраняешь делегаты и вызываешь.

Я такое пробовал использовать на c# + 2д движек. И даже без делегатов на небольших сценках все быстро работало.

Цитата:

Цитата:

Сообщение от Nex (Сообщение 305000)
Научите, пожалуйста, как делать методы подобно Start() или Update(), которые уже где то в Юнити объявлены, но при этом их можно использовать/не использовать без override.
Я пробовал несколько способов, но не удачно.
1) Интерфейс требует что бы было объявлено и обязательно public.
2) Если объявлено выше по иерархии, то надо перезаписать.
3) Абстрактный метод как и интерфейс - нужно объявлять.
Какие еще способы есть? :)

Цитата:

Сообщение от pax (Сообщение 305004)
Все просто, это рефлексия
PHP код:

obj.GetType().GetMethod("MyMethod").Invoke(objarguments[]) 

Пара ссылок
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 ничего не объявлено, все ищется по по именам.

Вот вам пример:
PHP код:

using UnityEngine;
using System.Reflection;

/// <summary>
/// Аналог SendMessage с более продвинутыми функциями.
/// Посылает сообщения даже выключенным объектам.
/// </summary>
public static class Message
{

    public static 
void InvokeMethod(this GameObject gameObjectstring methodName)
    {
        foreach (
Component c in gameObject.GetComponents<Component>())
        {
            
MethodInfo m c.GetType().GetMethod(methodName);
            if (
!= null)
                
m.Invoke(cnull);
        }
    }

    public static 
void InvokeMethod(this GameObject gameObjectstring methodNameparams object[] parameters)
    {
        foreach (
Component c in gameObject.GetComponents<Component>())
        {
            
MethodInfo m c.GetType().GetMethod(methodName);
            if (
!= null)
                
m.Invoke(cparameters);
        }
    }
    
    public static 
void InvokeMethodUpwards(this GameObject gameObjectstring methodName)
    {
        
Transform t gameObject.transform;
        while (
t)
        {
            foreach (
Component c in t.gameObject.GetComponents<Component>())
            {
                
MethodInfo m c.GetType().GetMethod(methodName);
                if (
!= null)
                    
m.Invoke(cnull);
            }
            
t.parent;
        }
    }

    public static 
void InvokeMethodUpwards(this GameObject gameObjectstring methodNameparams object[] parameters)
    {
        
Transform t gameObject.transform;
        while (
t)
        {
            foreach (
Component c in t.gameObject.GetComponents<Component>())
            {
                
MethodInfo m c.GetType().GetMethod(methodName);
                if (
!= null)
                    
m.Invoke(cparameters);
            }
            
t.parent;
        }
    }

    public static 
void BroadcastMethod(this GameObject gameObjectstring methodName)
    {
        foreach (
Component c in gameObject.GetComponentsInChildren<Component>())
        {
            
MethodInfo m c.GetType().GetMethod(methodName);
            if (
!= null)
                
m.Invoke(cnull);
        }
    }

    public static 
void BroadcastMethod(this GameObject gameObjectstring methodNameparams object[] parameters)
    {
        foreach (
Component c in gameObject.GetComponentsInChildren<Component>())
        {
            
MethodInfo m c.GetType().GetMethod(methodName);
            if (
!= null)
                
m.Invoke(cparameters);
        }
    }






Жека 18.08.2016 06:14

Ответ: MonkeyBehaviour
 
Цитата:

Просто при старте игры проверять код на наличие нужных функций и если есть, то делать из найденных функций делегат
а куда денется исходный метод, он же останется.
например, если завернуть Update в делегат.
или ты про другой случай? какой?

Также можно расширить MonoBehaviour или Component своими функциями и использовать их.
Как вариант - навешивать на корневой объект сцены некий менеджер скриптов, который пробежит по всем дочерним объектам и компонентам, соберёт все экземпляры твоего переопределённого Behaviour'а, и менеджер будет в юнитевских методах Start, Update и прочих пробегать по спискам и вызывать делегаты.
По части Update получается модель привычного gameloop'a, где все логики перебираются и выполняются.

Однако, тогда не получим фушку с очередностью выполнения скриптов. Но можно наверное и с ней заморочиться.
Это всё теория, даст ли оно профит, чтоб стоило с этим извращаться.)

Пример с классом Message - тормозное решение, конечно.
Да и вызов через строковое имя метода опасен - отрефакторишь имя функции и "привет".

Жека 19.08.2016 15:36

Ответ: MonkeyBehaviour
 
К знатокам юнити и прочих движков.

Как лучше считать глобальную позицию и масштаб объектов?

Например, хочу сделать свойства localPosition и position.
Local возвращает просто вектор, а не-local делает проход по всем родителям и вычисляет позицию с учётом родительских позиций, масштабов, поворотов.

Так обычно делают или нет?

UPD: второй вариант - пересчитывать трансформы дочерних элементов при изменении свойств в родителе.
Для манки проблематично юзать такой подход, т.к. здесь все структуры - это классы, и нельзя запретить
изменение переменных внутри структуры.
То есть:

Если в юнити написать
transform.position.x = 200;
мы получим ошибку при компиляции.
За счёт этого можно перехватывать назначение переменной transform.position (а не поля "x" в структуре Vector) - и делать необходимые пересчёты.
В манки так сделать нельзя из-за ограничений языка.

Надо глянуть в сторону продвинутого Monkey2.

mingw 20.08.2016 10:20

Ответ: MonkeyBehaviour
 
Эм... в любом компоненте, обязательно должны быть только 3-4 метода... каждый называет их по своему, но я их назову так : Load(), Free(), Loop(dt#) и Draw() - думаю смысл их понятен. Все остальное - детали движка, которые легко реализовываются при помощи вышеописанных методов. Хотите LateUpdate() ? - Создайте хук, прицепите к нему нужный объект в методе Load() и вызывайте этот хук после всех обновлений... и выглядеть будет красиво и с ООП не придется заниматься сексом.

насчет второго вопроса... ну как бэ название говорит само за себя... localPosition - позиция объекта в локальной системе координат своего родителя. соотв. position - позиция относительно мировой системы координат. И для того, что бы узнать глобальное свойство без перебора всех родителей не обойтись ибо каждый родитель влияет на результат.

Кирпи4 20.08.2016 14:36

Ответ: MonkeyBehaviour
 
Цитата:

Сообщение от Жека (Сообщение 307917)
К знатокам юнити и прочих движков.

Как лучше считать глобальную позицию и масштаб объектов?

Например, хочу сделать свойства localPosition и position.
Local возвращает просто вектор, а не-local делает проход по всем родителям и вычисляет позицию с учётом родительских позиций, масштабов, поворотов.

Так обычно делают или нет?

Это очень интересный вопрос. Однако, скажу что у меня сделано так:
У каждого объекта хранится полная трансформация в виде матрицы, а переменные позиций и углов находятся в локальных координатах. Дети обновляются только если изменилась трансформация родителя.

https://github.com/clashbyte/spriteb...orld/Entity.cs

Жека 20.08.2016 16:58

Ответ: MonkeyBehaviour
 
Цитата:

Хотите LateUpdate() ? - Создайте хук, прицепите к нему нужный объект в методе Load() и вызывайте этот хук после всех обновлений.
Можешь пояснить подробнее?
Псевдокод или в рамках юнити сущностей.

Жека 20.08.2016 17:12

Ответ: MonkeyBehaviour
 
Кирпи4, в твоём коде есть Vector3 и Vec3, зачем так.

Кирпи4 20.08.2016 17:15

Ответ: MonkeyBehaviour
 
Цитата:

Сообщение от Жека (Сообщение 307939)
Кирпи4, в твоём коде есть Vector3 и Vec3, зачем так.

Vector3 - это тип OpenTK, а Vec3 - мой тип, юзается при пришивании библиотеки к редактору, чтобы от OpenTK зависел только этот проект

mingw 20.08.2016 22:05

Ответ: 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)


Часовой пояс GMT +4, время: 12:37.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot