Parte: 3 — Física y matemáticas de juegos aplicadas · Fuente: Godot Engine 4.x — Vector math (documentación oficial) y práctica de aula ⏱️ Duración estimada: 55 min · Nivel: Intermedio
Recuperar los conceptos de vectores y transformaciones desde la práctica real del motor, no desde la teoría abstracta. Al terminar sabrás responder preguntas que aparecen todos los días al programar un juego: ¿el enemigo me está mirando de frente?, ¿hacia dónde debo moverme para alcanzar un objetivo?, ¿cómo rebota una velocidad contra una pared? Usaremos Vector2 y Vector3 de Godot 4 y sus operaciones nativas.
Al finalizar, el alumno podrá:
dot) para medir el ángulo entre direcciones y resolver el problema "de frente vs. de espaldas"..normalized() y .length()..bounce() o la fórmula manual).| # | Tema | Por qué importa |
|---|---|---|
| 1 | Vector como punto y como flecha | Evita el error de sumar posiciones sin sentido |
| 2 | Normalización de direcciones | Separa "hacia dónde" de "cuánto" |
| 3 | Producto punto y ángulo | Base de visión, IA y iluminación |
| 4 | Frente vs. espalda con signo del dot | Detección de sigilo y ataques por la espalda |
| 5 | Distancia y dirección a un objetivo | Movimiento, seguimiento, rangos de ataque |
| 6 | Proyección de un vector | Deslizamiento sobre superficies |
| 7 | Reflexión de una velocidad | Rebotes de proyectiles y pelotas |
.normalized().a.dot(b)): escalar igual a |a||b|cos(θ). Clave: con vectores unitarios, su valor va de -1 (opuestos) a 1 (iguales)..length()): longitud del vector. Clave: usa .length_squared() para comparar distancias sin la raíz cuadrada.a.project(b)): sombra de a sobre la dirección de b. Clave: descompone velocidad en componentes paralela y perpendicular..bounce(n)): espeja el vector respecto a la superficie de normal n. Clave: bounce invierte, reflect no; revisa cuál usa tu versión..lerp(b, t)): mezcla lineal entre dos vectores con t en [0,1]. Clave: útil para suavizar movimiento.Necesitas Godot 4.x (descarga en godotengine.org). Crea un proyecto 2D vacío y una escena con un Node2D raíz. Trabajaremos con un script de prueba que imprime resultados en la consola de salida, así que no hace falta arte: los números son lo observable. Ten a mano la referencia de Vector2. Recuerda que en Godot 2D el eje Y crece hacia abajo, algo que afecta el signo de los ángulos.
Vamos a resolver tres problemas reales en un solo script. Crea un Node2D, adjúntale este script y observa la consola.
Paso 1 — ¿El enemigo me ve de frente? El enemigo mira en una dirección; comparamos con la dirección hacia el jugador.
extends Node2D
func _ready() -> void:
# Enemigo en (0,0) mirando hacia la derecha.
var enemigo_pos := Vector2.ZERO
var enemigo_frente := Vector2.RIGHT # ya es unitario
var jugador_pos := Vector2(6, 2)
# Dirección desde el enemigo hacia el jugador.
var hacia_jugador := (jugador_pos - enemigo_pos).normalized()
var d := enemigo_frente.dot(hacia_jugador)
print("dot = ", d) # ~0.949
# Cono de visión: cos(45°) ≈ 0.707. Si dot supera ese umbral, lo ve.
if d > cos(deg_to_rad(45.0)):
print("El enemigo VE al jugador de frente")
elif d < 0.0:
print("El jugador está a la ESPALDA del enemigo")
else:
print("El jugador está de lado")
Paso 2 — Moverse hacia el objetivo con distancia. Separamos "hacia dónde" de "cuánto falta".
var distancia := enemigo_pos.distance_to(jugador_pos)
print("distancia = ", distancia) # ~6.324
var paso := hacia_jugador * min(2.0, distancia) # avanza 2 px o lo que falte
print("nueva pos = ", enemigo_pos + paso)
Paso 3 — Reflejar una velocidad contra una pared. Un proyectil golpea una pared vertical (normal apuntando a la izquierda).
var velocidad := Vector2(5, -3)
var normal_pared := Vector2.LEFT # pared a la derecha, normal hacia -X
var rebote := velocidad.bounce(normal_pared)
print("velocidad tras rebote = ", rebote) # (-5, -3): invierte X, conserva Y
Ejecuta (F6). Verás en consola el dot, la clasificación de visión, la distancia y la velocidad reflejada. Cambia jugador_pos a (-6, 0) y confirma que ahora aparece "a la ESPALDA" (dot negativo).
angulo_grados(a, b) que devuelva el ángulo entre dos vectores usando a.angle_to(b) y rad_to_deg..length_squared() para comparar cuál de dos enemigos está más cerca sin calcular raíces.velocidad.project(dir_rampa) e imprime la componente de deslizamiento.Vector2(4, 4) contra una normal diagonal Vector2(-1, -1).normalized() y verifica el resultado a mano.Vector3 y una pared con normal = Vector3.LEFT.Crea un script que reciba la posición del jugador, la del enemigo y la dirección de mirada del enemigo, y clasifique la situación en tres categorías impresas en consola: "VISIBLE", "DE_LADO" o "A_ESPALDA", usando un cono configurable con @export var angulo_vision := 60.0.
Criterio de aceptación: con el enemigo en el origen mirando a Vector2.RIGHT y angulo_vision = 60, un jugador en (10, 0) imprime VISIBLE, uno en (0, 10) imprime DE_LADO y uno en (-10, 0) imprime A_ESPALDA.
| Síntoma | Causa y arreglo |
|---|---|
El dot da valores raros (>1 o muy grandes) |
Olvidaste .normalized(); el dot solo va en [-1,1] con unitarios |
| El enemigo "ve" en todas direcciones | Comparaste dot > 0 en vez de dot > cos(mitad del ángulo) |
| La distancia sale enorme al comparar | Mezclaste length() con length_squared(); no compares uno contra otro |
| El rebote invierte el eje equivocado | La normal apunta al lado incorrecto; en 2D recuerda que Y crece hacia abajo |
normalized() de un vector cero da NaN |
Verifica if v.length() > 0.0 antes de normalizar |
¿Cuándo normalizo y cuándo no? Normaliza cuando solo te importa la dirección (mirada, empuje). No normalices posiciones ni cuando la magnitud es información (velocidad, distancia).
¿dot sirve en 3D igual que en 2D? Sí, la operación y su interpretación (coseno del ángulo) son idénticas en Vector3.
¿Diferencia entre bounce y reflect? En Godot, bounce(n) devuelve el vector reflejado "hacia afuera" (invertido); reflect(n) refleja respecto a la línea de la normal. Para rebotes físicos usa bounce.
¿Por qué usar length_squared? Evita la raíz cuadrada, que es costosa. Para saber solo cuál está más cerca, comparar cuadrados basta y es más rápido.
Clase 067 - Capstone Parte 2: un nivel 3D explorable en tercera persona