El proyecto que acompaña a la Parte 10 (clases 188–199) y a su capstone (clase 199).
Un menú, un HUD y una pantalla de opciones que no se rompen: cambian de idioma en marcha, aguantan el texto al 200 %, se navegan enteros con el teclado y sobreviven a 16:9, 21:9 y 4:3.
es / en, cambiable en runtime (clase 197).physical_keycode (clase 195).user://ajustes.cfg (clase 199).ui-accesible/
├── inicio/ ← empieza aquí: completa los TODO
│ ├── project.godot
│ ├── i18n/textos.csv (las traducciones: esto se edita a mano)
│ ├── escenas/ (menu, hud, opciones, tema, verificar)
│ └── scripts/
│ ├── ajustes.gd ← TU TRABAJO (TODO 1-3): idioma, escala, remapeo
│ ├── hud.gd ← TU TRABAJO (TODO 4): textos hechos por código
│ ├── menu.gd ← TU TRABAJO (TODO 5): foco y navegación
│ ├── opciones.gd (los controles de la pantalla — resuelto)
│ └── verificador.gd (la prueba automática — resuelto)
└── solucion/ ← referencia completa
labs/ui-accesible/inicio/project.godot → F5.MENU_JUGAR en vez de Jugar. Ese es el síntoma clásico de la localización sin conectar, y tu punto de partida.TODO en orden:| TODO | Dónde | Qué consigues | Clase |
|---|---|---|---|
| 1 | ajustes.gd |
El idioma y el tamaño del texto se aplican a toda la UI | 190, 196, 197 |
| 2 | ajustes.gd |
Los ajustes sobreviven al cierre del juego | 199 |
| 3 | ajustes.gd |
Se puede remapear una tecla | 195 |
| 4 | hud.gd |
Los textos hechos por código también se traducen | 197 |
| 5 | menu.gd |
Hay foco, y la navegación da la vuelta | 192, 195 |
¿Atascado? Abre el archivo equivalente en
solucion/y compara. No es hacer trampa: leer código bueno es parte de aprender.
1. El .text de un Control guarda la clave, no la traducción. Si lees boton.text te devuelve MENU_JUGAR, no Jugar: el Control guarda la clave y traduce al dibujar. Lo que el jugador ve es atr(boton.text). Esto se descubre siempre depurando, y siempre tarde.
2. Los textos hechos por código no se enteran de nada. Un Label con la clave puesta en el editor se retraduce solo. Pero en cuanto haces "Vidas: " + str(vidas) eso es una cadena normal, y al cambiar de idioma se queda como estaba — mientras los botones de al lado sí cambian. De ahí que el HUD escuche una señal y se repinte (TODO 4).
3. Nunca concatenes. tr("HUD_VIDAS").format([vidas]) y no tr("VIDAS") + str(vidas): el orden de las palabras cambia entre idiomas, y con {0} el hueco puede moverse donde el idioma lo pida.
Y una del CSV: si un texto lleva una coma, el campo va entre comillas. Si no, el importador cuenta columnas de más y falla en silencio: te quedas con cero traducciones y una UI llena de claves. Míralo en
i18n/textos.csv, en la línea de los subtítulos.
La clase 199 lo dice: lo difícil no es cada pieza suelta, es que convivan. Sube el texto al 200 % y el botón necesita el doble de ancho; cambia a inglés y "Tamaño del texto" pasa a "Text size" (más corto) pero "Jugar" pasa a "Play"… y otro texto crecerá. Si la UI está montada con posiciones y tamaños fijos, se rompe.
La respuesta no es "probar mucho": es montar la UI con contenedores que crecen (VBoxContainer, MarginContainer, CenterContainer) en vez de con coordenadas. Entonces el texto empuja al botón, el botón empuja a la caja, y todo encaja solo. En este lab no hay ni un offset a mano en el menú, y por eso aguanta el 200 % sin tocar nada.
Y lo mejor: eso se puede comprobar sin mirar. Lo que un texto necesita (get_minimum_size()) contra lo que tiene (size). Si necesita más, se está recortando. Es el assert que corre en CI.
TouchScreenButton (clase 195).Tween. Ojo: eso ya no es verificable en CI — corre en tiempo real. Es un buen ejemplo de dónde acaba la máquina y empieza tu ojo.Este lab se comprueba en CI de dos formas:
tr() devuelve la clave y el marcador lo canta).Esto es posible porque la UI de Godot se calcula en la CPU: el layout, el foco y lo que ocupa un texto se resuelven sin GPU — solo el dibujado necesita tarjeta gráfica. Un runner sin pantalla puede responder a todo lo de arriba.
Lo que la CI no comprueba, y conviene decirlo: si la UI se ve bien. Que quepa no es que sea bonito. Eso lo juzgas tú.
godot --headless --path labs/ui-accesible/solucion --import # compila el CSV
godot --headless --path labs/ui-accesible/solucion res://escenas/verificar.tscn
# Debe terminar en: == 17 comprobaciones, 0 fallo(s) ==