Parte: 0 — Fundamentos y prerrequisitos · Fuente: Glenn Fiedler, "Fix Your Timestep!"; Robert Nystrom, Game Programming Patterns ⏱️ Duración estimada: 90 min · Nivel: Fundamentos
Los juegos se ejecutan a distintas velocidades: una máquina potente puede dibujar 240 cuadros por segundo y otra apenas 30. Si mueves un personaje sumando una cantidad fija por cuadro, en la máquina rápida volará y en la lenta irá a paso de tortuga. La solución es multiplicar el movimiento por delta time, el tiempo transcurrido desde el cuadro anterior, para que la velocidad sea la misma en cualquier hardware.
Pero la física necesita algo más: un paso fijo (fixed timestep) que garantice estabilidad y repetibilidad. En esta clase entenderás por qué se multiplica por delta, cómo Godot separa el proceso de dibujo del proceso de física (_process vs _physics_process), qué es un acumulador con interpolación y por qué el determinismo —obtener siempre el mismo resultado con las mismas entradas— importa en multijugador y repeticiones (replays), complicado por la aritmética de coma flotante.
Al finalizar, el alumno podrá:
delta para ser independiente del framerate._process (variable) de _physics_process (paso fijo) en Godot.float dificultan el determinismo entre máquinas.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Delta time | Velocidad igual en cualquier framerate. |
| 2 | Frame time variable | Los cuadros no duran siempre lo mismo. |
| 3 | Fixed timestep | Física estable y repetible. |
| 4 | _process vs _physics_process |
Godot separa render y física. |
| 5 | Acumulador | Ejecutar N pasos fijos por cuadro. |
| 6 | Interpolación | Suavizar el render entre pasos. |
| 7 | Determinismo | Multijugador y replays fiables. |
| 8 | Floats y precisión | Por qué el mismo cálculo puede diferir. |
delta): segundos transcurridos desde el cuadro anterior. Clave: multiplicador que normaliza la velocidad._process(delta): función de Godot que corre cada cuadro de render. Clave: delta variable._physics_process(delta): función que corre a frecuencia fija (60 Hz por defecto). Clave: delta constante.Trabajarás en Godot 4 con GDScript. Crea un proyecto 2D vacío. Necesitarás un nodo visible (por ejemplo un Sprite2D o un ColorRect) para observar el movimiento, y el monitor de FPS que puedes forzar desde Proyecto > Ajustes del proyecto > Application > Run > Max FPS o mediante Engine.max_fps. Las lecturas de referencia son el artículo clásico Fix Your Timestep! de Glenn Fiedler (https://gafferongames.com/post/fix_your_timestep/) y el capítulo Game Loop de Game Programming Patterns de Robert Nystrom (https://gameprogrammingpatterns.com/game-loop.html). La documentación de Godot sobre _process y _physics_process completa la parte práctica.
Añade un Sprite2D a la escena, adjúntale un script y mueve el nodo sumando una cantidad fija por cuadro:
extends Sprite2D
var speed := 4.0 # pixeles por CUADRO (mal)
func _process(_delta: float) -> void:
position.x += speed # depende del framerate
Ejecuta el juego. El sprite se mueve a cierta velocidad, pero esa velocidad está atada al framerate: cuantos más FPS, más rápido va.
Fuerza el framerate desde el script _ready y compara:
func _ready() -> void:
Engine.max_fps = 30 # prueba luego con 120
Con max_fps = 30 el sprite avanza lento; con 120 vuela. El comportamiento del juego cambia según la máquina: inaceptable.
Convierte la velocidad a píxeles por segundo y multiplica por delta:
extends Sprite2D
var speed := 240.0 # pixeles por SEGUNDO (bien)
func _process(delta: float) -> void:
position.x += speed * delta # independiente del framerate
Ahora, con 30 o con 120 FPS, el sprite recorre los mismos 240 píxeles cada segundo. delta es pequeño cuando hay muchos FPS y grande cuando hay pocos, compensando exactamente la diferencia.
_physics_processPara colisiones y movimiento físico usa el paso fijo, que Godot llama 60 veces por segundo con un delta constante:
extends CharacterBody2D
var speed := 240.0
func _physics_process(delta: float) -> void:
velocity.x = speed
move_and_slide() # integracion estable a 60 Hz
_physics_process recibe siempre el mismo delta (1/60 ≈ 0.01667), lo que mantiene la simulación estable y repetible aunque el render fluctúe.
Internamente, el motor usa un acumulador: suma el tiempo del cuadro y ejecuta tantos pasos fijos como quepan, guardando el resto. Este pseudocódigo ilustra la idea del artículo de Fiedler:
var accumulator := 0.0
const STEP := 1.0 / 60.0
func _process(delta: float) -> void:
accumulator += delta
while accumulator >= STEP:
_simular_paso(STEP) # siempre el mismo dt fijo
accumulator -= STEP
# el sobrante (accumulator) serviria para interpolar el render
Godot ya hace esto por ti al separar _physics_process de _process; entenderlo explica por qué la física es estable y el render fluido.
print cuántos píxeles recorre el sprite en un segundo con y sin delta.Engine.max_fps a 15, 60 y 144 y confirma que la versión con delta no varía._physics_process recibe un delta casi constante.STEP del acumulador a 1/30 y describe el efecto esperado.Engine.get_physics_ticks_per_second() y cámbialo a 30.Crea una escena con dos sprites: uno que se mueva con position.x += speed (sin delta) y otro con position.x += speed * delta. Añade un control para cambiar Engine.max_fps en tiempo de ejecución entre 30 y 120, y demuestra que solo el segundo mantiene la misma velocidad real. Documenta con un print la posición de cada sprite tras dos segundos.
Criterio de aceptación: al alternar el framerate, el sprite sin delta recorre distancias distintas en el mismo tiempo real mientras que el sprite con delta recorre siempre la misma distancia (±1 %); el código de física usa _physics_process; y el registro impreso evidencia ambos comportamientos.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| El personaje va más rápido en un PC potente | Sumas una cantidad fija por cuadro. Multiplica la velocidad por delta. |
El movimiento con delta va lentísimo |
Dejaste la velocidad en píxeles-por-cuadro. Súbela a píxeles-por-segundo. |
| La física tiembla o atraviesa paredes | Pones lógica de colisión en _process. Muévela a _physics_process. |
| Un replay se desincroniza en otra máquina | Dependes de float no deterministas o del framerate. Usa paso fijo y evita azar sin semilla. |
delta es enorme el primer cuadro |
La carga inicial produce un pico. Ignora el primer delta o usa _physics_process. |
❓ ¿Por qué exactamente se multiplica por delta? Porque convierte una velocidad expresada por segundo en el desplazamiento correcto para la duración real de ese cuadro. Si el cuadro dura poco, delta es pequeño y el avance también; el resultado por segundo es constante.
❓ ¿Cuándo uso _process y cuándo _physics_process? Usa _process para cosas visuales y de entrada que pueden variar (animaciones, cámara). Usa _physics_process para movimiento con colisiones y cualquier simulación que deba ser estable y repetible.
❓ ¿Qué gana la física con un paso fijo? Estabilidad y repetibilidad. Los integradores numéricos se comportan mal con pasos de tiempo variables; con un dt constante la simulación es predecible y no explota con cuadros largos.
❓ ¿Por qué los float complican el determinismo? Porque el redondeo de coma flotante puede diferir entre CPUs, compiladores u órdenes de operación. Dos máquinas pueden obtener resultados ligeramente distintos y, acumulados, desincronizar una simulación multijugador o un replay.
move_and_slide": https://docs.godotengine.org/en/stable/classes/class_characterbody2d.htmlClase 021 - Assets y pipeline de contenido: import, compresión y presupuestos
Clase 023 - Debugging y profiling: herramientas y mentalidad