Parte: 1 — Motores 2D y tu primer juego jugable · Fuente: Documentación de Godot 4 (Idle and Physics processing, Signals) ⏱️ Duración estimada: 80 min · Nivel: Intermedio
Comprender en la práctica cómo Godot ejecuta tu juego cada frame mediante _process(delta) y _physics_process(delta), cuándo usar cada uno y por qué el delta es esencial para un movimiento independiente de la velocidad del equipo.
Además, aprenderás el sistema de eventos de Godot: las señales. Declararás una señal propia, la conectarás por el editor y por código, y la emitirás para desacoplar objetos. El laboratorio mueve el sprite con delta y usa un Timer con señal para llevar un contador en pantalla.
Al finalizar, el alumno podrá:
_process y _physics_process y elegir el correcto.delta para lograr movimiento independiente de los FPS.| # | Tema | Por qué importa |
|---|---|---|
| 1 | El game loop de Godot | Es el corazón que ejecuta tu lógica cada frame. |
| 2 | _process(delta) |
Lógica de render/visual cada frame variable. |
| 3 | _physics_process(delta) |
Física en pasos fijos y estables. |
| 4 | Qué es delta |
Permite movimiento uniforme sin importar los FPS. |
| 5 | Señales de Godot | Sistema de eventos para comunicar nodos. |
| 6 | Conectar por editor | Enlaza señales sin escribir código. |
| 7 | Conectar y emitir por código | Control dinámico del flujo de eventos. |
| 8 | Nodo Timer | Fuente de eventos periódicos con señal. |
_process(delta): se llama cada frame de render, a ritmo variable. Clave: ideal para animación y lógica no física._physics_process(delta): se llama a paso fijo (por defecto 60/s). Clave: obligatorio para mover cuerpos físicos con estabilidad.delta: segundos transcurridos desde el frame anterior. Clave: multiplicar por delta hace el movimiento independiente de los FPS.nombre.emit(args). Clave: notifica a todos los conectados en ese instante.senal.connect(metodo). Clave: puede hacerse por editor o por código.timeout cada cierto tiempo. Clave: fuente natural de eventos periódicos.Sigue en PlataformasCurso. Trabajaremos sobre escenas/jugador.tscn y escenas/mundo.tscn. No hacen falta assets nuevos. Ten a mano el panel Output para ver los prints y el panel Node → Signals (a la derecha, junto al Inspector) para conectar señales por editor.
Documentación de apoyo: procesamiento en https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html y señales en https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html.
Moveremos el sprite con delta, añadiremos un Timer con señal y crearemos una señal propia.
escenas/jugador.tscn y reemplaza jugador.gd por un movimiento horizontal sencillo basado en delta:extends Node2D
@export var velocidad: float = 120.0 # pixeles por segundo
var direccion: int = 1
func _process(delta: float) -> void:
# Movimiento independiente de los FPS: pixeles/seg * segundos.
position.x += velocidad * direccion * delta
# Rebote simple al salir de los limites horizontales.
if position.x > 1000.0:
direccion = -1
elif position.x < 152.0:
direccion = 1
Ejecuta con F5. El sprite se desplaza a velocidad constante. Si tu equipo corriera a más FPS, delta sería menor y el resultado visual sería el mismo: esa es la clave.
Ahora un contador con Timer. Abre escenas/mundo.tscn, selecciona Mundo, añade un hijo Timer y renómbralo TimerContador. En el Inspector pon Wait Time a 1, activa Autostart y deja One Shot desactivado.
Añade un nodo Label como hijo de Mundo, llámalo EtiquetaContador, colócalo arriba a la izquierda y escribe un texto inicial como Segundos: 0.
Conecta la señal del Timer por el editor: selecciona TimerContador, ve a la pestaña Node → Signals, doble clic en timeout(), elige Mundo como receptor y confirma. Godot creará el método _on_timer_contador_timeout() en mundo.gd.
Añade también una señal propia para practicar el desacople. Edita mundo.gd así:
extends Node2D
signal cada_diez_segundos # senal propia que emitiremos nosotros
var segundos: int = 0
func _ready() -> void:
# Conexion de una senal propia POR CODIGO.
cada_diez_segundos.connect(_on_cada_diez_segundos)
# Conectada al Timer DESDE EL EDITOR (pestana Node > Signals).
func _on_timer_contador_timeout() -> void:
segundos += 1
$EtiquetaContador.text = "Segundos: " + str(segundos)
if segundos % 10 == 0:
cada_diez_segundos.emit() # emitimos nuestra senal
func _on_cada_diez_segundos() -> void:
print("Han pasado 10 segundos. Total: ", segundos)
Ejecuta con F5. La etiqueta debe subir de uno en uno cada segundo, y cada diez segundos el Output imprimirá el aviso emitido por tu señal propia.
Para comparar _process con _physics_process, añade temporalmente al mundo.gd estos dos métodos y observa la frecuencia en Output:
func _process(delta: float) -> void:
pass # se llama cada frame de render (variable)
func _physics_process(delta: float) -> void:
pass # se llama a paso fijo (por defecto 60 veces por segundo)
Ya tienes claro el ritmo del juego y el patrón de señales para eventos. En la próxima clase usaremos _physics_process con input real.
velocidad desde el Inspector (gracias a @export) y observa el efecto sin tocar código._physics_process y compara la suavidad.Label que muestre los FPS con Engine.get_frames_per_second().rebote en el jugador que se emita cada vez que cambia de dirección e imprímela desde el mundo.$TimerContador.start() en _ready().Haz que el jugador emita una señal rebote(nueva_direccion: int) cada vez que invierte su marcha, y que el mundo escuche esa señal (conectándola por código a la instancia del jugador) para contar cuántos rebotes lleva y mostrarlo en una Label.
Criterio de aceptación: al ejecutar, el jugador se mueve y rebota entre los límites, y una etiqueta actualiza el número de rebotes en tiempo real usando la señal, sin que el mundo lea directamente la variable direccion.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| El movimiento es más rápido en equipos potentes | Olvidaste multiplicar por delta. Usa velocidad * delta. |
| "Signal 'timeout' is already connected" | Conectaste la señal por editor y por código a la vez. Deja solo una. |
El método _on_..._timeout no se ejecuta |
El nombre no coincide o la conexión se borró. Revisa la pestaña Node → Signals. |
| "Nonexistent function 'emit'" o error al emitir | Usaste sintaxis de Godot 3. En Godot 4 es mi_senal.emit(). |
| La Label no cambia | Ruta $EtiquetaContador incorrecta. Verifica el nombre exacto del nodo. |
❓ ¿Cuándo uso _process y cuándo _physics_process? Usa _physics_process para mover cuerpos físicos y lógica que deba ser estable; usa _process para animación visual y lógica no física.
❓ ¿Qué pasa si no multiplico por delta? El movimiento dependerá de los FPS: irá más rápido en equipos potentes y más lento en los débiles.
❓ ¿Conviene conectar señales por editor o por código? Ambas son válidas. El editor es cómodo para nodos fijos; el código es mejor para instancias creadas en tiempo de ejecución.
❓ ¿Las señales pueden llevar datos? Sí. Declara signal murio(puntos: int) y emítela con murio.emit(10); el método receptor recibe ese argumento.
Clase 027 - Godot: interfaz, proyecto y primer sprite en pantalla
Clase 029 - Input: teclado, ratón, gamepad y mapeo de acciones