Organización de escenas en Godot 4
Escena, script o recurso
- Escena (
.tscn): un árbol reutilizable de nodos con sus propios recursos: un jugador, un enemigo, un objeto recogible, un panel de UI, un nivel. Cualquier cosa que coloques más de una vez o que quieras ejecutar sola (F6) es una escena. - Script (
.gd): comportamiento adjunto a un nodo. Adjúntalo a la raíz de la escena que controla, no a un padre que accede a ella. - Script de recurso (
extends Resourceconclass_name): datos sin un nodo, como estadísticas de objetos, definiciones de oleadas o datos de guardado, editables en el Inspector y guardados como.tres.
Una escena debería funcionar al ejecutarse por sí sola. Si necesita algo del exterior (un objetivo, un padre de generación), exponlo con @export o una señal en lugar de cadenas de get_parent().
Instanciación de escenas
extends Node2D
@export var enemy_scene: PackedScene
@export var spawn_parent: Node2D
func spawn_enemy(at: Vector2) -> void:
var enemy := enemy_scene.instantiate() as Node2D
enemy.position = at
spawn_parent.add_child(enemy)
func load_level(path: String) -> void:
var packed := load(path) as PackedScene
if packed == null:
push_error("Nivel no encontrado: " + path)
return
get_tree().change_scene_to_packed(packed)
@export var x: PackedScenese configura en el Inspector y sobrevive a movimientos de archivos, porque la escena almacena la referencia.const BULLET := preload("res://bullet.tscn")se resuelve en tiempo de análisis: un archivo faltante es un error de análisis (Preload file "res://bullet.tscn" does not exist.). Bueno para activos fijos.load()se resuelve en tiempo de ejecución y devuelvenullcon un error si la ruta es incorrecta. Úsalo para rutas construidas en tiempo de ejecución.instantiate()(no elinstance()de Godot 3) devuelve unNode; conviértelo al tipo que necesites. Libera las instancias conqueue_free().
Encontrar nodos
extends Control
@onready var title: Label = %Title
@onready var start_button: Button = %StartButton
@onready var music: AudioStreamPlayer = $Music
@onready var optional_hint: Label = get_node_or_null("Hint")
func _ready() -> void:
title.text = "Menú principal"
start_button.grab_focus()
if optional_hint:
optional_hint.visible = false
music.play()
$Pathesget_node("Path"), relativo a este nodo. Da error si el nodo falta; usaget_node_or_null()para nodos opcionales.%Namealcanza un nodo marcado como "Acceder como Nombre Único" (clic derecho en el dock de la Escena) en cualquier lugar de la misma escena, por lo que sobrevive a cambios de padre dentro de la escena. No alcanza escenas hijas instanciadas: usa$Child/%Name, o%Child/%Namecuando la instancia en sí es única.- Las variables
@onreadyse asignan justo antes de_ready(). Sonnullen_init()y_enter_tree(). _ready()se ejecuta en los hijos antes que en su padre, por lo que el_ready()de un padre puede usar sus hijos, pero el_ready()de un hijo no debe depender de que el padre esté listo.- Tipifica las variables
@onready(: Label) para que el analizador verifique los nombres de los miembros;$Label.txten una ruta sin tipo solo se detecta en tiempo de ejecución.
Grupos
Los grupos etiquetan nodos a través de escenas. Agréguelos en el dock de Nodos (pestaña Grupos) o en código, luego consulta el árbol:
extends Node
func _ready() -> void:
add_to_group("enemies")
func alert_all_enemies() -> void:
get_tree().call_group("enemies", "set_alerted", true)
for enemy in get_tree().get_nodes_in_group("enemies"):
print(enemy.name)
var player := get_tree().get_first_node_in_group("player") as Node2D
if player:
print("Jugador en ", player.global_position)
call_group() llama inmediatamente en Godot 4. Usa get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFERRED, ...) para diferir.
Autoloads
Un autoload es un nodo creado antes de la escena principal y se mantiene a través de los cambios de escena. Úsalo para estado que sobrevive a una escena (configuraciones, datos guardados, un cambiador de escenas) y para un bus de eventos solo de señales. Regístralo en Proyecto > Ajustes del Proyecto > Globales > Autoload; project.godot entonces contiene:
[autoload]
Events="*res://autoload/events.gd"
El * significa que "Habilitado" está marcado, por lo que los scripts pueden usar el nombre directamente (Events.player_died.emit()). No hagas free() ni queue_free() a un autoload. Mantén pocos autoloads: cualquier cosa que pertenezca a un nivel debe vivir en la escena de ese nivel.
Nota para herramientas: godot --check-only --script no carga autoloads, por lo que un script que usa un nombre de autoload reporta Identifier not found: Events allí aunque funcione correctamente. Verifica dichos scripts ejecutando la escena (consulta el skill godot-verify).
class_name
class_name Player registra un tipo global, por lo que otros scripts pueden escribir var player: Player y obtener verificación de miembros. El editor mantiene un caché de clases global en .godot/; después de agregar un class_name fuera del editor, abre el editor o ejecuta godot --headless --path . --import antes de verificar los scripts que lo usan.
UIDs, archivos .uid y mover archivos
Godot 4 referencia recursos por UID además de por ruta. Las escenas y los recursos almacenan su propio UID en el encabezado del archivo, los activos importados en su archivo .import, y desde Godot 4.4 los scripts y shaders obtienen un archivo compañero (player.gd.uid, water.gdshader.uid). Una referencia guardada por el editor se ve así:
[ext_resource type="Script" uid="uid://dxxoiuob4eoum" path="res://game_manager.gd" id="1_feb5d"]
Reglas:
- Incluye los archivos
*.uiden el control de versiones. No los agregues a.gitignore. - Mueve y renombra archivos en el dock del Sistema de Archivos de Godot cuando puedas; actualiza las referencias.
- Al mover un script o shader fuera de Godot (shell, IDE, agente), mueve su archivo
.uidcon él, luego abre el editor o ejecutagodot --headless --path . --importpara que el caché de UID aprenda la nueva ruta. Hasta ese escaneo, se sigue usando la ruta anterior y la carga falla. - Si el archivo
.uidse queda atrás, Godot genera un nuevo UID para el script movido y las escenas que referenciaban al antiguo recurren a su ruta obsoleta:WARNING: ... invalid UID: uid://... - using text path instead: res://old/path.gd, luegoERROR: ... referenced non-existent resource. - Al escribir un
.tscna mano, no inventes valoresuid=. Omite el atributo y usa unapathcorrecta; el editor agrega UIDs la próxima vez que guarde la escena. - Una escena de Godot 4 escrita a mano comienza con
[gd_scene format=3]y referencia recursos por id de cadena:[ext_resource type="Script" path="res://main.gd" id="1_main"], luegoscript = ExtResource("1_main")en el nodo. Los nodos hijos necesitanparent="."o una ruta desde la raíz. Godot 4.7 también escribeunique_id=en los nodos; omítelo a mano. load("uid://...")ypreload("uid://...")aceptan cadenas UID. Copia un UID desde el menú contextual del dock del Sistema de Archivos;ResourceUID.id_to_text(ResourceLoader.get_resource_uid(path))lo devuelve desde código.
Errores comunes
- Acceder hacia arriba con
get_parent().get_parent()o rutas absolutas como/root/Main/Player. Exporta una referencia, usa un grupo o emite una señal. - Usar un nodo en
_init()o antes de que haya entrado en el árbol. - Codificar rutas
res://a escenas en muchos scripts en lugar de exportar unPackedScene. - Mover archivos
.gdsin sus archivos.uid, o ignorar*.uiden git. - Hacer que todo sea un autoload porque es conveniente.