game loop - patterns - ¿Por qué es necesario un bucle de juego “principal” para desarrollar un juego?
game development patterns (11)
Me parece que la mayoría del desarrollo de juegos requiere un bucle principal, pero no sé por qué es necesario. ¿No podríamos implementar un detector de eventos y responder a cada acción del usuario? Las animaciones (etc.) se pueden reproducir cuando ocurre un evento.
¿Cuál es el propósito de un bucle principal del juego?
Cualquier programa que pueda permanecer allí indefinidamente y responder a la entrada del usuario necesita algún tipo de bucle. De lo contrario, solo llegará al final del programa y saldrá.
Dos razones -
Incluso los sistemas controlados por eventos generalmente necesitan un bucle de algún tipo que lea eventos de una cola de algún tipo y los envíe a un controlador, por lo que, de todos modos, usted termina con un bucle de eventos en las ventanas y podría extenderlo.
Para los propósitos de la animación, necesitarías manejar algún tipo de par para cada fotograma de la animación. Ciertamente, podría hacer esto con un temporizador o algún tipo de evento inactivo, pero probablemente terminaría creando esos en algún tipo de bucle de todos modos, así que es más fácil usar el bucle directamente.
He visto sistemas que lo manejan todo usando eventos, tienen un detector de cuadros que escucha un evento enviado al comienzo de cada cuadro. Todavía tienen un pequeño bucle de juego interno, pero hace poco más que manejar eventos del sistema de ventanas y crear eventos de marco,
El argumento de que "necesita un bucle porque, de lo contrario, lo que llama al oyente del evento" no retiene el agua. Es cierto que en cualquier sistema operativo principal, sí que tiene un bucle de este tipo, y los oyentes de eventos funcionan de esa manera, pero es completamente posible crear un sistema de interrupción que funcione sin ningún tipo de bucles.
Pero todavía no querrías estructurar un juego de esa manera.
Lo que hace que un bucle sea la solución más atractiva es que su bucle se convierta en lo que en la programación en tiempo real se conoce como un "ejecutivo cíclico". La idea es que puede hacer que las tasas de ejecución relativas de las diversas actividades del sistema sean deterministas entre sí. La velocidad general del bucle puede ser controlada por un temporizador, y ese temporizador puede ser en última instancia una interrupción, pero con los sistemas operativos modernos, es probable que vea evidencia de esa interrupción como un código que espera un semáforo (o algún otro mecanismo de sincronización) como parte de su "bucle principal".
Entonces, ¿por qué quieres un comportamiento determinista? Tenga en cuenta las tasas relativas de procesamiento de las entradas de su usuario y los malos AIs. Si pone todo en un sistema puramente basado en eventos, no hay garantía de que los AI no obtengan más tiempo de CPU que su usuario, o al revés, a menos que tenga algún control sobre las prioridades de los subprocesos, e incluso entonces, está Apto para tener dificultades para mantener el tiempo consistente.
Sin embargo, ponga todo en un bucle y garantiza que las líneas de tiempo de sus AIs van a continuar en una relación fija con respecto al tiempo de su usuario. Esto se logra haciendo una llamada desde su bucle para dar a los AI un intervalo de tiempo en el que decidir qué hacer, una llamada a sus rutinas de entrada de usuario, para sondear los dispositivos de entrada para descubrir cómo quiere comportarse su usuario, y llame para hacer su representación.
Con un bucle de este tipo, debe observar que no está tardando más en procesar cada paso de lo que realmente pasa en tiempo real. Si está tratando de hacer un ciclo de su ciclo a 100Hz, todo el procesamiento de su ciclo debería terminar en menos de 10 ms, de lo contrario, su sistema se volverá irregular. En la programación en tiempo real, se llama sobrepasar su marco de tiempo. Un buen sistema le permitirá monitorear qué tan cerca está de sobrepasar, y luego puede mitigar la carga de procesamiento como mejor le parezca.
El bucle principal llama al detector de eventos. Si tiene la suerte de tener un sistema operativo controlado por eventos o un administrador de ventanas, el bucle de eventos reside allí. De lo contrario, escribe un bucle principal para mediar la "falta de coincidencia de impedancia" entre las interfaces de llamada del sistema que se basan en I / O, poll
o select
, y una aplicación tradicional basada en eventos.
PD: puesto que etiquetó su pregunta con la programación funcional, es posible que desee revisar la Programación reactiva funcional , que hace un gran trabajo al conectar abstracciones de alto nivel con implementaciones basadas en eventos de bajo nivel.
En términos prácticos, como han indicado otras personas, se necesita un bucle.
Sin embargo, tu idea es teóricamente sólida. No necesitas un bucle. Necesita operaciones basadas en eventos.
En un nivel simple, puede conceptualizar la CPU para tener varios temporizadores;
- uno dispara en el flanco ascendente de 60Hz y llama al código de blitting.
- Otro podría ser el tictac a 60kHz y la última actualización de los objetos en el mundo del juego al búfer de blitter.
- Otro podría estar marcando a 10kHz y recibiendo información del usuario. (bastante alta resolución, jajaja)
- Otro podría ser el juego ''latido del corazón'' y marca a 60MHz; La inteligencia artificial y la física pueden operar en el momento del latido del corazón.
Por supuesto, estos temporizadores pueden ser sintonizados.
En la práctica, lo que estaría sucediendo es que estarías (algo desilusionado) así:
void int_handler1();
//...
int main()
{
//install interrupt handlers
//configure settings
while(1);
}
Es porque los sistemas operativos actuales no están totalmente basados en eventos. Aunque las cosas a menudo se representan como eventos, aún tendrá que crear un bucle donde esperará el próximo evento y procesarlo indefinidamente (como un ejemplo del bucle de eventos de Windows). Las señales de Unix son probablemente lo más cercano a eventos a nivel de sistema operativo, pero no son lo suficientemente eficientes para cosas como esta.
La naturaleza de los juegos es que normalmente son simulaciones y no solo reaccionan en base a eventos externos, sino también en procesos internos. Podría representar estos procesos internos mediante eventos recurrentes en lugar de sondeos, pero son prácticamente equivalentes:
schedule(updateEvent, 33ms)
function updateEvent:
for monster in game:
monster.update()
render()
vs:
while 1:
for monster in game:
monster.update()
wait(33ms)
render()
Curiosamente, pyglet implementa el método basado en eventos en lugar del bucle más tradicional. Y aunque esto funciona bien muchas veces, a veces causa problemas de rendimiento o un comportamiento impredecible causado por la resolución del reloj, vsync, etc. ).
No es cierto que todo tipo de juegos requiera un circuito principal dedicado.
Los juegos de acción necesitan este tipo de bucle debido a las frecuentes actualizaciones de objetos y la precisión de entrada del juego.
Por otro lado, implementé un juego de buscaminas y utilicé Windows.
Mensajes para las notificaciones.
Un bucle de juego (muy simplificado es el siguiente)
initialise do input update render loop clean up
Esto sucederá en cada cuadro del juego. Entonces, para los juegos que se ejecutan a 60 fps, lo anterior se realiza sesenta veces por segundo.
Esto significa que el juego funciona sin problemas, el juego se mantiene sincronizado y las actualizaciones / sorteos por ciclo ocurren con la frecuencia suficiente. La animación es simplemente un truco visual, los objetos se mueven entre ubicaciones, pero cuando se reproducen lo suficientemente rápido, parecen estar viajando entre estas ubicaciones.
Si solo se actualizara con la entrada del usuario, el juego solo reaccionaría cuando el usuario proporcionara la entrada. Otros componentes del juego, como los objetos del juego de IA, no reaccionarían por sí solos. Por lo tanto, un bucle es la forma más fácil y mejor de actualizar un juego.
Un detector de eventos también depende de algún bucle de invocación, ya sea que lo vea o no. ¿Quién más va a llamar al oyente?
La creación de un bucle de juego explícito le da un control absoluto sobre lo que está sucediendo, por lo que no dependerá de lo que haga la biblioteca de manejo de eventos / kit de herramientas en su bucle de eventos.
Un juego debe ejecutarse en real-time , por lo que funciona mejor si se ejecuta en una CPU / núcleo continuamente. Una aplicación controlada por eventos típicamente cederá la CPU a otro subproceso cuando no haya ningún evento en la cola. Puede haber una espera considerable antes de que la CPU vuelva a su proceso. En un juego, esto significaría paradas breves y animaciones temblorosas.