Jump to content
UnitySpain
DevNullsp

Llamadas en UNITY

Recommended Posts

Buenas,

Este es un tema que supongo árido, pero he visto que generalmente en los cursos de formación suelen centrar el código en un fichero algo así como el "controlador" del juego, con lo que hay un único método "update".

La pregunta es:

¿Realmente afecta al rendimiento el tener un metodo update a que cada gameobject tenga su update()?

Supongamos que en un momento dado tengo 200 GameObjects generados, ¿Es malo que el sistema tenga que llamar a 200 updates? ¿Realmente los llama aunque no se defina un script? 

Es que esto del controlador me recuerda a algunos métodos de codificación que acabábamos llamándolos "codificación spaguetty"

Saludos

Share this post


Link to post
Share on other sites

Obviamente, Unity está diseñado en torno a la idea de Actualización (Update) (y los otros métodos de eventos como Awake o Start), pero también es obvio que tener muchos eventos puede afectar el rendimiento. Mi consejo es: si no necesitas algo, no lo tengas allí. Si el componente no tiene código dentro de su método de actualización, elimínalo.
Aparte de eso, si tienes una cantidad extremadamente alta de cosas que suceden en tu juego, debes preguntarte si todas deben suceder en cada frame y, si no, implementar su propio "planificador de tareas" (por cierto, usar las corrutinas para hacer eso por ti puede causar un impacto en el rendimiento aún mayor que el uso del Update, dependiendo de la cantidad de corrutinas que tengas y lo que hacen exactamente) o algún otro "manager" de ese tipo.

Una llamada a Update tiene una carga, por lo que si tiene 1000 objetos con Update, entonces tiene esa carga X 1000. Si tienes una gran cantidad de objetos que necesitan actualizarse en cada frame, he escuchado que sería más eficiente tener una único Update que utiliza un bucle para recorrer los objetos y realiza las operaciones necesarias para cada uno dentro del bucle.

Esto te puede ser útil: https://blogs.unity3d.com/2015/12/23/1k-update-calls/

Edited by B2Lotus

Share this post


Link to post
Share on other sites

Estaba por linkear exactamente ese blog @B2Lotus, me acordaba que trataba del tema.

22 hours ago, DevNullsp said:

¿Realmente afecta al rendimiento el tener un metodo update a que cada gameobject tenga su update()?

Depende, si hacés una prueba en una escena vacía, con 200 objetos vacíos con un script que no haga nada, probablemente al final tengas una mejora de x10 (esto tirando un valor cualquiera), entonces terminás el test y anotás en tu cuaderno de test "Siempre hacer X método, da una mejora de Y" (como catchear el Time.deltaTime era casi x3 de mejora vs no catchearlo, pero era el 0.2% del total del tiempo de frame), esto no es un error, pero en contexto no significa absolutamente nada, es el típico problema de optimización prematura, se ve solo lo que se gana y no lo que se pierde. En este caso sí, tener objetos autónomos(Update) vs manejados(Custom Manager) te va a dar una mejora, haga lo que haga el script, el overhead típico de las llamadas al monobehaviour (y lo que haga Unity de fondo) no lo vas a tener,. Esto es lo que ganás, pero y lo que perdés? Como dice el blog,:

To be honest, this test is not completely fair. Unity does a great job guarding you and your game from unintended behavior and crashes: Is this GameObject active? Wasn’t it destroyed during this update loop? Does Update method exist on the object? What to do with a MonoBehaviour created during this update loop?  — my manager script doesn’t handle anything of that, it just iterates through a list of objects to update.

Osea que el enfoque clásico se encarga de algunas cosas internas que, pueden no ser relevantes pero que ahí están, supongo que si usar uno u otro va a depender más del tipo de problema que querés resolver, además de que si es lógico desde el punto de vista de diseño de tu código, cómo es que podés aprovechar ese Custom Editor, y demás. Por ej yo uso un manager que actualiza a todos los personajes (todos kinemáticos), por qué? porque con un solo manager puedo cambiar el método Update por un FixedUpdate, y en X situación puedo decir Personaje i actualizate y luego plataforma j (o viceversa), ambos con dos componentes distintos, haciendo esto no dependo el ExecutionOrder global (que te impone este componente va antes y este otro va después). Este es un caso ejemplo de por qué usar un esquema así.

22 hours ago, DevNullsp said:

Supongamos que en un momento dado tengo 200 GameObjects generados, ¿Es malo que el sistema tenga que llamar a 200 updates? ¿Realmente los llama aunque no se defina un script?

200? no creo, ni cerca, igual el número no te dice nada, lo que realmente importa es lo que hace cada uno de esos dentro, pero hablando solo del overhead no creo que 200 sea tanto, quizás 2000 para empezar a hablar, igual no me creas, medilo vos mismo con el profiler (development build con autoconnect profiler).

Share this post


Link to post
Share on other sites

Yo creo que esta bien rallarse con estas cosas pero tampoco mucho, al final los PC hacen millones de cosas por segundo sin inmutarse. Si me disces en los años 80 o 90 ahi si que tenian que optimizar muchisimo los juegos unas decenas de lineas de más echaban al traste el juego ( de hecho hay muchos juegos que no se ni como los hacian con tan poca memoria).

 Muchas veces me he rallado con añadir o no algunas cosas en el update y despues de añadirla ver que no afecta nada en absoluto, el PC se lo pasa por el forro. Incluso los moviles actuales son capaces de mover juegos muy pesados y con gráficos bastante decentes aunque no esta de más tener la optimizacion siempre en mente.

Share this post


Link to post
Share on other sites

Yo, por el contrario, veo con buenos ojos investigar acerca de cómo optimizar el código, aunque sea por mero interés personal, que es el caso que nos ocupa con @DevNullsp. A la larga, es una buena práctica para ser eficiente en tu trabajo y no tener que quedarte después a arreglar errores...

Al igual que pasa con los Using que no necesitamos en la cabecera de los scripts, suele haber casos en los que nos interesa quitar los métodos por defecto, para que no ocupen en memoria, ni procesen más de la cuenta.

 

En ese aspecto, el único debate conflictivo que he encontrado es que el Checkbox visual que aparece para activar/desactivar un script en el Inspector de Unity3D depende exclusivamente de que exista un Awake o un Start definido en dicho script. Eso conlleva a la sobrecarga anteriormente mencionada...

Share this post


Link to post
Share on other sites
2 hours ago, zelleGames said:

Yo creo que esta bien rallarse con estas cosas pero tampoco mucho, al final los PC hacen millones de cosas por segundo sin inmutarse. Si me disces en los años 80 o 90 ahi si que tenian que optimizar muchisimo los juegos unas decenas de lineas de más echaban al traste el juego ( de hecho hay muchos juegos que no se ni como los hacian con tan poca memoria).

 Muchas veces me he rallado con añadir o no algunas cosas en el update y despues de añadirla ver que no afecta nada en absoluto, el PC se lo pasa por el forro. Incluso los moviles actuales son capaces de mover juegos muy pesados y con gráficos bastante decentes aunque no esta de más tener la optimizacion siempre en mente.

Hace no mucho tiempo tuve que rehacer el código de un sistema de movimiento en cuadricula, los fps mejoraron de 40-45 a 60 fijos.

Optimizar el código es una buena practica, sin embargo, se deja notar más a largo plazo.

Share this post


Link to post
Share on other sites
20 hours ago, pioj said:

Yo, por el contrario, veo con buenos ojos investigar acerca de cómo optimizar el código, aunque sea por mero interés personal, que es el caso que nos ocupa con @DevNullsp. A la larga, es una buena práctica para ser eficiente en tu trabajo y no tener que quedarte después a arreglar errores...

Esta claro, es como en la vida si adquieres rutinas y buenas practicas mucho mejor al igual que ser organizado, al final lo haces sin darte cuenta y eso que ganas. En mi primer juego todo era un caos, los modelos todos juntos en una carpeta, los scripts sin organizar ni documentar, etc... ahora que me he metido en la cabeza ser organizado se trabaja mucho mejor y se nota.

 

20 hours ago, pioj said:

Al igual que pasa con los Using que no necesitamos en la cabecera de los scripts, suele haber casos en los que nos interesa quitar los métodos por defecto, para que no ocupen en memoria, ni procesen más de la cuenta.

Una pregunta sobre esto, yo a veces uso por ejemplo esto para dar formato a un String:

String.Format("{0:0,0}", PlayerProfile.playerProfile.tokens);

y arriba añado "using System"

 

¿Seria más eficiente ponerlo asi directamente sin añadir using?

System.String.Format("{0:0,0}", PlayerProfile.playerProfile.tokens);

 

20 hours ago, pioj said:

En ese aspecto, el único debate conflictivo que he encontrado es que el Checkbox visual que aparece para activar/desactivar un script en el Inspector de Unity3D depende exclusivamente de que exista un Awake o un Start definido en dicho script.

Asi que por esto es, alguna vez me pregunte porque algunos scripts me salían sin ese checkbox, bueno saberlo.

Share this post


Link to post
Share on other sites
2 hours ago, zelleGames said:

y arriba añado "using System"

 

¿Seria más eficiente ponerlo asi directamente sin añadir using?

System.String.Format("{0:0,0}", PlayerProfile.playerProfile.tokens);

Más eficiente hablando de performance? para nada (o por lo menos estoy casi seguro). Lo que sí te puede afectar es en tiempo de compilación, es lógico ya que a la hora de ensamblar se tiene que relacionar todo el namespace con las referencias y demás. Olvidate, si te saca el dolor de cabeza usalo sin problemas, yo era de los que ponía "UnityEngine.UI.Image" en vez de "Image". Si fuera un caso en particular de una sola línea, bueno está bárbaro, muchas veces con System me pasa, no es gran problema, ponés "System.X", incluso es más rápido.

Además puede quedar feo de leer, o puede que el zoom que estás usando en tu editor haga que la línea de código ocupe toda la pantalla, este tipo de cosas puede no ser relevante, pero programar 12 horas por día (o más) teniendo que lidiar con el zoom o moviendo la barrita horizontal de izq a der a cada rato te puede llevar a comprarte un dragon y quemar toda la ciudad de un momento a otro.

 

2 hours ago, zelleGames said:
23 hours ago, pioj said:

En ese aspecto, el único debate conflictivo que he encontrado es que el Checkbox visual que aparece para activar/desactivar un script en el Inspector de Unity3D depende exclusivamente de que exista un Awake o un Start definido en dicho script.

Asi que por esto es, alguna vez me pregunte porque algunos scripts me salían sin ese checkbox, bueno saberlo.

Ese checkBox sale solo con:

  • Start
  • OnEnable
  • Update
  • LateUpdate
  • FixedUpdate
  • etc (los que me estoy olvidando)

Mientras que Awake se corre si el gameObject está activado, una única vez. En cambio Start se corre si el componente se activa, también una sola vez (componente activado significa GameObject activado jerarquicamente, él junto a todos sus padres ). Si querés que se corra cada vez que se activa tenés OnEnable. Yo siempre los relacioné así:

  • Awake ---> GameObject (Inicialización Hardcore, )
  • OnEnable, Start, Update, etc ---> Componente (el checkBox)

 

Edited by lightbug

Share this post


Link to post
Share on other sites

Hola a todos!

El problema de las funciones predefinidas de Unity es que utilizan System.Reflection para en tiempo real saber si una class tiene una o tal función como Update, con la consiguiente sobrecarga. En un prototipo que hice que utilizaba muchos Updates que tenían otros gameobjects con sus Updates (miles y operaciones de cálculo), el crear un solo Update y que luego llamara al resto optimizó mucho todo el proceso (comprobado vía profiler).

Respecto a los using no referenciados, Visual Studio como mínimo desde la 2017 te los deshabilita automáticamente (aparecen en gris). Y luego el hecho de que lo referencies completamente o a través de using no creo que afecte en nada. Los compiladores de hoy en día están muy optimizados.

 

Share this post


Link to post
Share on other sites

Gracias por las respuestas....

Ha sido muy productivo.

Pero la idea iba mas encaminada a que usar una rutina central para ejecutar todo hace que tus elementos no sean "exportables" o que para hacerlos tengas que hacer maravillas.

tachan! código spagetty.

Como programador me gusta el concepto de encapsulamiento, el objeto y sus métodos todos o sea el prefab con todo lo que necesita por ejemplo un inventario, el inventario no es más que un objeto con una colección de referencias que puede abrirse, ordenarse, etc etc, esto se puede aislar creando un sistema de inventarios independiente del modo de presentación.

Otro ejemplo puede ser la representación interna de un bufo o de una magia, que lleva su control de tiempo, numero de cargas, etc etc... 

A mi me resulta más logico tratarlos independentiemente del juego y luego unir mediante eventos/acciones/estados (perdonad por este tema que no domino) la capa gráfica con la "programática".

Además sin hablar de objetos destruidos o creados y su control centralizado que tendria que estar controlandose mediante arrays o lista, tal y como describe el blog.

Y así con todos los "componentes" que pueblan un juego actualmente.

También tengo la curiosidad por que las versiones van avanzando y supongo que el sistema a la hora de compilar hará todo tipo de mejoras de "performance" incrementando estas entre versiones. Pero todas las discusiones que habia visto eran del año 2014 aproximadamente con lo cual ya ha llovido desde entonces y nadie ha vuelto a revisar los datos :S

Un Saludo,

 

@iRobb

 

On 6/5/2019 at 8:34 PM, iRobb said:

Hola a todos!

El problema de las funciones predefinidas de Unity es que utilizan System.Reflection para en tiempo real saber si una class tiene una o tal función como Update, con la consiguiente sobrecarga. 

Perdona iRobb me ha llamdo mucho la atención esto por que es tan evidente que es un problema que lo he buscado y en principio parece que no es así o casi.... este thread es mas moderno.

https://gamedev.stackexchange.com/questions/164892/why-does-unity-use-reflection-to-get-the-update-method

Parece que lo hace en la carga de clase o en el primer uso... no lo tengo claro ya que por firs time type accessed no se si es al cargar la clase o al usarla.

"Instead, the first time a MonoBehaviour of a given type is accessed the underlying script is inspected through scripting runtime (either Mono or IL2CPP) whether it has any magic methods defined and this information is cached"

Un Saludo,

Edited by DevNullsp

Share this post


Link to post
Share on other sites

Gracias. Le pegaré un ojo.

Lo que si puedo asegurar por experiencia personal y, versión de Unity 2018.4, es que con muchos gameobjects con Update vs un solo gameobject que llame al resto de fake Updates directamente el rendimiento es mucho más alto.

Share this post


Link to post
Share on other sites

Pues entonces... manos a la obra! que lo que se hace uno siempre se comprende mejor....

<momento desarrollo>

Había respondido pero he revisado el código por que no estaba muy convencido del resultado....

Mi conclusión es que hay  diferencia, no una muy exagerada.. Aunque depende de muchos factores y sobre todo de tener muchos ojetos y por muchos me refiero a decenas de miles.

Me da que esto lo medio arreglaron en la 19 ya que parece que los objetos se crean y quizás el tiempo de carga mejore, yo lo veo imperceptible.

ahora bien si en la ejecución están instanciandose y borrandose miles de elementos no dudo que debido al cálculo inicial para asignarlo a la tabla interna de Update's si hay una perdida de rendimiento sobre todo si está usando introspección.... Pero en esto no lo puedo ni confirmar ni desmentir :)

 for (int i = 0; i < totalObjetos; i++)
            lista[i] = Instantiate(myPrefab).GetComponent<UpdateReal2>().UpdateFake;

Pero mantener nuevos o destruir existentes es otra cosa... no lo veo sencillo.

Adjunto el minipoyecto por si alguien quiere repasarmelo...

Simplemente en un proyecto nuevo descargar el zip en el raiz

Gracias

Edito para agregar proyecto en github

https://github.com/devnullsp/Unity-PruebaRendimiento.git

 

Edited by DevNullsp

Share this post


Link to post
Share on other sites
4 hours ago, DevNullsp said:

Pues entonces... manos a la obra! que lo que se hace uno siempre se comprende mejor....

<momento desarrollo>

Había respondido pero he revisado el código por que no estaba muy convencido del resultado....

Mi conclusión es que hay  diferencia, no una muy exagerada.. Aunque depende de muchos factores y sobre todo de tener muchos ojetos y por muchos me refiero a decenas de miles.

Me da que esto lo medio arreglaron en la 19 ya que parece que los objetos se crean y quizás el tiempo de carga mejore, yo lo veo imperceptible.

ahora bien si en la ejecución están instanciandose y borrandose miles de elementos no dudo que debido al cálculo inicial para asignarlo a la tabla interna de Update's si hay una perdida de rendimiento sobre todo si está usando introspección.... Pero en esto no lo puedo ni confirmar ni desmentir :)


 for (int i = 0; i < totalObjetos; i++)
            lista[i] = Instantiate(myPrefab).GetComponent<UpdateReal2>().UpdateFake;

Pero mantener nuevos o destruir existentes es otra cosa... no lo veo sencillo.

Adjunto el minipoyecto por si alguien quiere repasarmelo...

Simplemente en un proyecto nuevo descargar el zip en el raiz

Gracias

Edito para agregar proyecto en github

https://github.com/devnullsp/Unity-PruebaRendimiento.git

 

Yo hablo de utilizar en los Updates cálculos y muchos objetos. Explico mi caso a ver si se entiende mejor:

En un juego náutico 3d donde cada objeto (lancha) tiene que controlar su flotabilidad en cálculo y físicas. Además dejan "trazas" de espuma que van con el movimiento y la velocidad. Todas las trazas se crean en tiempo real y constan de cientos de meshes que se colocan en base al sentido y su alpha basado en el tiempo. La instanciación la realizo vía pooling con una carga previa de unos 4k/5k meshes (cada lancha unos 1k/1,5k) donde cada uno tiene su propio Update virtual.

Anteriormente era el Update de Unity y según profiler, ya no me acuerdo de los datos exactos, no conseguía superar los 40/50 fps. Al cambiarlo a Update virtual está casi siempre en los 60fps. Como comento al realizarlo vía pooling, no es la instanciación de objetos sino las llamadas.

Está claro que con unos cientos de Updates y sin demasiado cálculo la diferencia no se va a notar. Cuando el tema sube, ya es otra cosa. Espero sirva :D

 

Share this post


Link to post
Share on other sites

Podrias crear una extensión de MonoBehaviour 

public abstract class GameBehaviour : MonoBehaviour
{
    protected abstract void OnAwake();
    protected abstract void OnStart();
    protected abstract void OnUpdate();

    private static List<GameBehaviour> m_instances = new List<GameBehaviour>();

    private void Awake()
    {
        m_instances.Add(this);
        OnAwake();
    }

    private void Start()
    {
        OnStart();
    }

    // o utilizar OnDestroy()
    ~ GameBehaviour()
    {
        m_instances.Remove(this);
    }

    public static void UpdateBehaviour()
    {
        for(int i = 0; i < m_instances.count; i++)
        {
            if(m_instances[i] == null)
            {
                m_instances.RemoveAt(i);
                continue;
            }
            m_instances[i].OnUpdate();
        }
    }
}

Tendrías que llamar desde tu GameManager a GameBehaviour.UpdateBehaviour() y tendrias todos los Game Behaviours en 1 solo update.

Share this post


Link to post
Share on other sites

×
×
  • Create New...