[#_lesson_13_a_game_folder] == Lección 13: Una carpeta de juego image::learn/13-game-folder.png[La lección terminada, 480] Tu juego es un script en una carpeta cuyo nombre recuerdas, que se arranca escribiendo su nombre en una terminal. Eso te vale a ti y no le sirve a nadie más. Un jugador debería poder copiar una carpeta junto a Singe, arrancar el menú, ver tu juego en la lista con su título y su arte, y pulsar un botón. La imagen de arriba es ese menú, con tu juego dentro. Para eso hace falta un archivo nuevo, de unas seis líneas, y dos imágenes que puedes sacar con una tecla. Tu script no cambia en absoluto. [#_give_the_game_a_name] === Ponle nombre al juego Un juego tiene dos nombres, uno para el ordenador y otro para las personas. El del ordenador es la carpeta, y lo elegiste en la lección nueve: `rocks`, con `rocks.singe` dentro. La carpeta y el script comparten nombre, como en todas las lecciones desde la lección uno, y por eso `Singe rocks` encuentra el script solo con la carpeta. Mantén ese nombre sencillo. Sin espacios, que una terminal lee como el hueco entre dos nombres. Y con las mismas mayúsculas en todos los sitios donde lo escribas: en la mayoría de los ordenadores que no son Windows, `rocks` y `Rocks` son dos carpetas distintas, y un juego que solo funciona en tu ordenador no es un juego que pueda tener nadie más. El nombre para las personas puede ser el que quieras, con mayúsculas, espacios y todo, y no vive en el nombre de la carpeta. Vive en el archivo del que trata esta lección, para que cambiarle el nombre al juego en el menú nunca signifique cambiarle el nombre a una carpeta. [#_what_goes_in_the_folder] === Qué va en la carpeta Todo lo que necesita tu juego, y nada más. Al final de esta lección tiene este aspecto: ---- rocks/ games.dat Lo que lee el menú rocks.singe El juego art/ El kit de arte de la lección nueve spaceship.png asteroid.png bullet.png star.png shoot.wav boom.wav ... cabinet.png Una imagen que el menú muestra junto a la lista marquee.png Una imagen que el menú muestra encima de ella ---- Cualquier otra cosa que haya acabado ahí mientras aprendías -- `walk.singe` de la lección nueve, la pequeña prueba de sonido de la lección diez -- no es parte del juego. Muévelas a una carpeta propia junto a `rocks`. El kit de arte también tiene unos cuantos archivos que este juego nunca carga, y de momento no hacen ningún daño. La lección treinta es donde repasas una carpeta de juego y quitas todo lo que no es el juego. La regla que hay detrás de esa organización es que un juego nunca sale de su propia carpeta. Ni para el arte, ni para un sonido, ni para una biblioteca que también usas en otro juego -- si compartes código entre dos juegos, cada uno guarda su propia copia. Hay exactamente un sitio fuera de la carpeta desde el que un juego puede cargar, y ya estás cargando desde él: la propia carpeta `Singe` del motor, donde vive `Singe/Framework.singe`, y desde la lección diez también el clic y la música. La carpeta del juego está en tu carpeta de trabajo, junto al motor: ---- singe/ Tu carpeta de trabajo Singe-v3.00-... El propio motor, se llame como se llame el tuyo Menu.sh Arranca el menú. Menu.bat en Windows Singe/ Archivos de soporte. No son tuyos; déjalos en paz data/ Todo lo que escribe cualquier cosa, tu partida guardada incluida rocks/ Tu juego ---- El ordenador de un jugador tiene esa misma forma. El motor escribió `Singe`, `data` y `Menu.sh` (`Menu.bat` en Windows) la primera vez que ejecutaste algo, allá en la lección uno. La lección uno mencionó los dos primeros. El tercero es nuevo para ti, y esta lección lo pone a trabajar. [#_paths_count_from_where_singe_started] === Las rutas cuentan desde donde arrancó Singe Llevas haciendo esto desde la lección nueve, así que esto es un recordatorio, no un cambio. Cada nombre que carga tu juego -- `art/spaceship.png`, `art/boom.wav` -- se busca empezando por la carpeta en la que *se arrancó* Singe, no por la carpeta en la que está tu script. Arrancas Singe en tu carpeta de trabajo, así que un `art/spaceship.png` a secas se buscaría en `singe/art`, que no existe. `DIR` es la propia carpeta de tu script con la barra ya puesta al final, así que un nombre pegado a ella apunta a tu archivo desde dondequiera que se haya arrancado Singe: [source,lua] ---- local shipSprite = spriteLoad(DIR .. "art/spaceship.png") ---- El menú arranca Singe en la carpeta en la que está `Menu.sh`, que vuelve a ser tu carpeta de trabajo, así que `DIR` hace ahí exactamente lo mismo que hace para ti. Lo sigue haciendo cuando el juego se empaqueta en un solo archivo en la lección treinta, cuando la carpeta de trabajo de otra persona se llama de una forma completamente distinta, y en un ordenador que no verás nunca. Un nombre que empieza por `Singe/` es la carpeta del motor y nunca necesita `DIR`; todos los nombres que son tuyos lo necesitan. [#_games_dat] === games.dat `games.dat` es como se presenta un juego. El menú lee el `games.dat` de cada carpeta de juego que tiene al lado y enumera lo que encuentra. Sin `games.dat` no hay entrada en la lista: tu carpeta es invisible, por bueno que sea el juego que hay dentro. Crea un archivo nuevo llamado `games.dat` en tu carpeta `rocks`. Este es el más pequeño que funciona: [source,lua] ---- GAMES = { { TITLE = "Rocks", SCRIPT = "rocks/rocks.singe" } } ---- Eso es Lua, y es una tabla del tipo que conociste en la lección seis: `GAMES` es una lista, y cada cosa de la lista es un registro que describe un juego. Una carpeta puede contener varios juegos -- por eso es una lista -- pero la tuya contiene uno. `SCRIPT` es el campo que el motor necesita para ejecutar el juego, y `TITLE` el que necesita el menú para mostrarlo en la lista. La ruta del script se cuenta desde la carpeta *de encima* del `games.dat`, no desde la carpeta en la que está el `games.dat`. Así que un `games.dat` dentro de `rocks` nombra su propio script como `rocks/rocks.singe`. Parece que dice `rocks` dos veces, y está bien. Todas las demás rutas del archivo funcionan igual. Si te equivocas, nada se queja cuando escribes el nombre: la entrada simplemente nunca coincide con el script que has ejecutado. `TITLE` es el nombre para las personas del principio de esta lección. Es lo que muestra el menú, y los juegos se listan por orden de título, así que el título también decide en qué lugar de la lista queda tu juego. [#_telling_the_menu_more] === Contarle más al menú El menú tiene todo un panel que llenar junto a la lista, y lo llena con el resto de la entrada: [source,lua] ---- GAMES = { { TITLE = "Rocks", SCRIPT = "rocks/rocks.singe", CANVAS_X = 720, CANVAS_Y = 480, YEAR = 2026, GENRE = "Shooter", DEVELOPER = "Your Name Here", PUBLISHER = "Your Name Here", DESCRIPTION = "Shoot the rocks before they reach your ship.", CABINET = "rocks/cabinet.png", MARQUEE = "rocks/marquee.png" } } ---- `CABINET` y `MARQUEE` son archivos de imagen normales en la carpeta de tu juego, en cualquier formato que Singe sepa leer. El arte de la recreativa es la imagen grande junto a la lista y la marquesina es la tira de encima. Hazlas del tamaño que quieras; el menú las encaja en sus paneles. No necesitas un programa de dibujo para hacerlas, porque tu juego puede sacarse su propia foto. Ejecútalo y, cuando la pantalla tenga el aspecto con el que quieres que la vea el mundo, pulsa F12 (o F11, porque algunos escritorios se quedan F12 para ellos). Singe guarda una imagen de la ventana en la carpeta de datos de tu juego, `data/rocks`, con el nombre `singe000.png`, luego `singe001.png`, y así sucesivamente. Saca una de la pantalla de título y otra en mitad de una pelea. Cópialas a tu carpeta `rocks` y cámbiales el nombre: la de la pelea es `cabinet.png` y la del título es `marquee.png`. Una marquesina de verdad es una tira ancha con el nombre del juego en letras grandes, y una pantalla entera apretujada ahí dentro parece un sello de correos. Servirá hasta que tengas algo mejor. Si dejas fuera `MARQUEE`, el menú pone ahí la imagen que usa el propio motor cuando falta una, un tablero de ajedrez rosa que dice MISSING, y eso es peor. `YEAR`, `GENRE`, `DEVELOPER`, `PUBLISHER` y `DESCRIPTION` son el texto de debajo, y el motor nunca los mira. Son para la persona que lee el menú, que es de lo que se trata. `CANVAS_X` y `CANVAS_Y` son el tamaño del mundo de tu juego, y 720 por 480 es el valor por defecto -- el mismo mundo en el que llevas dibujando desde la lección uno. Ponlos solo si tu juego quiere otra forma. Un juego sin campo `VIDEO` es un juego sin vídeo, que es lo que es el tuyo; la lección catorce es donde ese campo empieza a importar. La sección `games.dat` de la referencia enumera todos los campos que existen, incluidos varios para hardware que todavía no tienes. Vale la pena conocer ya el nombre de unos cuantos más. `REQUIRES` dice lo que un juego necesita de la máquina más allá del 2D sencillo: `"GUI"` para los menús y páginas de la lección veinte, `"SCENE"` para el 3D que empieza en la lección veintidós, y unos cuantos más. El menú lo lee para poder decirle a un jugador «esta máquina no puede ejecutar eso» en lugar de dejar que el juego falle a medias. Rocks es 2D sencillo, y dejar fuera `REQUIRES` significa exactamente eso, así que déjalo fuera. Rara vez tendrás que averiguar la lista tú mismo, porque Singe se da cuenta de lo que usa un juego mientras se ejecuta y lo dice cuando la entrada no lo incluye. A partir de la lección veinte verás líneas como esta, tenga o no tenga el juego un `games.dat`: ---- Warning: this game uses GUI, which its games.dat does not list in REQUIRES. ---- Es un consejo, no un error; el juego funciona de las dos maneras. El arreglo es una línea en la entrada, `REQUIRES = { "GUI" },`, y al empaquetar un juego en la lección treinta se escribe la lista por ti si la entrada no tiene ninguna. Su compañero `TIER`, un número del 0 al 3, dice cuánta máquina quiere un juego, y puede esperar hasta que escribas un juego lo bastante pesado como para que importe. `GAME_ID` es una identidad única para tu juego, que necesitan las tablas de récords en línea de la lección veintiocho y nada más. Y `RESOLUTION_X` y `RESOLUTION_Y` piden un tamaño de ventana concreto. Olvídate del resto hasta que algo te mande a buscarlo. [#_running_it_from_the_menu] === Ejecutarlo desde el menú Ejecuta `Menu.sh` -- `Menu.bat` en Windows -- desde tu carpeta de trabajo. Encuentra el Singe más nuevo que tiene al lado y arranca el menú. Tu juego está en la lista, con su arte al lado y su descripción debajo. Es lo único que hay en la lista, porque `rocks` es la única carpeta a la que le has dado un `games.dat`; `hello`, `dodge` y las demás no son juegos, por lo que el menú puede saber. Arriba y abajo mueven un juego, izquierda y derecha mueven una página, y start o un botón de disparo juega el que esté seleccionado. Sal del juego con Esc y vuelves al menú, en el mismo juego, listo para otra vez. El menú también recuerda en qué juego estabas de una ejecución a otra. Eso es todo lo que hace falta para publicar un juego, en lo que al motor respecta. Un jugador copia tu carpeta junto a Singe y aparece. Escribir el nombre sigue funcionando, y funciona igual: ---- Singe rocks ---- Cuando lanzas un script a mano así, Singe busca un `games.dat` a su lado -- y hasta en tres carpetas por encima -- y si una entrada nombra el script que has lanzado, los ajustes de esa entrada se le aplican. Así que un juego funciona igual desde una terminal que desde el menú, lo que significa que puedes seguir trabajando como hasta ahora, con `-R` y un editor de texto, y aun así fiarte de lo que le llegará a un jugador. También escribe en el mismo sitio: lo arranques como lo arranques, la partida guardada de tu juego es la de `data/rocks`. [#_later_one_file] === Más adelante: un solo archivo Una carpeta es cómoda para trabajar y algo incómoda para regalar: una docena de archivos, una carpeta dentro de otra y un jugador que la descomprime en el sitio equivocado. Un juego de Singe terminado se puede empaquetar en un único archivo `.game` que el motor ejecuta directamente, con arte, sonidos y todo, y que un jugador copia junto a Singe y listo. Para eso no tiene que cambiar nada de tu juego. Es la lección treinta, y vale la pena esperar: empaqueta un juego que no está terminado y mañana lo volverás a empaquetar. [#_what_just_happened_9] === Qué acaba de pasar [source,lua] ---- GAMES = { { ---- Dos llaves, porque es una lista de registros. La de fuera es la lista. La de dentro es lo primero de la lista, que es el registro de un juego. Equivocarse con una de ellas es el error habitual, y Lua te dirá la línea. [source,lua] ---- SCRIPT = "rocks/rocks.singe", ---- Se cuenta desde la carpeta de encima del `games.dat`, que es tu carpeta de trabajo. Es el mismo nombre que escribirías después de `Singe` para ejecutar el script a mano, y esa es la forma más fácil de recordarlo. La coma al final de la línea separa un campo del siguiente, exactamente como en la lección seis. El último campo de un registro no lleva coma detrás -- a Lua no le importa si la lleva, pero si no la pones tu archivo se parecerá al de todos los demás. Una coma que falta en medio es otra cosa, y es el error de esta lección. [source,lua] ---- CABINET = "rocks/cabinet.png", ---- Es una imagen que carga el menú, no tu juego, así que se nombra como `SCRIPT`, desde la carpeta de encima, y no con `DIR`. `DIR` es de tu script mientras se ejecuta, y el menú y el motor leen `games.dat` antes de que tu script haya empezado. [#_try_it_13] === Pruébalo . *Haz un arte mejor.* Si tienes un programa de dibujo, haz un `marquee.png` como es debido: una tira ancha con ROCKS en letras grandes. Ponlo en lugar de la captura de pantalla y vuelve a mirar el menú. . *Dale un games.dat a Dodge.* Escribe un `games.dat` en tu carpeta `dodge` de la lección siete y arranca el menú. Ahora hay dos juegos, por orden de título. Luego cambia el `TITLE` de Rocks a `Asteroid Field` y mira cómo sube por encima de Dodge: la lista se ordena por título, no por carpeta. . *Rompe games.dat para el menú.* Borra una de las llaves de cierre del `games.dat` de `rocks` y arranca el menú. Tu juego ha desaparecido de la lista, y la terminal dice por qué. Vuelve a ponerla. . *Equivócate con SCRIPT.* Pon `CANVAS_X` a `1280` y `CANVAS_Y` a `720`, y ejecuta `Singe rocks`. La zona de juego es más ancha. Ahora cambia `SCRIPT` a `rocks.singe` y vuelve a ejecutarlo. El juego ha vuelto a su tamaño de antes, y nada te lo ha dicho: la entrada ya no nombra el script que has ejecutado, así que no se aplica. Deja los dos como estaban. . *Encuentra tu partida guardada.* Arranca el juego desde el menú, marca un récord, sal al menú y sal del menú. Ejecuta `Singe rocks` y mira el título. Luego busca en `data/rocks` el archivo que lo ha recordado. [#_break_it_on_purpose_12] === Rómpelo a propósito Quita la coma del final de la línea de `TITLE`, para que el principio del archivo quede así: [source,lua] ---- GAMES = { { TITLE = "Rocks" SCRIPT = "rocks/rocks.singe", ---- Ejecuta `Singe rocks`. El juego arranca, y la terminal dice esto: ---- Note: rocks/games.dat ignored: [string "rocks/games.dat"]:4: '}' expected (to close '{' at line 2) near 'SCRIPT' ---- Tu juego sigue funcionando, porque un juego arrancado por su nombre no necesita su entrada. Simplemente funciona sin ella. Pero arranca el menú y tu juego ha desaparecido. Si era el único, la lista dice `No games yet`, y la terminal dice lo mismo de otra forma: ---- rocks/games.dat: rocks/games.dat:4: '}' expected (to close '{' at line 2) near 'SCRIPT' Skipped. ---- Léelo como cualquier otro error de Lua. El archivo es `rocks/games.dat` y la línea es la 4. Pero la línea 4 es la de `SCRIPT`, y no tiene nada de malo. El error está al final de la línea 3. Después de `TITLE = "Rocks"`, Lua esperaba o una coma y otro campo, o la `}` que cierra el registro que empezó en la línea 2. En su lugar encontró `SCRIPT`, así que la línea 4 es donde se dio cuenta. Lo que propone para arreglarlo -- una `}` -- está mal; la línea que da va una por detrás. Cuando un error señala el principio de una línea, mira el final de la línea anterior. Vuelve a poner la coma y arranca otra vez el menú. [#_what_you_learned_13] === Lo que has aprendido * Un juego es una carpeta: el script, su arte y un `games.dat`, y nada de fuera de ella salvo la propia carpeta `Singe` del motor. * El nombre de la carpeta es para el ordenador, así que mantenlo sencillo. El nombre para las personas es `TITLE`. * Los nombres que carga tu script se cuentan desde donde se arrancó Singe, y por eso cada uno de ellos se construye a partir de `DIR`. * `games.dat` es un archivo de Lua que contiene una lista `GAMES`, y cada entrada es un juego. * Una entrada necesita `TITLE` y `SCRIPT` y nada más; sus rutas cuentan desde la carpeta de encima del `games.dat`. * `CABINET`, `MARQUEE`, `YEAR`, `GENRE`, `DEVELOPER`, `PUBLISHER` y `DESCRIPTION` son lo que el menú le muestra al jugador, y F12 saca una imagen que puedes usar para las dos primeras. * `REQUIRES` le dice al menú lo que un juego necesita más allá del 2D, y Singe te avisa cuando un juego usa algo que su entrada no incluye. * `Menu.sh` o `Menu.bat` arranca el menú incluido, que enumera cada carpeta de juego que tiene al lado, ordenadas por título. * Un juego lanzado por su nombre también recibe su entrada de `games.dat`, así que se comporta igual de las dos formas, y un `games.dat` que no se puede cargar se avisa y se ignora. * Un juego terminado se puede empaquetar más adelante en un solo archivo, en la lección treinta. [#_next_time_13] === La próxima vez Con esto termina la segunda parte: tienes un juego, con arte, sonido, colisiones, puntuación y una forma de que otra persona lo ejecute. La tercera parte trata de lo que casi ningún otro motor hace -- reproducir imágenes filmadas como el mundo en el que ocurre el juego -- y la lección catorce empieza con el disco, el fotograma y la espera.