Presentamos nuestro nuevo libro electrónico: Pila tecnológica orientada a datos (DOTS) de Unity para desarrolladores avanzados

THOMAS KROGH-JACOBSEN / UNITY TECHNOLOGIESSenior Technical Content Marketing Manager
May 30, 2024|9 minutos
Presentamos nuestro nuevo libro electrónico: Pila tecnológica orientada a datos (DOTS) de Unity para desarrolladores avanzados
Para tu comodidad, tradujimos esta página mediante traducción automática. No podemos garantizar la precisión ni la confiabilidad del contenido traducido. Si tienes alguna duda sobre la precisión del contenido traducido, consulta la versión oficial en inglés de la página web.

La pila tecnológica orientada a datos (DOTS) de Unity te permite crear juegos complejos a gran escala gracias a un conjunto de herramientas de mejora del rendimiento que te ayudan a sacar el máximo partido del hardware de destino.

Este libro electrónico de más de 50 páginas, Introducción a la pila tecnológica orientada a datos para desarrolladores avanzados de Unityya está disponible para su descarga gratuita. Utilícelo como manual para comprender mejor la programación orientada a datos y evaluar si DOTS es la opción adecuada para su próximo proyecto. Tanto si quieres empezar un nuevo proyecto basado en DOTS, como si quieres implementar DOTS en partes críticas de rendimiento de tu juego basado en Monobehaviour, esta guía cubre todo el terreno necesario de forma estructurada y clara.

Con Unity 6 en vista previa y DOTS 1.0 listo para producción, este es un buen momento para explorar las oportunidades que ofrece DOTS. El libro electrónico, escrito por Brian Will, ingeniero de software sénior de Unity, se une a las muestras actualizadas de Unity Learn, al reciente bootcamp de DOTS y a las muestras de GitHub en la colección de recursos disponibles para los desarrolladores que quieran aprender a trabajar con DOTS.

Una guía para ayudarte a decidir si DOTS es la opción adecuada para tu juego
En el Sistema de Componentes de Entidades de Unity, todas las entidades con el mismo conjunto de tipos de componentes se almacenan juntas en el mismo "arquetipo".

Nuestro objetivo con este libro electrónico es ayudarle a tomar una decisión informada sobre si la implementación de algunos o todos los paquetes y tecnologías DOTS es la decisión correcta para su proyecto Unity actual o futuro. Cada parte de la pila desempeña un papel en la mejora de la velocidad de ejecución y la eficacia de un juego. La guía pretende explicar cada una de estas partes, cómo pueden utilizarse conjuntamente y su base común, el Sistema de Componentes de Entidades (ECS) de Unity.

Una de las principales razones para utilizar DOTS es obtener el máximo rendimiento de su hardware de destino, y esto requiere la comprensión de multithreading y asignación de memoria. Además, para aprovechar DOTS, tendrá que diseñar su código y proyectos orientados a datos de forma diferente a sus proyectos Monobehaviour basados en C# con su mayor nivel de abstracción.

Veamos con más detalle lo que encontrará en el libro electrónico.

CTA: Descargar Introducción a la pila tecnológica orientada a datos para desarrolladores avanzados de Unity.

¿Qué contiene el libro electrónico DOTS?
 Una escena de la muestra Bomberos, disponible en EntityComponentSystemSamples Github

La primera sección de la guía, que hemos incluido a continuación, presenta algunos de los factores que pueden contribuir a un rendimiento deficiente de la CPU en un juego, como la sobrecarga de la recogida de basura, los datos y el código que no son compatibles con la caché, el código máquina generado por el compilador que no es óptimo, etc.

En la siguiente sección se explica cómo cada uno de los paquetes y funciones DOTS facilitan la escritura de código que evite los problemas de rendimiento de la CPU. Encontrará explicaciones útiles para:

  • Sistema de trabajo C
  • Compilador de ráfagas
  • Colecciones
  • Matemáticas
  • Entidades
  • Entidades Gráficas
  • Unidad Física
  • Netcode para entidades

Después de un resumen de cada parte de la pila, tendrás una introducción al repositorio de GitHub EntityComponentSystemSamples, que incluye muchos ejemplos que introducen características básicas y avanzadas de DOTS. Algunos de los ejemplos del repositorio de Github se reproducen en un nuevo curso de Unity Learn sobre DOTS, Get acquainted with DOTS.

La otra sección clave de la guía DOTS es el apéndice. Es aquí donde Brian Will ofrece explicaciones detalladas sobre conceptos relacionados con Unity ECS, como la asignación de memoria y la recogida de basura, la memoria y la caché de la CPU, la programación multihilo, las limitaciones de la programación orientada a objetos y la programación orientada a datos.

Extracto: Sobre el rendimiento
Un perfil del Unity Profiler mostrando trabajos compilados en ráfaga utilizando el potencial de la CPU y ejecutándose a través de muchos hilos de trabajo.

Si eres un desarrollador de juegos experimentado, sabrás que la optimización del rendimiento en las plataformas de destino es una tarea que atraviesa todo el ciclo de desarrollo. Puede que tu juego funcione bien en un PC de gama alta, pero ¿qué pasa con las plataformas móviles de gama baja a las que también te diriges? ¿Los cuadros tardan mucho más que otros, creando contratiempos notables? ¿Los tiempos de carga son demasiado largos y el juego se bloquea durante segundos cada vez que el jugador cruza una puerta? En este caso, no sólo la experiencia actual es deficiente, sino que se impide añadir más funciones: Más detalle y escala del entorno, mecánicas, personajes y comportamientos, física y plataformas.

¿Cuál es el culpable? En muchos proyectos es el renderizado: Las texturas son demasiado grandes, las mallas demasiado complejas, los sombreadores demasiado caros, o no se hace un uso eficaz de la dosificación, la selección y el LOD.

Otro escollo habitual es el uso excesivo de colisionadores de malla complejos, que aumentan el coste de la simulación física. O, la propia simulación del juego es lenta. El código C# que has escrito y que define lo que hace que tu juego sea único puede estar consumiendo demasiados milisegundos de CPU por fotograma.

Entonces, ¿cómo se escribe código de juego que sea rápido, o al menos no lento?

En décadas anteriores, los desarrolladores de juegos para PC solían resolver este problema simplemente esperando. Desde la década de 1970 y hasta el siglo XXI, el rendimiento de un solo hilo de la CPU se duplicaba cada pocos años (un fenómeno conocido como ley de Moore), por lo que un juego de PC se volvía "mágicamente" más rápido a lo largo de su ciclo de vida. En las dos últimas décadas, sin embargo, las mejoras de rendimiento de un solo hilo de la CPU han sido relativamente modestas. En cambio, el número de núcleos de la CPU ha ido creciendo e incluso los dispositivos portátiles pequeños, como los smartphones, incorporan hoy varios núcleos. Además, la brecha entre los dispositivos de juego de gama alta y los de gama baja se ha ampliado, y una gran parte de la base de jugadores utiliza hardware de varios años de antigüedad. Esperar a un hardware más rápido ya no parece una estrategia viable.

La pregunta que hay que hacerse, entonces, es "¿Por qué mi código CPU es lento en primer lugar?". Existen varios escollos comunes:

  • La recogida de basura induce una sobrecarga y pausas notables: Esto ocurre porque el recolector de basura sirve como gestor automático de memoria que gestiona la asignación y liberación de memoria para una aplicación. La recogida de basura no sólo supone una sobrecarga para la CPU y la memoria, sino que a veces detiene la ejecución del código durante varios milisegundos. Los usuarios pueden experimentar estas pausas como pequeños tirones o tartamudeos más intrusivos.
  • El código máquina generado por el compilador no es óptimo: Algunos compiladores generan código mucho menos optimizado que otros, con resultados que varían según las plataformas.
  • Los núcleos de la CPU están insuficientemente utilizados: Aunque los dispositivos de gama más baja de hoy en día tienen CPU multinúcleo, muchos juegos simplemente mantienen la mayor parte de su lógica en el hilo principal porque escribir código multihilo suele ser difícil y propenso a errores.
  • Los datos no se almacenan en caché: Acceder a los datos desde la caché es mucho más rápido que hacerlo desde la memoria principal. Sin embargo, acceder a la memoria del sistema puede requerir que la CPU se siente y espere durante cientos de ciclos de CPU; en su lugar, usted quiere que la CPU lea y escriba datos de su caché tanto como sea posible. La forma más sencilla de organizar esto es leer y escribir en la memoria de forma secuencial, por lo que la forma más cómoda de almacenar los datos en caché es en matrices contiguas muy juntas. Por el contrario, si los datos están distribuidos de forma no contigua por toda la memoria, el acceso a ellos provocará muchas pérdidas de caché costosas; la CPU solicita datos que no están presentes en la memoria caché y, en su lugar, tiene que recuperarlos de la memoria principal, más lenta.
  • El código no es compatible con la caché: Cuando se ejecuta el código, debe cargarse desde la memoria del sistema si no está ya en la caché. Una estrategia es favorecer la llamada a una función en el menor número de lugares posible para reducir la frecuencia con la que debe cargarse desde la memoria del sistema. Por ejemplo, en lugar de llamar a una función concreta en varios lugares repartidos por el fotograma, es mejor llamarla en un único bucle para que el código sólo tenga que cargarse como máximo una vez por fotograma.
  • El código es excesivamente abstracto: Entre otros problemas, la abstracción tiende a crear complejidad tanto en los datos como en el código, lo que agrava los problemas antes mencionados: la gestión de las asignaciones sin recolección de basura se vuelve más difícil; el compilador puede no ser capaz de optimizar con la misma eficacia; el multithreading seguro y eficiente se vuelve más difícil, y tus datos y código tienden a ser menos amigables con la caché. Además de todo esto, las abstracciones tienden a repartir los costes de rendimiento, de forma que todo el código es más lento, lo que te deja sin cuellos de botella claros que optimizar.

Todos los males mencionados se dan con frecuencia en los proyectos Unity. Veámoslos más concretamente:

  • Aunque C# le permite crear objetos asignados manualmente (es decir, objetos que no se recogen de la basura), la norma por defecto en C# y en la mayoría de los proyectos de Unity es utilizar instancias de clases C#, que se recogen de la basura. En la práctica, los usuarios de Unity llevan mucho tiempo mitigando este problema con una técnica llamada pooling (aunque podría decirse que el pooling anula el propósito de utilizar un lenguaje de recolección de basura en primer lugar). La principal ventaja de la agrupación de objetos es la reutilización eficaz de los objetos de una reserva preasignada, lo que elimina la necesidad de crear y reasignar objetos con frecuencia.
  • En el editor de Unity, el código C# se compila normalmente a código máquina con el compilador Mono de . Para compilaciones independientes se pueden obtener mejores resultados utilizando IL2CPP (C# Intermediate Language cross-compiled to C++), pero esto conlleva algunas desventajas, como tiempos de compilación más largos y una mayor dificultad para la compatibilidad con mods.
  • Es común que los proyectos Unity ejecuten todo su código en el hilo principal, en parte porque hacerlo es lo que Unity hace fácil:
  • Las funciones de eventos de Unity, como el método Update() de MonoBehaviours, se ejecutan todas en el hilo principal.
  • La mayoría de las APIs de Unity sólo pueden ser llamadas con seguridad desde el hilo principal.
  • Los datos de un proyecto Unity típico tienden a estructurarse como un montón de objetos aleatorios dispersos por toda la memoria, lo que conduce a una mala utilización de la caché. De nuevo, esto se debe en parte a que es lo que Unity facilita:
  • Un GameObject y sus componentes se asignan por separado, por lo que a menudo terminan en diferentes partes de la memoria.
  • El código en un proyecto Unity típico tiende a no ser amigable con el caché:
  • C# convencional y las API de Unity fomentan un estilo de código orientado a objetos, que tiende hacia numerosos métodos pequeños y cadenas de llamadas complejas. A diferencia de un enfoque orientado a los datos, no es muy amigable con el hardware.
  • Las funciones de evento de cada MonoBehaviour se invocan individualmente, y las llamadas no están necesariamente agrupadas por tipo de MonoBehaviour. Por ejemplo, si tienes 1000 Monstruos MonoConductores, cada Monstruo se actualiza por separado y no necesariamente junto con los otros Monstruos.

El estilo orientado a objetos del C# convencional y muchas APIs de Unity generalmente conducen a soluciones con mucha abstracción. El código resultante tiende a presentar ineficiencias difíciles de desentrañar y aislar.

¿A quién va dirigido el libro electrónico DOTS?
Un avance del nuevo libro electrónico Introducción a la pila tecnológica orientada a datos para desarrolladores avanzados de Unity.

Este libro electrónico está disponible gratuitamente para todo el mundo, pero está adaptado a los desarrolladores de Unity que tienen experiencia con el desarrollo de juegos orientado a objetos y basado en Monobehaviour, pero son nuevos en Unity DOTS y en el desarrollo de diseños orientados a datos.

Esperamos que la guía le ayude a entender DOTS y cómo estas características podrían beneficiar a su próximo proyecto de Unity, así como facilitarle la obtención de todo el valor de las muestras disponibles en nuestro repositorio de GitHub.