Bucle de juego "óptimo" para desplazamiento lateral 2D


14

¿Es posible describir un diseño "óptimo" (en términos de rendimiento) para un bucle de juego de desplazamiento lateral en 2D? En este contexto, el "bucle del juego" toma la entrada del usuario, actualiza los estados de los objetos del juego y dibuja los objetos del juego.

Por ejemplo, tener una clase base GameObject con una jerarquía de herencia profunda podría ser bueno para el mantenimiento ... puede hacer algo como lo siguiente:

foreach(GameObject g in gameObjects) g.update();

Sin embargo, creo que este enfoque puede crear problemas de rendimiento.

Por otro lado, todos los datos y funciones de los objetos del juego podrían ser globales. Lo cual sería un dolor de cabeza de mantenimiento, pero podría estar más cerca de un bucle de juego con un rendimiento óptimo.

¿Alguna idea? Estoy interesado en aplicaciones prácticas de una estructura de bucle de juego casi óptima ... incluso si tengo un dolor de cabeza de mantenimiento a cambio de un gran rendimiento.


Respuestas:


45

Por ejemplo, tener una clase base GameObject con una jerarquía de herencia profunda podría ser bueno para el mantenimiento ...

En realidad, las jerarquías profundas son generalmente peores para la mantenibilidad que las superficiales, y el estilo arquitectónico moderno para los objetos del juego tiende hacia enfoques poco profundos basados ​​en la agregación .

Sin embargo, creo que este enfoque puede crear problemas de rendimiento. Por otro lado, todos los datos y funciones de los objetos del juego podrían ser globales. Lo cual sería un dolor de cabeza de mantenimiento, pero podría estar más cerca de un bucle de juego con un rendimiento óptimo.

El bucle que ha mostrado potencialmente tiene problemas de rendimiento, pero no, como lo implica su declaración posterior, porque tiene datos de instancia y funciones miembro en la GameObjectclase. Más bien, el problema es que si tratas a todos los objetos del juego como exactamente iguales, probablemente no estés agrupando esos objetos de manera inteligente, es probable que estén dispersos al azar por toda la lista. Potencialmente, entonces, cada llamada al método de actualización para ese objeto (si ese método es una función global o no, y si ese objeto tiene datos de instancia o "datos globales" flotando en alguna tabla en la que está indexando o lo que sea) diferente de la llamada de actualización en las últimas iteraciones de bucle.

Esto puede aumentar la presión sobre el sistema, ya que es posible que deba buscar en la memoria la función relevante dentro y fuera de la memoria y volver a llenar el caché de instrucciones con mayor frecuencia, lo que resulta en un bucle más lento. Si esto es observable o no a simple vista (o incluso en un generador de perfiles) depende exactamente de lo que se considera un "objeto de juego", cuántos de ellos existen en promedio y qué más está sucediendo en su aplicación.

Los sistemas de objetos orientados a componentes son una tendencia popular en este momento, aprovechando la filosofía de que la agregación es preferible a la herencia . Tales sistemas potencialmente le permiten dividir la lógica de "actualización" de componentes (donde "componente" se define más o menos como alguna unidad de funcionalidad, como lo que representa la parte físicamente simulada de algún objeto, que es procesada por el sistema físico ) en varios subprocesos, discriminados por tipo de componente, si es posible y deseado, lo que podría tener una ganancia de rendimiento. Como mínimo, puede organizar los componentes de manera que todos los componentes de un tipo dado se actualicen juntos , haciendo un uso óptimo de la memoria caché de la CPU. Un ejemplo de dicho sistema orientado a componentes se discute en este hilo .

Tales sistemas a menudo están muy desacoplados, lo que también es una bendición para el mantenimiento.

El diseño orientado a datos es un enfoque relacionado: se trata de orientarse en torno a los datos requeridos de los objetos como primera prioridad, de modo que esos datos puedan procesarse efectivamente en masa (por ejemplo). Esto generalmente significa una organización que trata de mantener juntos los datos utilizados para el mismo clúster de propósito y operar todos a la vez. No es fundamentalmente incompatible con el diseño OO, y puede encontrar alguna charla sobre el tema aquí en GDSE en esta pregunta .

En efecto, un enfoque más óptimo para el ciclo del juego sería, en lugar de su original

foreach(GameObject g in gameObjects) g.update();

algo mas como

ProcessUserInput();
UpdatePhysicsForAllObjects();
UpdateScriptsForAllObjects();
UpdateRenderDataForAllObjects();
RenderEverything();

En un mundo así, cada uno GameObjectpuede tener un puntero o referencia a su propio PhysicsDatao Script, o RenderData, para los casos en los que pueda necesitar para interactuar con los objetos de forma individual, pero el real PhysicsData, Scripts, RenderData, etcétera, todos estaríamos de sus respectivos subsistemas (simulador de física, entorno de alojamiento de script, renderizador) y procesado en masa como se indicó anteriormente.

Es muy importante tener en cuenta que este enfoque no es una bala mágica y no siempre producirá una mejora en el rendimiento (aunque generalmente es un mejor diseño que un árbol de herencia profunda). Es muy probable que note que básicamente no hay diferencia de rendimiento si tiene muy pocos objetos o muchos objetos para los que no puede paralelizar efectivamente las actualizaciones.

Desafortunadamente, no existe un bucle mágico que sea el más óptimo: cada juego es diferente y puede necesitar ajustes de rendimiento de diferentes maneras. Por lo tanto, es muy importante medir las cosas (perfil) antes de seguir ciegamente el consejo de un tipo aleatorio en Internet.


55
Buen señor, esta es una gran respuesta.
Raveline

2

El estilo de programación de objetos de juego está bien para proyectos más pequeños. ¡A medida que el proyecto se vuelve realmente enorme, terminarás con una jerarquía de herencia profunda y la base de objetos de juego se volverá realmente pesada!

Como ejemplo ... Supongamos que comienza a crear una base de objetos de juego con atributos mínimos, como Posición (para x e y), displayObject (esto se refiere a la imagen si es un elemento de dibujo), ancho, alto, etc.

Ahora, más adelante, si necesitamos agregar una subclase que tenga un estado de animación, entonces probablemente mueva 'código de control de animación' (por ejemplo, currentAnimationId, Number of frames for this animation, etc.) a GameObjectBase en el pensamiento de que la mayoría de Los objetos tendrán animación.
Más adelante, si desea agregar un cuerpo rígido que es estático (no tiene ninguna animación), debe verificar el 'código de control de animación' en la función de actualización de GameObject Base (aunque sea una verificación de si la animación está allí o no). ... ¡importa!) Al contrario de esto, si sigue la Estructura basada en componentes, tendrá un 'Componente de control de animación' adjunto a su objeto. Si es necesario, lo adjuntará. Para un cuerpo estático no adjuntará ese componente. De esta manera, podemos evitar controles innecesarios.

Por lo tanto, la selección de elegir el estilo depende del tamaño del proyecto. La estructura de GameObject solo es fácil de dominar para proyectos más pequeños. Espero que esto ayude un poco.


@Josh Petrie - ¡Realmente una gran respuesta!
Ayyappa

@Josh Petrie - ¡Muy buena respuesta con enlaces útiles! (Sry no sabe cómo colocar este comentario al lado de tu publicación: P)
Ayyappa

Puede hacer clic en el enlace "agregar comentario" debajo de mi respuesta, normalmente, pero eso requiere más reputación de la que tiene actualmente (vea gamedev.stackexchange.com/privileges/comment ). ¡Y gracias!
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.