singe/docs/es/lessons/30-shipping.adoc

500 lines
21 KiB
Text

[#_lesson_30_shipping_it]
== Lección 30: Publicarlo
image::learn/30-shipping.png[La lección terminada, 480]
Tienes una carpeta. Dentro hay un script, unas cuantas imágenes, algunos sonidos
y el `games.dat` que escribiste en la lección trece. Funciona, porque todo lo que
necesita está donde lo dejaste y tú sabes dónde es.
Esta lección convierte esa carpeta en un solo archivo que alguien que no te
conoce de nada puede copiar en su ordenador y jugar. Casi todo son dos líneas de
comandos. El resto es la parte que no enseña nadie: qué va en el archivo, qué
trabajo de otros hay ahí además del tuyo y cómo averiguar si funciona en algún
sitio aparte de aquí.
[#_one_file]
=== Un solo archivo
Un juego de Singe terminado se publica como un único archivo `.game`.
Empaquetar uno es:
----
Singe --pack MyGame MyGame.game
----
El primer nombre es la carpeta de tu juego, el segundo es el archivo que hay que
escribir. Singe mete todos los archivos de la carpeta en una base de datos y
dice lo que ha hecho:
----
>>> Packed 42 files (3819204 bytes) from MyGame into MyGame.game
----
Se ejecuta igual que cualquier otra cosa:
----
Singe MyGame.game
----
Eso ejecuta la primera entrada del `games.dat` que lleva dentro; `--entry=2`
ejecuta la segunda, para un archivo que contiene más de un juego. Para
instalarlo, el jugador copia `MyGame.game` junto al programa Singe. El menú
incluido lo encuentra y lo pone en la lista. No hay nada más: ni instalador, ni
archivo comprimido que descomprimir, ni carpeta que poner en el sitio correcto.
[#_what_packing_does_to_every_file_name]
=== Lo que el empaquetado le hace a cada nombre de archivo
No hay que cambiar nada de tu juego para empaquetarlo, y vale la pena saber por
qué, porque es lo que más probablemente te sorprenda.
Todos los nombres que usa tu juego -- `dofile`, `require`, `io.open`,
`spriteLoad`, `soundLoad`, `fontLoad`, `videoLoad`, un framefile, un
`controls.cfg` -- pasan por una única búsqueda. Para una carpeta suelta, esa
búsqueda es el sistema de archivos normal. Para un juego empaquetado, el nombre
se prueba en tres sitios y gana el primero que lo encuentra:
. una carpeta suelta con el nombre del archivo sin su extensión -- `MyGame/`
junto a `MyGame.game`;
. la carpeta de datos del juego, dentro de `files/`, que es donde ha ido a parar
todo lo que el juego escribe;
. la propia base de datos.
Dentro de la base de datos, los nombres se comparan sin tener en cuenta las
mayúsculas, con cualquiera de los dos tipos de barra y a partir de la raíz del
juego. Un nombre de carpeta inicial igual al del propio juego se ignora, así que
`MyGame/art/spaceship.png`, `art/spaceship.png` y `DIR .. "art/spaceship.png"`
encuentran todos el mismo archivo.
De ahí salen dos reglas, y si has ido siguiendo el libro ya cumples las dos. Usa
`DIR` para todo lo que cargues, para que un nombre nunca dependa de dónde se
arrancó Singe. Y escribe solo dentro de `singeGetDataPath()`, que es donde
`saveSet` ya pone las cosas -- un juego empaquetado se abre de solo lectura y
nunca se escribe en él, así que un juego que guarda junto a sí mismo funciona
suelto y falla empaquetado.
Dos nombres van a otro sitio a propósito. Todo lo que empieza por `Singe/` se
queda en el sistema de archivos de verdad, así que
`dofile("Singe/Framework.singe")` sigue funcionando, y lo mismo pasa con la
carpeta de datos. Y un nombre con `..` que intenta salirse del juego no lleva a
ninguna parte: `io.open` devuelve `nil` y un mensaje que dice que el nombre
apunta fuera del juego, y `spriteLoad` y sus parientes informan de que el
archivo no existe.
[#_the_loose_folder_still_wins]
=== La carpeta suelta sigue ganando
El primer sitio de la lista es el que usarás todos los días. Una carpeta con el
nombre del archivo empaquetado, puesta a su lado, gana a las copias empaquetadas
archivo por archivo.
----
MyGame.game el juego empaquetado
MyGame/ una carpeta con un archivo cambiado dentro
----
Mete ahí un solo `art/spaceship.png` corregido y el juego empaquetado se ejecuta
con tu imagen nueva y todo lo demás sacado de la base de datos. No hay que
volver a empaquetar nada para probar un cambio, y puedes tener ahí toda tu
carpeta de trabajo mientras terminas el juego. También es como un jugador
modifica uno, cosa que puede que te parezca una ventaja o puede que no.
De ahí salen dos cosas. Prueba tu juego una vez con esa carpeta renombrada para
que no estorbe, o publicarás un archivo que solo funciona en el ordenador que
tiene la carpeta. Y que sepas que empaquetar no esconde nada: `--unpack` es una
opción documentada y cualquiera puede usarla. Empaqueta tu juego porque es un
solo archivo, no porque sea un archivo cerrado con llave.
[#_patching_a_released_game]
=== Parchear un juego publicado
Alguien tiene tu juego, hay un fallo en un script y no quieres volver a
mandarle cuarenta megabytes.
----
Singe --patch MyGame.game fixes
----
`fixes` es una carpeta organizada como el juego -- los mismos nombres en los
mismos sitios -- que contiene solo los archivos que han cambiado. Los archivos
que el parche no nombra se dejan como están. Todo el cambio es una sola
transacción, así que un parche interrumpido deja el juego exactamente como
estaba en lugar de a medio sustituir.
Un parche puede viajar como un archivo propio, hecho con el empaquetador:
----
Singe --pack fixes MyGame-1.0.2.patch
Singe --patch MyGame.game MyGame-1.0.2.patch
----
Un `.patch` no necesita `games.dat`, no se puede ejecutar y el menú lo ignora.
También puede llevar una tabla `removed(path)` que nombra archivos que borrar,
para el arte que hayas sustituido por otro con un nombre distinto. En cualquier
caso, Singe comprueba después el juego parcheado igual que comprueba uno nuevo,
así que un parche no puede dejar tu `games.dat` apuntando a un archivo que ya no
está.
Y para volver a sacarlo todo:
----
Singe --unpack MyGame.game MyGameUnpacked
----
[#_what_the_packer_refuses]
=== Lo que el empaquetador rechaza
El empaquetador no construye un juego con cualquier cosa, y cada rechazo tiene
que ver con algo que se rompería más tarde. Rechaza una carpeta sin `games.dat`
en su raíz. Rechaza un `Framework.singe`, un `controls.dat`, cualquier cosa que
termine en `exe`, `sh`, `bat` o `cmd`, y un archivo sin extensión cuyo nombre
empiece por `singe` -- es decir, una copia del motor o de sus archivos de
soporte que se ha colado en la carpeta de tu juego. Rechaza dos archivos cuyos
nombres solo se diferencian en las mayúsculas, porque la búsqueda no puede
distinguirlos. Rechaza una entrada de primer nivel con el mismo nombre que la
propia carpeta. Y comprueba tu `games.dat` antes de confirmar nada: cada entrada
necesita un `SCRIPT`, y cada `SCRIPT`, `VIDEO`, `CABINET`, `MARQUEE` y `ATTRACT`
que nombre tiene que ser un archivo que esté de verdad ahí dentro. Una ruta mala
se informa con el título de la entrada y no se escribe nada.
Cada queja nombra el archivo, y sale la lista entera en lugar de solo la
primera, así que una sola pasada te dice todo lo que hay que arreglar.
[#_what_to_include_and_what_to_leave_out]
=== Qué incluir y qué dejar fuera
Mira en la carpeta de tu juego. Parte de lo que hay es el juego, y parte es cómo
se hizo el juego.
Publica: el script y todo lo que carga, el `games.dat`, el arte que muestra el
menú (`CABINET`, `MARQUEE` y el clip de `ATTRACT`), tus archivos de créditos y
de licencias, y un léame si tienes uno.
Deja fuera: los dibujos de los que exportaste el arte, las tomas de sonido que
no usaste, tus notas, los scripts de prueba, las capturas de pantalla, cualquier
cosa que se llame `old` o `backup`, la carpeta `data` con tus propias partidas
guardadas y tus propios récords, y cualquier cosa que no quieras que lea un
desconocido. Todo eso se empaqueta perfectamente, y ese es el problema: sale con
el juego y está a un `--unpack` de cualquiera.
La forma fiable de saber lo que has publicado de verdad es desempaquetarlo en
una carpeta vacía y mirar. Hazlo una vez con cada versión. Lleva un minuto y es
la única comprobación que ve lo que olvidaste en lugar de lo que recuerdas.
[#_credits_and_licenses]
=== Créditos y licencias
Si algo del arte, el sonido, la música o las fuentes de tu juego lo ha hecho
otra persona, estás publicando su trabajo, y las condiciones con las que lo
cedió son una obligación, no una cortesía. No es una formalidad ni es cosa de
abogados: una persona hizo eso y pidió una cosa concreta a cambio, normalmente
su nombre.
Lleva un registro mientras trabajas, no al final. Un archivo de texto plano en
la carpeta del juego con una línea por cosa: qué archivo es, de dónde salió,
quién lo hizo, qué licencia tiene y qué exige esa licencia. Escribirlo sobre la
marcha lleva segundos. Reconstruirlo la noche antes de publicar, a partir del
historial del navegador, es como se quedan créditos fuera.
Unas cuantas cosas que conviene saber antes de necesitarlas. «Descarga gratuita»
no es una licencia; busca las condiciones de verdad. Creative Commons `BY`
significa que tienes que dar el crédito que se indica, y no tiene ninguna
excepción para juegos. `CC0` y el dominio público no piden nada, y dar el
crédito de todos modos te cuesta una línea. Las fuentes llevan sus propias
licencias, y unas permiten incrustarlas y otras no. La música de un vídeo casi
nunca es tuya para usarla. Cualquier cosa generada por una herramienta solo está
tan limpia como las condiciones de la herramienta.
Luego pon los créditos en algún sitio al que pueda llegar un jugador, dentro del
juego. No solo en un archivo de texto al lado -- un juego empaquetado es un solo
archivo y el archivo de texto está dentro, donde nadie va a mirar. El script del
final de esta lección es esa pantalla.
El propio `Singe/LICENSES` de Singe es el modelo que copiar: una línea por
biblioteca, con su versión, su licencia y de dónde salió. La lista de tu juego
será más corta y el trabajo es el mismo.
[#_a_version_number_that_means_something]
=== Un número de versión que signifique algo
Tu juego tendrá una segunda versión. La primera seguirá por ahí mientras dure
internet, y alguien te mandará un informe de fallo sobre ella sin tener ni idea
de qué versión tiene.
Así que elige un número, ponlo donde un jugador pueda leerlo y no repitas nunca
uno. Tres partes es la forma habitual -- `1.0.2` -- y lo que signifique cada
parte importa menos que el hecho de que solo suban. Sube la última por un
arreglo, la del medio por algo nuevo y la primera cuando hayas hecho un juego
distinto. Elijas lo que elijas, apúntalo en tu léame para que tu yo del futuro
también lo cumpla.
`games.dat` no tiene clave de versión, así que el número lo llevas tú. Ponlo en
el script como constante, muéstralo en la pantalla de créditos y ponlo en el
nombre de lo que mandes (`MyGame-1.0.2.game`, `MyGame-1.0.2.patch`). Así el
informe de fallo dice la versión de la pantalla y sabes de qué archivos se
trata.
[#_testing_on_a_machine_that_is_not_yours]
=== Probar en un ordenador que no es el tuyo
Tu ordenador es el peor sitio posible para probar una versión. Tiene las fuentes
que instalaste, la carpeta que estabas editando, el archivo de partida guardada
con tus ajustes dentro y el motor en el estado en que lo haya dejado un mes de
trabajo.
La prueba mínima, en orden:
. Desempaqueta la versión en una carpeta vacía y lee la lista de archivos.
. Copia *solo* el archivo `.game` en otra carpeta, con una copia recién sacada
de Singe y nada más, y ejecútalo desde el menú como lo hará un jugador.
. Borra la carpeta `data` de esa carpeta y vuelve a ejecutarlo. Eso es una
primera ejecución en un ordenador nuevo: sin ajustes guardados, sin récords,
sin una cuenta con la sesión iniciada. Un juego que solo funciona la segunda
vez que se ejecuta es un fallo habitual, y así es como lo encuentras.
. Dáselo a una persona que no lo haya visto nunca y mira sin ayudar.
Luego prueba el ordenador además del juego. Tendrá una pantalla de otra forma,
así que mira tu juego en ventana y a pantalla completa. Puede que no tenga
ningún dispositivo 3D, que es por lo que el menú tiene un segundo renderizador.
La página *System Information* de las herramientas de servicio muestra en una
sola pantalla la versión del motor, el sistema operativo, el procesador, el
renderizador, los decodificadores y la carpeta de datos; una foto de esa página
responde a casi todo un informe de fallo, y decírselo a tus jugadores en tu
léame te ahorrará una semana de adivinanzas.
[#_the_script_2]
=== El script
Una pantalla de créditos y versión. Es poca cosa de escribir y es la parte de
publicar que la mayoría de los juegos hace mal.
[source,lua]
----
dofile("Singe/Framework.singe")
local GAME_NAME = "Rock Dodger"
local GAME_VERSION = "1.0.2"
local CREDITS = {
"Written and drawn by <your name here>",
"",
"Music \"Night Drive\" by A. Composer",
" Creative Commons BY 4.0",
" example.org/night-drive",
"",
"Explosion freesound.org user \"thud\"",
" Creative Commons CC0",
"",
"Font FreeSansBold",
" GPL with the font exception",
"",
"Engine Singe " .. SINGE_VERSION_STRING,
" GPL version 3",
" See Singe/LICENSES for the",
" libraries Singe is built on.",
"",
"Thank you for playing.",
}
local FIRST_ROW = 4
local ROWS = 10
local scroll = 0
local function scrollBy(lines)
local most = #CREDITS - ROWS
if most < 0 then
most = 0
end
scroll = scroll + lines
if scroll < 0 then
scroll = 0
end
if scroll > most then
scroll = most
end
end
function onInputPressed(what)
if what == SWITCH_UP then
scrollBy(-1)
elseif what == SWITCH_DOWN then
scrollBy(1)
elseif what == SWITCH_BUTTON1 then
scroll = 0
end
end
function onOverlayUpdate()
local line = nil
overlayClear()
overlayPrint(2, 0, GAME_NAME .. " version " .. GAME_VERSION)
overlayPrint(2, 1, "Singe " .. SINGE_VERSION_STRING .. " id " .. (singeGetGameId() or "none"))
for row = 0, ROWS - 1 do
line = CREDITS[scroll + row + 1]
if line then
overlayPrint(2, FIRST_ROW + row, line)
end
end
overlayPrint(2, FIRST_ROW + ROWS + 1, "Saves go to:")
overlayPrint(2, FIRST_ROW + ROWS + 2, singeGetDataPath())
overlayPrint(2, 17, "Up and down scroll. Button 1 goes back to the top.")
return OVERLAY_UPDATED
end
----
Sustituye cada línea de `CREDITS` por la verdad sobre tu propio juego. Los
textos de relleno están ahí para mostrar la forma: qué es, quién lo hizo, qué
licencia tiene y de dónde salió.
[#_what_just_happened_25]
=== Qué acaba de pasar
[source,lua]
----
local GAME_VERSION = "1.0.2"
----
Un solo sitio, arriba, en mayúsculas porque nunca cambia mientras el juego se
ejecuta. Todo lo que muestra la versión lee esto, así que subirla es un cambio y
no una búsqueda.
[source,lua]
----
"Music \"Night Drive\" by A. Composer",
----
Esas barras invertidas son la secuencia de escape que conociste en la lección
uno: unas comillas dobles que forman parte del texto en lugar de terminar la
cadena. Los títulos llevan comillas, y por eso una lista de créditos se topa con
esto en su primera línea.
[source,lua]
----
for row = 0, ROWS - 1 do
line = CREDITS[scroll + row + 1]
if line then
----
Diez filas de pantalla que muestran diez líneas de una lista de más de diez.
`scroll` es cuánto ha bajado la ventana por la lista. El `+ 1` está porque Lua
cuenta una lista desde uno y las filas de la pantalla cuentan desde cero, y el
`if line then` está porque el final de la lista es `nil` -- leer más allá del
final de una tabla no es un error en Lua, simplemente no te da nada, e imprimir
nada sí lo sería.
[source,lua]
----
overlayPrint(2, 1, "Singe " .. SINGE_VERSION_STRING .. " id " .. (singeGetGameId() or "none"))
----
Tres datos que necesita un informe de fallo y que un jugador no puede encontrar
de otra manera: tu versión, en la línea de arriba, la versión del motor y el id
del juego. `SINGE_VERSION_STRING` es una variable global que el motor fija antes
de que se ejecute tu script. `or "none"` cubre el caso en que el juego no tiene
entrada en `games.dat`, para que la línea se imprima en lugar de fallar con un
`nil`.
[source,lua]
----
overlayPrint(2, FIRST_ROW + ROWS + 2, singeGetDataPath())
----
Dónde escribe este juego. En un juego empaquetado es el único sitio donde se
escribe algo, y es la carpeta que borra un jugador para empezar de cero.
Mostrarla convierte «¿dónde están mis partidas guardadas?» en una pregunta que
nunca tendrás que responder.
Si esa ruta se sale por la derecha de la pantalla, no es que esté rota:
`overlayPrint` descarta los caracteres que pasarían del borde. El overlay por
defecto tiene sesenta celdas de carácter de ancho.
[#_try_it_30]
=== Pruébalo
. *Empaquétalo.* Pon este script en la carpeta de tu juego, ejecuta `--pack` y
ejecuta el archivo `.game`. Luego desempaquétalo en una carpeta vacía y lee la
lista de lo que ha salido.
. *Sustituye un archivo.* Junto al archivo empaquetado, crea una carpeta con su
nombre sin la extensión y pon dentro un archivo cambiado. Ejecuta el juego
empaquetado y mira cómo gana tu archivo suelto.
. *Haz un parche.* Cambia la versión a `1.0.3`, pon solo el script en una
carpeta `fixes`, empaqueta eso como un `.patch` y aplícalo al juego que ya
hiciste. Comprueba la versión en pantalla.
. *Rompe el empaquetador.* Cambia el nombre de tu `games.dat` un momento e
intenta empaquetar. Luego déjalo como estaba, haz que uno de sus `SCRIPT`
apunte a un archivo que no existe y vuelve a intentarlo. Dos rechazos
distintos, los dos antes de que se escriba nada.
. *Escribe los créditos de verdad.* Repasa la carpeta de tu juego archivo por
archivo y escribe una línea por cada uno que no hayas hecho tú. Si no
recuerdas de dónde salió algo, esa es la respuesta a si deberías publicarlo.
[#_break_it_on_purpose_29]
=== Rómpelo a propósito
Ejecuta Singe una vez desde *dentro* de la carpeta de tu juego, para que el
motor desempaquete ahí sus archivos de soporte, y luego empaquétala:
----
Singe --pack MyGame MyGame.game
----
----
!!! MyGame has Framework.singe: Singe/Framework.singe
!!! MyGame has sh file: Menu.sh
----
No se escribe nada. Cada línea es un archivo que no acepta y el porqué, y puede
que te salgan en otro orden o que salgan otras además. Hay una copia de la
propia carpeta de soporte del motor dentro de tu juego, y el script lanzador que
el motor escribió a su lado, porque en algún momento ejecutaste Singe desde ahí
dentro. Un juego no debe llevar nunca ninguno de los dos: los archivos de
soporte pertenecen al Singe que esté usando el jugador, no a tu juego, y un
`Framework.singe` empaquetado se cargaría en lugar del suyo. En Windows, la
segunda línea nombra `Menu.bat` en su lugar.
Borra la carpeta `Singe` y `Menu.sh` (o `Menu.bat`) de dentro de tu juego, deja
la carpeta de tu juego como una carpeta junto al programa Singe y no alrededor
de él, y vuelve a empaquetar.
Es la misma organización que describía la lección trece, y este error es lo que
pasa cuando se descuida.
[#_what_you_learned_30]
=== Lo que has aprendido
* `Singe --pack folder name.game` convierte la carpeta de un juego en un solo
archivo, y copiar ese archivo junto al programa Singe lo instala.
* Todo nombre de archivo dentro de un juego empaquetado se busca primero en una
carpeta suelta, luego en la carpeta de datos y luego en la base de datos.
* Una carpeta suelta con el nombre del archivo empaquetado lo sustituye archivo
por archivo, que es como sigues trabajando -- y como publicas una versión rota
si te olvidas de que está ahí.
* Carga con `DIR` y escribe solo dentro de `singeGetDataPath()`, o tu juego
funcionará suelto y fallará empaquetado.
* `--patch` sustituye archivos de un juego publicado en una sola transacción;
`--unpack` vuelve a sacarlo todo.
* El empaquetador rechaza los archivos del motor, los nombres que chocan y un
`games.dat` que apunta a archivos que no tiene.
* Desempaqueta tu propia versión y lee lo que lleva antes de mandarla a ningún
sitio.
* El arte y el sonido de otros vienen con condiciones, y cumplirlas significa
una pantalla de créditos dentro del juego.
* Un número de versión solo sube, y su sitio es la pantalla.
* Prueba en un ordenador que no sea el tuyo, empezando sin ninguna carpeta
`data`.
[#_next_time_30]
=== La próxima vez
Un solo archivo, instalado, con sus créditos y su número. La última lección lo
mete en una caja de madera con una ranura para monedas y sin teclado, lo que
cambia más cosas de tu juego de lo que crees.