Parte: 3 — Física y matemáticas de juegos aplicadas · Fuente: Eric Lengyel, Mathematics for 3D Game Programming · Documentación oficial de Godot 4 (RigidBody3D) ⏱️ Duración estimada: 60 min · Nivel: Intermedio
Dominar la física de proyectiles con gravedad: lanzar un objeto que describe una parábola, calcular la velocidad inicial necesaria para acertar a un punto dado, y predecir la posición futura de un blanco móvil para "adelantar" el disparo (lead). Combinarás simulación con RigidBody3D y las fórmulas de balística resueltas en GDScript.
Al finalizar, el alumno podrá:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Movimiento parabólico | Todo proyectil con gravedad sigue una parábola |
| 2 | Descomposición de velocidad | Separar componentes horizontal y vertical |
| 3 | Tiempo de vuelo | Clave para saber dónde caerá el proyectil |
| 4 | Cálculo de velocidad inicial | Apuntar a un punto exacto, no "a ojo" |
| 5 | Dos soluciones de ángulo | Tiro tenso vs. tiro en arco (mortero) |
| 6 | Predicción de intercepción | Acertar a un enemigo que se mueve |
| 7 | Lead (adelanto) | Disparar donde estará, no donde está |
| 8 | Limitaciones | Sin solución si el objetivo está fuera de alcance |
p(t) = p₀ + v₀·t + ½·g·t².t en que proyectil y blanco móvil coinciden en el espacio.Necesitas Godot 4.2+. Prepara una escena 3D con un cañón (un Node3D de origen), un proyectil como RigidBody3D con CollisionShape3D, y un objetivo. Verifica el valor de gravedad en Project Settings → Physics → 3D → Default Gravity (por defecto 9.8) porque las fórmulas deben usar ese mismo valor. Para depurar, dibuja la trayectoria prevista con puntos. Recuerda que la gravedad de Godot apunta hacia -Y. Consulta: https://docs.godotengine.org/en/stable/classes/class_rigidbody3d.html y el capítulo de cinemática de Lengyel.
extends Node3D
@export var proyectil: PackedScene
@export var velocidad_salida: float = 20.0
func disparar(direccion: Vector3) -> void:
var p: RigidBody3D = proyectil.instantiate()
get_tree().current_scene.add_child(p)
p.global_position = global_position
# Impulso inicial: la gravedad hará el resto (parábola).
p.apply_central_impulse(direccion.normalized() * velocidad_salida * p.mass)
Observable: el proyectil sale recto y va cayendo describiendo un arco; a mayor velocidad_salida, más lejos llega antes de tocar el suelo.
Dado un objetivo y un ángulo de lanzamiento elegido, calculamos la rapidez necesaria. Trabajamos con la componente horizontal (distancia en el plano XZ) y la vertical (diferencia de altura).
# Devuelve el vector velocidad inicial para pasar por 'objetivo'
# con un 'angulo' de elevación dado. Vacío si no hay solución.
func velocidad_para_acertar(origen: Vector3, objetivo: Vector3,
angulo_rad: float, gravedad: float) -> Vector3:
var delta := objetivo - origen
var dir_horiz := Vector3(delta.x, 0.0, delta.z)
var dist := dir_horiz.length() # alcance horizontal
var altura := delta.y # desnivel (puede ser negativo)
var cos_a := cos(angulo_rad)
var tan_a := tan(angulo_rad)
# Denominador de la ecuación balística.
var denom := 2.0 * cos_a * cos_a * (dist * tan_a - altura)
if denom <= 0.0:
return Vector3.ZERO # sin solución con ese ángulo
# Rapidez que satisface la ecuación balística para ese ángulo.
var rapidez := sqrt(gravedad * dist * dist / denom)
# Componer el vector final: horizontal + vertical.
var v_horiz := dir_horiz.normalized() * rapidez * cos_a
var v_vert := Vector3.UP * rapidez * sin(angulo_rad)
return v_horiz + v_vert
Observable: aplica el vector devuelto como impulso (impulso = v * mass) y el proyectil impacta el objetivo. Cambia angulo_rad (p. ej. 30° vs 70°) para tiro tenso o en arco hacia el mismo blanco.
Si el blanco se mueve, apuntar a su posición actual falla. Estimamos dónde estará cuando llegue el proyectil y apuntamos ahí. Una aproximación robusta: iterar el tiempo de vuelo.
# Estima la posición de intercepción de un blanco que se mueve
# a 'vel_blanco' constante, con proyectil de rapidez 'rapidez_proj'.
func predecir_intercepcion(origen: Vector3, pos_blanco: Vector3,
vel_blanco: Vector3, rapidez_proj: float) -> Vector3:
var t := 0.0
var punto := pos_blanco
# Iteramos: con cada estimación de tiempo, refinamos el punto.
for _i in range(6):
var dist := origen.distance_to(punto)
t = dist / rapidez_proj # tiempo aproximado de vuelo
punto = pos_blanco + vel_blanco * t # dónde estará el blanco
return punto
Observable: dispara a un objetivo que se desplaza lateralmente usando predecir_intercepcion como punto de mira; el proyectil lo alcanza en movimiento, mientras que apuntar a la posición actual falla por detrás.
p(t) = p₀ + v₀·t + ½·g·t².Implementa una torreta que dispare proyectiles con gravedad a un objetivo móvil: debe calcular la velocidad inicial para el alcance y adelantar el tiro según la velocidad del blanco, acertando de forma consistente mientras el blanco se mueve a velocidad constante.
Criterio de aceptación: con el blanco desplazándose lateralmente a velocidad constante, la torreta acierta en al menos 8 de 10 disparos; los proyectiles describen una parábola visible y el punto de mira se adelanta al blanco (no le dispara donde ya estuvo).
| Síntoma | Causa y arreglo |
|---|---|
| El proyectil cae demasiado corto | La gravedad de la fórmula no coincide con la del proyecto. Usa el mismo valor. |
| Impulso vs. velocidad confundidos | apply_central_impulse recibe cantidad de movimiento. Multiplica la velocidad por mass. |
| Nunca acierta a blanco móvil | Apuntas a la posición actual. Usa la predicción de intercepción. |
sqrt de número negativo |
Objetivo fuera de alcance con ese ángulo. Comprueba el denominador antes de la raíz. |
| Trayectoria plana | La gravedad del cuerpo está desactivada (gravity_scale = 0). Actívala. |
¿Simular con RigidBody o calcular la parábola a mano? Para pocos proyectiles, RigidBody3D es cómodo; para cientos de balas, calcula la posición con la ecuación y sáltate la física del motor.
¿Por qué dos ángulos para el mismo blanco? El alcance es simétrico: un ángulo bajo (rápido y tenso) y otro alto (lento y en arco) llegan al mismo punto.
¿La predicción funciona si el blanco acelera? La versión iterativa asume velocidad constante; con aceleración, incorpora el término ½·a·t² en la estimación.
¿Afecta el arrastre del aire? Las fórmulas ideales lo ignoran; con linear_damp alto, ajusta empíricamente o simula por pasos.
Clase 078 - Vehículos: física de ruedas y suspensión