Advertisement
  1. Game Development
  2. Platform Agnostic

Optimizaciones de Código para Desarrolladores de Juegos: Estructuras y Actitudes Básicas

Scroll to top
Read Time: 8 min

() translation by (you can also view the original English article)

Para muchos desarrolladores independientes que comienzan la optimización de código se vuelve casi un pensamiento secundario. Se deja de lado en favor de los motores de juego o marcos, o se pueden considerar una técnica más avanzada fuera de su alcance. Sin embargo, hay métodos de optimización que se pueden utilizar in maneras más básicas, lo cual le permite a tu código operar más eficientemente y en más sistemas. Echemos un vistazo a algunos códigos básicos de optimización de código para que te vayas familiarizando.

Optimizando para los Jugadores y para la Cordura

No es raro para los desarrolladores independientes que ellos emulen los métodos de optimización de compañías más grandes. No es necesariamente algo malo, pero esforzarte para optimizar tu juego más allá de los regresos útiles es una buena manera de volverte loco a ti mismo. Una táctica brillante para darle seguimiento a cuan efectivas son tus optimizaciones es segmentar tu grupo demográfico objetivos y mirar los tipos de especificaciones que sus máquinas tienen. Hacer benchmarking de tu juego contra las computadoras y consolas que tus jugadores potenciales están usando ayudará a mantener un equilibrio entre optimización y cordura.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               

Optimizaciones de Código Básicas

Dejando todo eso de lado hay varias optimizaciones que pueden ser usadas casi siempre para ayudarle a tu juego a que corra mejor. La mayoría de estos son sistemas agnósticos (y algunos motores de juego y marcos ya las toman en cuenta), así que incluiré algunos ejemplos de pseudocódigo para orientarte en el camino correcto. Echemos un vistazo.

Minimizando el Impacto de los Objetos Fuera de Pantalla

A menudo manejado por motores de juego, y a veces aún por las mismas unidades procesadoras de gráficos (GPUs). minimizar la cantidad de poder de procesamiento que va hacia objetos que se encuentren fuera de la pantalla es extremamente importante. Para tus propias estructuras, es una buena idea comenzar a separar tus objetos en dos "capas" esencialmente—la primera siendo su representación gráfica, y la segunda siendo sus datos y funciones (tales como su localización). Cuando un objeto está fuera de pantalla, ya no necesitamos gastar los recursos generándolo, y en su lugar debemos optar por darle seguimiento. El darle seguimiento a elementos, como localización y estado, con variables reduce los recursos necesarios a sólo una fracción del costo inicial.

Para juegos con un gran número de objetos, u objetos que son pesados de datos, puede aún ser útil llevarlo un paso más allá al crear rutinas de actualización separadas, definiendo uno para que actualice mientras el objeto está en pantalla y el otro para que lo haga fuera de pantalla. COnfigurar una separación más avanzada de esta manera puede ahorrar que el sistema tenga que ejecutar un número de animaciones, algoritmos, y otras actualizaciones que puedan ser innecesarias cuando el objeto se encuentra oculto.

Aquí puedes ver un ejemplo de pseudocódigo de una clase de objeto usando indicadores y restricciones de locación.

1
Object NPC {
2
    Int locationX, locationY; //current location of the object on a 2d plane

3
4
	Function drawObject() {
5
		//a function to draw your object, to be called in your screen update loop

6
	}
7
8
	//function that checks if the object is within the current view port

9
	Function pollObjectDraw(
10
        array currentViewport[minX,minY,maxX,maxY]
11
        ) {
12
13
	//if it is within the viewport, return that it can be drawn

14
		If (this.within(currentViewport)) {
15
			Return true;
16
		}
17
		Else {
18
			Return false;
19
		}
20
	}
21
	
22
}

Aunque este ejemplo es simplificado, nos permite sondear si el objeto tan siquiera se mostrará antes de elaborarlo, permitiendonos ejecutar una función bastante simplificada en lugar de hacer el trazo completo. Para separar funciones aparte de llamadas gráficas, puede ser necesario utilizar un búfer adicional—por ejemplo, una función que incluiría cualquier cosa que un jugador podría ser capaz de ver pronto, en lugar de lo que lo que sólo puedan ver en el momento.

Separándose de Actualizaciones de Marco.

Dependiendo del motor de juego o el marco que uses, podrías comúnmente tener objetos que se actualizan en cada marco, o "momento". Hacerlo asi puede fatigar un procesador muy rápido, asi que para facilitar esa carga querremos reducir llamadas en cada marco siempre que sea posible.

Lo primero que vamos a querer separar es la generación (rendering) de función. Estas llamadas típicamente requieren muchos recursos, asi que integrar una llamada que nos pueda decir cuándo las propiedades visuales de un objeto han cambiado puede reducir la generación drásticamente.

Para llevarlo un paso más allá, podemos utilizar una pantalla temporal para nuestros objetos. Al hacer que los objetos sean directamente trazados a este contenedor temporal, podemos asegurarnos que solamente se ejecuten cuando sea necesario.

SImilar a la primera optimización mencionada arriba, la iteración inicial de nuestro código introduce sondeos simples:

1
Object NPC {
2
    boolean hasChanged; //set this flag to true whenever a change is made to the object

3
4
	//function that returns whether

5
	Function pollObjectChanged(
6
		return hasChanged();
7
	}
8
}

Ahora durante cada marco, en lugar de llevar a cabo un número de funciones, podemos ver si tan siquiera es necesario. Mientras que esta implementación es también simple, ya puede comenzar a mostrar  grandes ganancias en la eficiencia de tu juego, especialmente cuando se trata de objetos estáticos y objetos que se actualizan lentamente como un HUD (barra de estado).

Para llevar esto más allá en tu propio juego, romper el indicador en múltiples componentes más pequeños puede resultar útil para segmentar la funcionalidad. Por ejemplo, podrías tener indicadores para un cambio de datos y un cambio gráfico ocurre separadamente.

Cálculos en Vivo vs. Valores de Vistazo

Esta es una optimización que ha estado en uso desde los primeros días de los sistemas de juego. Determinar los intercambios entre cálculos en vivo y valores de vistazo puede ayudar a reducir los tiempos de procesamiento de manera drástica. Un uso bien conocido en la historia de los juegos es almacenar los valores de funciones trigonométricas en las tablas ya que, en la mayoría de los casos, era más eficiente de almacenar una tabla grander y recuperla en lugar de hacer los cálculos sobre la marcha y ejercer presión adicional en el CPU.

En la computación moderna, raramente necesitamos tomar la elección entre almacenar los resultados y ejecutar un algoritmo. Sin embargo, aun hay situaciones cuando el hacerlo puede reducir los recursos que se están usando, lo que permite la inclusión de otras características sin sobrecargar un sistema.

Una manera secinlla de comenzar a implementar esto es identificar cálculos, o partes de cálculos, que ocurren comúnmente dentro de tu juego: mientras más grande el cálculo, mejor. Echar a andar partes recurrentes de algoritmos una sola vez y almacenarlo puede a menudo ahorrar cantidades considerables de poder de procesamiento. Aún, el aislar estás partes en bucles de juego puede ayudar a optimizar el desempeño.

Como ejemplo, en muchos juegos de disparos verticales con frecuencia hay números grandes de enemigos que ejecutan los mismos comportamientos. Al contrario, si hay 20 enemigos, cada uno moviendose en un arco, en lugar de calcular cada movimiento individualmente es más eficiente almacenar los resultados del algoritmo . El hacer esto le permite ser modificado basado en la posición inicial de cada enemigo.

Para determinar si este método es útil para tu juego, prueba usar el benchmarking para comparar la diferencia en recursos usados entre cálculos en vivo y almacenamiento de datos.

Utlización de la Inactividad del CPU

Aunque esto representa más en la utilización de recursos durmientes, con pensamiento cuidadoso  para tus objetos y algoritmos, podemos apilar tareas en una manera que presione la eficiencia de nuestro código.

Para comenzar a usar sensitividad a la inactividad en tu propio software, primero deberás separar qué tareas dentro de tu juego no son críticas en relación al tiempo o pueden ser calculadas antes de que se vayan a necesitar. La primera área a revisar por si existe código que caiga en esta categoría es la funcionalidad que está estrictamente relacionada a la atmósfera del juego. Los sistemas de clima que no interactúan con la geografía, efectos visuales del fondo, y audio de fondo pueden todos entrar en la computación inactiva fácilmente.

Más allá de artículos que son estrictamente atmosféricos, los cálculos garantizados son otro tipo de computación que se pueden ubicar en espacios inactivos. Los cálculos de inteligencia artificial que van a ocurrir sin tomar en cuenta la interacción del jugador (ya sea porque no toman al jugador en cuenta, o porque son poco probables de requerir la interacción del jugador hasta el momento) se pueden hacer más eficientes, al igual que los movimientos calculados, tales como los eventos predefinidos.

Crear un sistema que use la inactividad hace aun más que permitir que haya mayor eficiencia—puede ser usado para escalar "el dulce para los ojos". Por ejemplo, en un sistema de límite inferior, talvez un jugador sólo experimenta una versión sin características extras del juego. Si nuestro sistema está detectando marcos inactivos, no obstante, podemos usarlo para agregar partículas adicionales, eventos gráficos, y otros arreglos atmosféricos para darle al juego un poco más de garbo.

Para implementar esto, usa la funcionalidad disponible en tu motor de juego favorito, marco, o lenguage para medir cuánto del CPU se está usando. Coloca indicadores dentro de tu código que facilite revisar cuanto poder de procesamiento "extra" está disponible, y luego configura tus sub-sistemas para ver este indicador y comportarse correspondientemente.

Encadenando Optimizaciones

Al combinar estos métods, es posible hacer tu código significativamente más eficiente. Con esa eficiencia viene la habilidad de agregar más características, ejecutar más sistemas, y asegurar una experiencia más sólida para los jugadores.

¿Tienes una manera fácil de implementar optimizaciones de código que uses regularmente? ¡Cuéntame sobre ellas!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.