Ilustración en acuarela de un ciclo abstracto: un input circular a la izquierda alimenta un remolino de procesamiento en el centro que termina dibujando un paisaje enmarcado a la derecha — el game loop como concepto

Antes de construir Chivo-8 había leído la palabra "game loop" en decenas de artículos sin que me dijera nada real. Sonaba a jerga. Update, render, delta time — palabras que se explican con otras palabras igual de vacías.

Entendí lo que era cuando tuve que escribir uno que no se rompiera.


Un game loop, sin humo, es esto: un ciclo que no para de hacer dos cosas — actualizar el estado del juego y dibujarlo en pantalla — muchas veces por segundo, para siempre, hasta que cierras el juego.

mientras el juego esté corriendo:
    procesar entrada (¿qué tecla apretaste?)
    actualizar estado (¿dónde está todo ahora?)
    dibujar en pantalla

Procesar entrada es leer los controles. Actualizar es el update: mover, calcular, resolver colisiones. Dibujar es el render: pintar ese resultado en pantalla.

Diagrama retro-mecánico del game loop: el control alimenta los engranes del update, que pasan por colisión y terminan dibujando el frame en la pantalla central, con un personaje pixel art saltando entre plataformas

Tan simple como se ve escrito. El problema empieza en la letra chica: ¿cada cuánto se repite ese ciclo?


La respuesta ingenua es "lo más rápido posible". Y ahí está el primer error que casi todo el mundo comete una vez, yo incluido: si actualizas el estado del juego una vez por cada vuelta del ciclo, y el ciclo corre distinto en cada computadora, tu juego se comporta distinto en cada computadora. Un salto que se siente bien a 60 cuadros por segundo se siente como teletransportación a 144, y como movimiento en cámara lenta a 30.

Eso no es un detalle menor. Es la diferencia entre un juego que se siente igual para todos y uno que solo funciona bien en tu máquina.

La solución tiene nombre y viene de un artículo ya canónico sobre el tema: "Fix Your Timestep!", de Glenn Fiedler (2004). La idea central es separar dos cosas que parecen la misma: cada cuánto actualizas el estado del juego, y cada cuánto dibujas ese estado en pantalla. Actualizas en pasos fijos — siempre el mismo intervalo de tiempo, sin importar qué tan rápida sea la máquina — y dibujas tan seguido como la pantalla te deje. El resultado: la física y la lógica avanzan con incrementos de tiempo consistentes tanto en una laptop vieja como en una gaming rig, aunque el dibujo se vea más o menos fluido según el hardware.

Hay un peligro del lado contrario, también documentado por Fiedler: si tu ciclo de actualización se atrasa (porque hay demasiado que calcular) e intentas "ponerte al día" corriendo más actualizaciones para compensar, puedes entrar en lo que él llama la espiral de la muerte — cada actualización extra te atrasa un poco más, hasta que el juego se congela intentando alcanzar un tiempo que ya pasó.


Armé un cartucho de plataformas simple para Chivo-8 — correr, saltar, aterrizar sobre plataformas — y ahí encontré un bug real, no de libro de texto. Si mantenías el botón de salto apretado desde el arranque del juego, el salto se perdía en silencio: mientras siguieras con el botón abajo, el jugador se quedaba pegado al piso. Nada tronaba, nada avisaba. Solo dejaba de funcionar — hasta que lo soltabas y volvías a apretarlo.

Diagrama pixel art del bug real de Chivo-8: el loop input/update/collision/render y, debajo, el pulso de "jump" perdiéndose antes de llegar al update ("LOST HERE" / "FIZZLE"), con la cabra protagonista junto a un letrero "PRESS JUMP TO REACH HIGHER"

Al principio pensé que era el mismo problema que advierte "Fix Your Timestep". No lo era. El timestep fijo resuelve cuánto avanza la simulación en el tiempo; mi bug estaba un nivel antes: en cómo leía el botón.

Un sistema de entrada normalmente distingue tres cosas: held (el botón sigue apretado), pressed (pasó de suelto a apretado en esta vuelta) y released (pasó de apretado a suelto). El salto no debía dispararse con held — debía dispararse con pressed, la transición, que se calcula comparando el estado actual del botón contra el estado anterior:

pressed = actual && !anterior

Mi loop arrancaba con el botón ya apretado y con el estado anterior inicializado también como apretado. Nunca había una transición que observar — para el sistema, ese botón "ya estaba apretado" desde siempre, no "acababa de apretarse". El bug no estaba en la física ni en el timestep. Estaba en el orden de las preguntas que mi loop le hacía al mundo, cada vuelta: cuándo leía la entrada, cuándo la comparaba contra el estado anterior, y cuándo ese estado anterior se actualizaba.

Ese tipo de error no se ve jugando cinco minutos. Se ve construyendo.


Lo que me quedó de esto, más que la solución técnica, es una forma distinta de ver cualquier videojuego que juego ahora: detrás de cada salto, cada colisión, cada enemigo que se mueve, hay un ciclo silencioso preguntando "¿qué cambió?" sesenta veces por segundo, sin que el jugador lo note jamás — hasta que algo sale mal, y entonces se nota todo.

Si programaste algo alguna vez y sentiste que "el movimiento no se sentía bien" sin saber por qué, el loop es de los primeros sospechosos — no el único: fricción, curva de velocidad, coyote time o el feedback visual también pesan, pero el loop es el que casi nadie mira primero. Vale la pena mirarlo de frente al menos una vez.