Parte: 2 — Desarrollo 3D: motores, escenas y transformaciones · Fuente: Godot Engine 4 — Documentación oficial: Mouse and input coordinates, CharacterBody3D ⏱️ Duración estimada: 65 min · Nivel: Intermedio
Construir un controlador en primera persona completo en Godot 4: capturar el ratón para el mouse look, girar el cuerpo en el eje Y y la cabeza/cámara en el eje X con un clamp para no voltear la vista, moverse con WASD y saltar, separando el nodo de la cabeza del cuerpo.
Al finalizar, el alumno podrá:
Input.MOUSE_MODE_CAPTURED.event.relative en _unhandled_input.clamp.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Captura del ratón | Permite mirar libremente sin salir de la ventana. |
| 2 | _unhandled_input | Lugar adecuado para leer el movimiento del ratón. |
| 3 | event.relative | Delta del ratón para rotar la vista. |
| 4 | Yaw en el cuerpo | Girar horizontalmente todo el personaje. |
| 5 | Pitch en la cabeza | Mirar arriba/abajo sin inclinar el cuerpo. |
| 6 | Clamp del pitch | Evita voltear la cámara boca abajo. |
| 7 | Movimiento relativo al cuerpo | Adelante = hacia donde mira el jugador. |
| 8 | Jerarquía de nodos | Orden cuerpo→cabeza→cámara para rotaciones limpias. |
InputEventMouseMotion.Vector2 con el desplazamiento del ratón desde el último frame. Clave: base del mouse look.CharacterBody3D (cuerpo).rotation está en radianes.Node3D intermedio que sostiene la cámara. Clave: aísla el pitch del yaw.Usa Godot 4.x con un nivel simple (suelo con colisión, algunas paredes y cajas como referencia visual). Define en el Input Map las acciones mover_izquierda, mover_derecha, mover_adelante, mover_atras y saltar. Consulta la captura del ratón en https://docs.godotengine.org/en/stable/tutorials/inputs/mouse_and_input_coordinates.html y la API de Input en https://docs.godotengine.org/en/stable/classes/class_input.html. Descarga el motor: https://godotengine.org/download.
CharacterBody3D llamada JugadorFPS. Agrégale un CollisionShape3D con CapsuleShape3D.Node3D llamado Cabeza, situado a la altura de los ojos (position = Vector3(0, 1.6, 0)). Dentro de Cabeza, añade una Camera3D.JugadorFPS (yaw) → Cabeza (pitch) → Camera3D.JugadorFPS:extends CharacterBody3D
@export var velocidad: float = 5.0
@export var fuerza_salto: float = 4.5
@export var sensibilidad: float = 0.003
var gravedad: float = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var cabeza: Node3D = $Cabeza
func _ready() -> void:
# Capturar el ratón al iniciar.
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
# Yaw: girar el cuerpo en Y.
rotate_y(-event.relative.x * sensibilidad)
# Pitch: girar la cabeza en X con clamp.
cabeza.rotate_x(-event.relative.y * sensibilidad)
cabeza.rotation.x = clamp(cabeza.rotation.x,
deg_to_rad(-89.0), deg_to_rad(89.0))
# Liberar el ratón con Escape.
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
# Entrada de teclado.
var entrada := Input.get_vector("mover_izquierda", "mover_derecha",
"mover_adelante", "mover_atras")
# Dirección relativa a la orientación del cuerpo.
# -transform.basis.z es el "adelante" local tras el yaw.
var direccion := (transform.basis * Vector3(entrada.x, 0.0, entrada.y)).normalized()
if direccion != Vector3.ZERO:
velocity.x = direccion.x * velocidad
velocity.z = direccion.z * velocidad
else:
velocity.x = move_toward(velocity.x, 0.0, velocidad)
velocity.z = move_toward(velocity.z, 0.0, velocidad)
move_and_slide()
sensibilidad en un menú y ajústala en vivo.Control.Entrega un controlador FPS jugable en un nivel con paredes: mouse look con pitch clampeado a ±89°, movimiento WASD relativo a la vista, salto desde el suelo y liberación/recaptura del ratón con Escape/clic.
Criterio de aceptación: el jugador mira con el ratón sin voltear la cámara, se mueve relativo a su orientación, salta solo desde el suelo, y el ratón se libera con Escape y se recaptura al hacer clic, sin errores en consola.
| Síntoma | Causa y arreglo |
|---|---|
| El cursor se ve y no rota | Falta Input.mouse_mode = Input.MOUSE_MODE_CAPTURED. |
| La cámara se voltea boca abajo | No hay clamp del pitch; acótalo con deg_to_rad(±89). |
| El cuerpo se inclina al mirar arriba | Aplicas el pitch al cuerpo; debe ir en la cabeza. |
| El movimiento no sigue la vista | No multiplicas por transform.basis; usa la base del cuerpo. |
| El ratón nunca se libera | No manejas ui_cancel; añade el cambio a MOUSE_MODE_VISIBLE. |
| Giro demasiado rápido o brusco | Sensibilidad alta; reduce el multiplicador. |
❓ ¿Por qué separar cabeza y cuerpo? Así el yaw rota todo el personaje (y con él el movimiento) mientras el pitch solo inclina la vista, evitando inclinar el cuerpo.
❓ ¿Por qué usar _unhandled_input y no _input? Para que la UI pueda consumir eventos primero; el mouse look solo actúa sobre lo que no capturó la interfaz.
❓ ¿event.relative depende de los FPS? Es el delta acumulado del ratón entre eventos; multiplicarlo por la sensibilidad da un giro consistente.
❓ ¿Puedo usar rotation_degrees en la cabeza? Sí, pero recuerda que clamp y deg_to_rad trabajan en radianes si operas sobre rotation.x.
Clase 054 - Movimiento 3D: CharacterBody3D y move_and_slide
Clase 056 - Controlador en tercera persona con cámara orbital