Unirban legends (I)

Cuando empezamos a trabajar con una tecnología los primeros meses los pasamos escuchando multitud de leyes o consejos que supuestamente harán nuestra vida algo más cómoda, pero nunca estamos seguros de si será verdad lo que nos cuentan o terminará siendo una suposición poco acertada.

Conectando con esto, el desarrollo de juegos de móvil en Unity está lleno de estas reglas: “No hagas esto”, “cachea el transform”, “evita las físicas si no las necesitas”… Si nos fijamos un poco, podemos ver que todas giran alrededor de lo mismo: La optimización.

¿Por qué la optimización? Bueno, la optimización es el mayor problema técnico de los juegos de móviles una vez superado el bache inicial que es acostumbrarse a programar el control. Los juegos de móvil tienen unas limitaciones claras en cuanto a CPU y GPU que sacrifican en pos de la portabilidad.

¿Cómo podemos comprobar que todo lo que nos cuentan es verdad? Pues en programación, y especialmente en programación de videojuegos, tenemos una manera muy concreta: Medir.

Al final, medir es lo que hacemos al darle al play con cada cambio de código. Estamos comprobando que lo que acabamos de programar es mejor que lo que teníamos hace unos minutos.

Para ejemplificar esto que acabamos de definir, vamos a hacer una batería de pruebas con alguna de las suposiciones que creemos sobre Unity, el movimiento y el uso de Tweeners entre ellas.


Nuestra batería consistirá en dos sencillas pruebas, mover 70.000 cubos hacía la derecha constantemente y rotar en un solo eje esos mismos 70.000 cubos constantemente. Ambos son operaciones lineales por simplificar las pruebas y que los datos sean mas leíbles.

Las maneras de solucionar las pruebas son las siguientes:

  • Por transform:
    • En el update de un solo controlador con un array con los transforms.
    • En el update de un solo controlador con una lista con los transforms.
    • En el update de un solo controlador con una lista recorriendo con foreach.
    • En el update de cada uno de los cubos, moviendo el transform.
  • Por físicas de Unity. Rigidbody.
  • DOTween
  • iTween.

Hemos metido como Tweeners DoTween y iTween, que son de los más conocidos en la escena de Unity, aparte de ser muy fáciles de utilizar.

Las pruebas:

Gif del movimiento en update individuales (70k llamadas)
Gif de movimiento con array en un solo update

 

 

 

 

 

 

 

Antes de ejecutar las pruebas podíamos suponer algunas cosas:

  • Ejecutar 70.000 veces una llamada de Update tiene que ser más lento que hacerlo con el array.
  • A su vez, la lista y más especialmente el foreach debería ser igualmente, más lento que el array.
  • Los tweeners, son mucho mas lentos que todo lo demás.
  • Las físicas no se deberían usar para esto, así que probablemente den resultados parecidos a los tweener.

Veamos las pruebas:

(Los datos están en deltaTime (tiempo por frame))

El ránking de la prueba de la posición quedaría con el script unitario sacando prácticamente el mismo benchmark de 120ms en las tres variaciones (las hay si usamos otros métodos de medición, pero eran bastante despreciables), DOTween ocupando un honroso segundo puesto, seguido por casi 100ms más del método de los Update Individuales, y iTween y las físicas ocupando los últimos puestos.

Las rotaciones sueltan valores parecidos, pero en esta prueba si que ha habido diferencia entre el foreach y usar una lista o no, además de que vemos que las físicas manejan mejor la velocidad angular, junto a que DOTween sorprende con el primer puesto.

Al final el ránking con la media de las dos pruebas quedaría así:

  • 134.5ms | Update grupal con array : Los arrays son baratos de recorrer y tener una sola llamada a función (un Update) es muy eficiente respecto a otras opciones.
  • 135ms | Update grupal con lista: La lista es ligeramente más costosa de recorrer que un array primitivo.
  • 140ms | Update grupal con lista y foreach: El foreach tiene una variable local extra, que ralentiza un poco la ejecución por iteración.
  • 147.5ms | DOTween: Está es de las primeras sorpresas. DOTween es un tweener con especial cuidado en la optimización, y si vamos al profiler veremos que todos los tweens que hemos puesto (70k) solo ocupan una call en CPU.
  • 255ms | Update individual: Un update individual por cubo genera una llamada por cada cubo que haya en pantalla. Esto trae una sobrecarga extra que se nota claramente en las pruebas.
  • 2121.5ms | iTween: Comparando con DOTween, que solo te genera una llamada en la CPU y además es más eficiente que un método “nativo” como es el de los updates individuales, iTween genera más de un Update por tween, un LateUpdate y un FixedUpdate que hacen que la sobrecarga sea terrible.
Profiler de iTween
  • 7000ms | Physics: Usar la velocidad de los RigidBody para mover objetos, o rotarlos con angularVelocity si no necesitamos detección de colisiones compleja, es mala idea. Las demás pruebas (menos iTween) aún son factibles para mover pocas cosas, esto queda lejos de lo deseable.
La prueba del DOTween

Conclusiones finales: 

Seguir una especie de método científico a la hora de programar es algo totalmente sano y muy recomendable para ir mejorando tus prácticas día a día. Con un testeo continuo y una introspección de nuestras maneras a la hora de picar código conseguiremos ser mejores profesionales y desenterrar esos mitos ocultos que pueden condicionar como hacemos las cosas.

Esta prueba ha sido realizada en un MacBook Air de 2016, con 8GB de RAM.

El código de la prueba es público y puede ser consultada aquí.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Crea un sitio web o blog en WordPress.com

Subir ↑

A %d blogueros les gusta esto: