Parte: 3 — Física y matemáticas de juegos aplicadas · Fuente: Simulación de física para juegos — apuntes de aula y práctica con Godot 4 / Python ⏱️ Duración estimada: 60 min · Nivel: Intermedio
Comprender cómo un motor "hace avanzar" el tiempo: la integración numérica. Compararemos tres métodos —Euler explícito, Euler semi-implícito y Verlet— sobre una misma partícula, veremos con números por qué unos "explotan" y otros conservan mejor la energía, y aprenderás por qué las telas y sistemas de partículas suelen usar Verlet con paso fijo.
Al finalizar, el alumno podrá:
delta constante) en la simulación.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Qué es integrar en un juego | El motor solo conoce fuerzas; debe deducir movimiento |
| 2 | Euler explícito | El más simple, pero inestable |
| 3 | Euler semi-implícito | Casi gratis y mucho más estable |
| 4 | Verlet en posición | Estabilidad para partículas y telas |
| 5 | Estabilidad y energía | Por qué unos sistemas "explotan" |
| 6 | Paso de tiempo fijo | Reproducibilidad y estabilidad |
| 7 | Elegir el método | Cada juego pide un compromiso distinto |
dt usando la aceleración. Clave: es una aproximación, no exacta.dt constante): integrar siempre con el mismo intervalo. Clave: hace la simulación determinista y estable.dt.Para este lab usaremos Python (solo la librería estándar) porque queremos ver los números de las trayectorias sin depender del render. Necesitas Python 3.10+ (python.org). Opcionalmente puedes graficar con matplotlib, pero el lab imprime tablas en consola para que sea autocontenido. Al final se indica cómo trasladar Verlet a Godot dentro de _physics_process(delta) con paso fijo.
Simularemos un oscilador armónico (un resorte): aceleración a = -k*x. La energía real debería mantenerse constante; veremos cuál método la respeta.
Paso 1 — Los tres integradores. Guarda esto como integradores.py.
def euler_explicito(x, v, k, dt):
a = -k * x
x_nuevo = x + v * dt # usa la v ANTIGUA
v_nuevo = v + a * dt
return x_nuevo, v_nuevo
def euler_semi_implicito(x, v, k, dt):
a = -k * x
v_nuevo = v + a * dt # primero la velocidad
x_nuevo = x + v_nuevo * dt # usa la v NUEVA
return x_nuevo, v_nuevo
def verlet(x, x_prev, k, dt):
a = -k * x
x_nuevo = 2 * x - x_prev + a * dt * dt
return x_nuevo, x # nueva posicion, y la actual pasa a ser "prev"
Paso 2 — Ejecutar y medir la energía. La energía del resorte es E = 0.5*v^2 + 0.5*k*x^2.
def energia(x, v, k):
return 0.5 * v * v + 0.5 * k * x * x
k, dt, pasos = 1.0, 0.1, 200
# Estado inicial comun.
xe, ve = 1.0, 0.0 # Euler explicito
xs, vs = 1.0, 0.0 # semi-implicito
xv, xv_prev = 1.0, 1.0 # Verlet: en reposo, prev = actual
print(f"{'paso':>4} {'E_expl':>10} {'E_semi':>10} {'E_verlet':>10}")
for i in range(pasos):
xe, ve = euler_explicito(xe, ve, k, dt)
xs, vs = euler_semi_implicito(xs, vs, k, dt)
xv_new, xv_prev = verlet(xv, xv_prev, k, dt)
xv = xv_new
if i % 40 == 0:
v_verlet = (xv - xv_prev) / dt # velocidad implicita
print(f"{i:>4} {energia(xe, ve, k):>10.4f} "
f"{energia(xs, vs, k):>10.4f} {energia(xv, v_verlet, k):>10.4f}")
Paso 3 — Observar. Al ejecutar python integradores.py verás algo como:
paso E_expl E_semi E_verlet
0 1.0000 0.9950 0.9950
40 1.4859 1.0000 0.9975
80 2.2076 1.0000 0.9975
160 4.8586 1.0000 0.9975
La energía de Euler explícito crece sin parar (el resorte se "carga" solo hasta explotar). El semi-implícito oscila alrededor de un valor estable. Verlet se mantiene casi constante. Esa es la razón práctica por la que Euler explícito casi nunca se usa en juegos.
Paso 4 — Verlet en Godot. El mismo esquema dentro de un CharacterBody2D con paso fijo:
var pos_prev: Vector2
var pos_actual: Vector2
func _physics_process(delta: float) -> void:
var a := Vector2(0, 980) # gravedad
var pos_nueva := 2.0 * pos_actual - pos_prev + a * delta * delta
pos_prev = pos_actual
pos_actual = pos_nueva
global_position = pos_actual
dt a 0.5 y observa cómo Euler explícito explota mucho antes.a = -9.8, sin resorte) con los tres métodos y compara posiciones tras 1 segundo.x_prev = x - v0*dt.dt = 0.1 y dt = 0.05.matplotlib y comenta las diferencias.Construye una simulación Verlet de una cadena de 5 puntos unidos por restricciones de distancia, con gravedad y un extremo fijado. En cada paso: integra por Verlet y luego aplica varias iteraciones de corrección de restricciones para mantener las distancias.
Criterio de aceptación: la cadena cuelga y se estabiliza formando una catenaria; tras 300 pasos, la distancia entre puntos consecutivos se mantiene dentro de ±2% de la longitud objetivo, y el punto fijado no se mueve.
| Síntoma | Causa y arreglo |
|---|---|
| La simulación "explota" a valores enormes | Euler explícito con dt grande; usa semi-implícito o reduce dt |
| Verlet no arranca con velocidad | Inicializaste x_prev = x; para velocidad v0, usa x_prev = x - v0*dt |
| El movimiento cambia con los FPS | Estás integrando en _process; usa _physics_process con paso fijo |
| La energía decae hasta detenerse | Introdujiste amortiguación no deseada en la corrección de restricciones |
| Verlet "tiembla" | Demasiadas o muy pocas iteraciones de restricción; ajusta el número |
¿Por qué no usar siempre el método más preciso? Los juegos priorizan estabilidad y velocidad sobre exactitud física. Semi-implícito y Verlet son baratos y "se sienten bien", que es lo que importa.
¿Verlet guarda velocidad? No de forma explícita: la velocidad está implícita en (x - x_prev)/dt. Por eso es cómodo aplicar restricciones moviendo posiciones directamente.
¿Por qué paso de tiempo fijo? Con dt variable la simulación deja de ser reproducible y puede volverse inestable. Godot llama _physics_process con delta fijo justamente por esto.
¿Cuándo elijo cada uno? Semi-implícito para cuerpos rígidos generales; Verlet para partículas, cuerdas y telas donde importan las restricciones de posición.
_physics_process y paso fijo: https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.htmlClase 069 - Cuaterniones: rotaciones 3D sin gimbal lock