Parte: 2 — Desarrollo 3D: motores, escenas y transformaciones · Fuente: Godot Engine 4 — Documentación oficial: SpringArm3D y Using CharacterBody3D ⏱️ Duración estimada: 65 min · Nivel: Intermedio
Crear un controlador en tercera persona en Godot 4 con una cámara orbital controlada por el ratón, usando un SpringArm3D para que la cámara no atraviese paredes, movimiento relativo a la cámara y un personaje que rota suavemente hacia su dirección de desplazamiento.
Al finalizar, el alumno podrá:
clamp.SpringArm3D para evitar que la cámara traspase la geometría.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Jerarquía de cámara orbital | Separar personaje, pivote y brazo simplifica la lógica. |
| 2 | SpringArm3D | Retrae la cámara ante obstáculos automáticamente. |
| 3 | Órbita con el ratón | Permite inspeccionar al personaje y el entorno. |
| 4 | Clamp del pitch | Evita ángulos de cámara imposibles. |
| 5 | Movimiento relativo a cámara | Control intuitivo en tercera persona. |
| 6 | Rotación del personaje | El modelo mira hacia donde avanza. |
| 7 | Interpolación de ángulo | Giro suave sin saltos bruscos. |
| 8 | Gravedad y salto | Física básica coherente con el resto. |
spring_length define la distancia deseada.event.relative. Clave: mismo patrón que el mouse look.(x, z) en yaw objetivo.Necesitas Godot 4.x, un nivel con suelo, paredes y algún obstáculo para probar la retracción del SpringArm3D, y un MeshInstance3D con forma reconocible (una flecha o cápsula con "frente") para ver hacia dónde mira el personaje. Define las acciones de movimiento y saltar en el Input Map. Consulta la API de SpringArm3D en https://docs.godotengine.org/en/stable/classes/class_springarm3d.html y la guía 3D en https://docs.godotengine.org/en/stable/tutorials/3d/introduction_to_3d.html. Motor: https://godotengine.org/download.
CharacterBody3D llamada JugadorTP, con su CollisionShape3D (cápsula) y un MeshInstance3D como Modelo cuyo frente apunte a -Z.Node3D llamado Pivote a la altura del pecho. Dentro de Pivote, añade un SpringArm3D con spring_length = 5.0. Dentro del brazo, una Camera3D.JugadorTP → Pivote (yaw+pitch) → SpringArm3D → Camera3D.JugadorTP:extends CharacterBody3D
@export var velocidad: float = 5.0
@export var fuerza_salto: float = 4.5
@export var sensibilidad: float = 0.005
@export var giro_suave: float = 10.0
var gravedad: float = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var pivote: Node3D = $Pivote
@onready var modelo: Node3D = $Modelo
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
# Yaw del pivote (orbita horizontal alrededor del jugador).
pivote.rotate_y(-event.relative.x * sensibilidad)
# Pitch del pivote con clamp.
pivote.rotation.x -= event.relative.y * sensibilidad
pivote.rotation.x = clamp(pivote.rotation.x,
deg_to_rad(-60.0), deg_to_rad(30.0))
if event.is_action_pressed("ui_cancel"):
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y -= gravedad * delta
if Input.is_action_just_pressed("saltar") and is_on_floor():
velocity.y = fuerza_salto
var entrada := Input.get_vector("mover_izquierda", "mover_derecha",
"mover_adelante", "mover_atras")
# Dirección relativa al yaw del pivote (la cámara).
var yaw := pivote.rotation.y
var direccion := Vector3(entrada.x, 0.0, entrada.y).rotated(Vector3.UP, yaw)
direccion = direccion.normalized()
if direccion != Vector3.ZERO:
velocity.x = direccion.x * velocidad
velocity.z = direccion.z * velocidad
# Rotar el modelo hacia la dirección de avance, suavemente.
var angulo_objetivo := atan2(direccion.x, direccion.z)
modelo.rotation.y = lerp_angle(modelo.rotation.y, angulo_objetivo,
giro_suave * delta)
else:
velocity.x = move_toward(velocity.x, 0.0, velocidad)
velocity.z = move_toward(velocity.z, 0.0, velocidad)
move_and_slide()
SpringArm3D acerca la cámara para no atravesarla y la aleja de nuevo al despejarse.spring_length con la rueda del ratón.spring_length y collision_mask del brazo para afinar la retracción.lerp en lugar de aplicarlo directo.Entrega un controlador en tercera persona: cámara orbital con SpringArm3D que evita atravesar paredes, pitch clampeado, movimiento relativo a la cámara, y un modelo que rota suavemente (con lerp_angle) hacia la dirección de avance.
Criterio de aceptación: la cámara orbita con el ratón sin traspasar la geometría, el personaje se mueve relativo a la cámara y su modelo encara la dirección de avance con un giro suave, sin errores en consola.
| Síntoma | Causa y arreglo |
|---|---|
| La cámara atraviesa paredes | No usas SpringArm3D o su collision_mask no incluye las paredes. |
| El personaje gira a tirones | Aplicas el ángulo directo; usa lerp_angle con delta. |
| El giro salta 360° al cruzar ±180° | Usas lerp normal; lerp_angle toma el camino corto. |
| El movimiento no sigue la cámara | No rotas la entrada por el yaw del pivote. |
| La cámara se voltea | Falta clamp en pivote.rotation.x. |
| El SpringArm choca con el propio jugador | Excluye el cuerpo con add_excluded_object o ajusta la máscara. |
❓ ¿Por qué el SpringArm3D cuelga del pivote y no del cuerpo? Porque el pivote orbita con el ratón; así el brazo y la cámara heredan el yaw y el pitch sin afectar al personaje.
❓ ¿Qué hace lerp_angle distinto de lerp? Interpola tomando el camino angular más corto, evitando giros absurdos al cruzar el límite de ±π.
❓ ¿Cómo evito que la cámara detecte al propio jugador? Configura la collision_mask del SpringArm3D para ignorar la capa del personaje.
❓ ¿Puedo usar look_at en vez de atan2? Sí, pero look_at reorienta todo el nodo; con atan2 + lerp_angle controlas solo el yaw de forma suave.
Clase 055 - Controlador en primera persona (FPS)