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

509 lines
22 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[#_lesson_30_shipping_it]
== Leçon 30 : Le publier
image::learn/30-shipping.png[La leçon terminée, 480]
Vous avez un dossier. Il contient un script, quelques images, quelques sons, et
le `games.dat` que vous avez écrit à la leçon treize. Il tourne, parce que tout
ce dont il a besoin est là où vous l'avez laissé et que vous savez où c'est.
Cette leçon transforme ce dossier en un seul fichier que quelqu'un qui ne vous a
jamais rencontré peut copier sur sa machine et jouer. L'essentiel tient en deux
lignes de commande. Le reste, c'est la partie que personne n'enseigne : ce qui a
sa place dans le fichier, le travail de qui s'y trouve à part le vôtre, et
comment savoir s'il marche ailleurs qu'ici.
[#_one_file]
=== Un seul fichier
Un jeu Singe fini se publie sous la forme d'un seul fichier `.game`. En
empaqueter un, c'est :
----
Singe --pack MyGame MyGame.game
----
Le premier nom est le dossier de votre jeu, le second est le fichier à écrire.
Singe lit chaque fichier du dossier dans une base de données et dit ce qu'il a
fait :
----
>>> Packed 42 files (3819204 bytes) from MyGame into MyGame.game
----
Lancez-le de la même façon que vous lancez n'importe quoi :
----
Singe MyGame.game
----
Ça lance la première entrée du `games.dat` qu'il contient ; `--entry=2` lance
la deuxième, pour un fichier qui contient plus d'un jeu. Pour l'installer, le
joueur copie `MyGame.game` à côté du programme Singe. Le menu fourni le trouve
et l'affiche dans sa liste. Il n'y a rien d'autre : pas d'installateur, pas
d'archive à décompresser, pas de dossier à mettre au bon endroit.
[#_what_packing_does_to_every_file_name]
=== Ce que l'empaquetage fait à chaque nom de fichier
Rien dans votre jeu n'a besoin de changer pour être empaqueté, et ça vaut la
peine de savoir pourquoi, parce que c'est ce qui risque le plus de vous
surprendre.
Chaque nom qu'utilise votre jeu -- `dofile`, `require`, `io.open`,
`spriteLoad`, `soundLoad`, `fontLoad`, `videoLoad`, un framefile, un
`controls.cfg` -- passe par une seule et même recherche. Pour un dossier non
empaqueté, cette recherche est le simple système de fichiers. Pour un jeu
empaqueté, le nom est essayé à trois endroits et le premier trouvé l'emporte :
. un dossier non empaqueté qui porte le nom du fichier sans son
extension -- `MyGame/` à côté de `MyGame.game` ;
. le dossier de données du jeu, sous `files/`, qui est l'endroit où atterrit
tout ce que le jeu écrit ;
. la base de données elle-même.
Dans la base de données, les noms sont comparés sans tenir compte des
majuscules, avec l'une ou l'autre sorte de barre oblique, et relativement à la
racine du jeu. Un nom de dossier en tête égal à celui du jeu lui-même est
ignoré, si bien que `MyGame/art/spaceship.png`, `art/spaceship.png` et
`DIR .. "art/spaceship.png"` trouvent tous le même fichier.
Deux règles en découlent, et si vous avez suivi le livre, vous obéissez déjà aux
deux. Utilisez `DIR` pour tout ce que vous chargez, pour qu'un nom ne dépende
jamais de l'endroit d'où Singe a été lancé. Et n'écrivez que sous
`singeGetDataPath()`, là où `saveSet` range déjà les choses -- un jeu empaqueté
est ouvert en lecture seule et n'est jamais écrit, si bien qu'un jeu qui
sauvegarde à côté de lui-même marche non empaqueté et échoue empaqueté.
Deux sortes de noms vont ailleurs exprès. Tout ce qui commence par `Singe/`
reste sur le vrai système de fichiers, si bien que
`dofile("Singe/Framework.singe")` continue de marcher, et le dossier de données
aussi. Et un nom contenant `..` qui essaie de sortir du jeu ne mène nulle part :
`io.open` renvoie `nil` et un message disant que le nom sort du jeu, et
`spriteLoad` et ses semblables signalent le fichier comme manquant.
[#_the_loose_folder_still_wins]
=== Le dossier non empaqueté l'emporte toujours
Cette première place dans la liste est celle dont vous vous servirez tous les
jours. Un dossier qui porte le nom du fichier empaqueté, posé à côté de lui,
l'emporte sur les copies empaquetées, fichier par fichier.
----
MyGame.game le jeu empaqueté
MyGame/ un dossier avec un seul fichier modifié dedans
----
Déposez-y un seul `art/spaceship.png` corrigé et le jeu empaqueté tourne avec
votre nouvelle image et tout le reste tiré de la base de données. Rien n'a
besoin d'être réempaqueté pour essayer une modification, et vous pouvez garder
là tout votre dossier de travail pendant que vous finissez le jeu. C'est aussi
comme ça qu'un joueur en fait un mod, ce que vous considérerez ou non comme une
fonctionnalité.
Deux choses en découlent. Vérifiez votre jeu une fois avec ce dossier renommé,
sinon vous publierez un fichier qui ne marche que sur la machine qui a le
dossier. Et sachez que l'empaquetage ne cache rien : `--unpack` est une option
documentée et n'importe qui peut s'en servir. Empaquetez votre jeu parce qu'il
tient en un seul fichier, pas parce qu'il serait verrouillé.
[#_patching_a_released_game]
=== Corriger un jeu publié
Quelqu'un a votre jeu, il y a un bogue dans un script, et vous n'avez pas envie
de lui renvoyer quarante mégaoctets.
----
Singe --patch MyGame.game fixes
----
`fixes` est un dossier organisé comme le jeu -- les mêmes noms aux mêmes
endroits -- qui ne contient que les fichiers qui ont changé. Les fichiers que le
correctif ne nomme pas sont laissés tels quels. Tout le changement est une seule
transaction, si bien qu'un correctif interrompu laisse le jeu exactement comme
il était, plutôt qu'à moitié remplacé.
Un correctif peut voyager dans un fichier à lui, créé avec l'empaqueteur :
----
Singe --pack fixes MyGame-1.0.2.patch
Singe --patch MyGame.game MyGame-1.0.2.patch
----
Un `.patch` n'a pas besoin de `games.dat`, ne peut pas être lancé, et le menu
l'ignore. Il peut aussi porter une table `removed(path)` qui nomme des fichiers
à supprimer, pour les images que vous avez remplacées par quelque chose qui
porte un autre nom. Dans tous les cas, Singe vérifie ensuite le jeu corrigé de
la même manière qu'il vérifie un nouveau jeu, si bien qu'un correctif ne peut
pas laisser votre `games.dat` pointer vers un fichier qui n'est plus là.
Et pour tout ressortir :
----
Singe --unpack MyGame.game MyGameUnpacked
----
[#_what_the_packer_refuses]
=== Ce que l'empaqueteur refuse
L'empaqueteur ne construit pas un jeu à partir de n'importe quoi, et chaque
refus concerne quelque chose qui casserait plus tard. Il refuse un dossier sans
`games.dat` à sa racine. Il refuse un `Framework.singe`, un `controls.dat`, tout
ce qui se termine par `exe`, `sh`, `bat` ou `cmd`, et un fichier sans extension
dont le nom commence par `singe` -- autrement dit, une copie du moteur ou de ses
fichiers de support qui s'est égarée dans le dossier de votre jeu. Il refuse
deux fichiers dont les noms ne diffèrent que par les majuscules, puisque la
recherche ne peut pas les distinguer. Il refuse une entrée de premier niveau qui
porte le nom du dossier lui-même. Et il vérifie votre `games.dat` avant de
valider : chaque entrée a besoin d'un `SCRIPT`, et chaque `SCRIPT`, `VIDEO`,
`CABINET`, `MARQUEE` et `ATTRACT` qu'elle nomme doit être un fichier qui s'y
trouve vraiment. Un mauvais chemin est signalé avec le titre de l'entrée et rien
n'est écrit.
Chaque plainte nomme le fichier, et vous en avez la liste complète plutôt que la
première seulement, si bien qu'un seul passage vous dit tout ce qu'il faut
corriger.
[#_what_to_include_and_what_to_leave_out]
=== Ce qu'il faut inclure et ce qu'il faut laisser de côté
Regardez dans le dossier de votre jeu. Une partie de ce qu'il contient est le
jeu, et une autre partie, c'est la façon dont le jeu a été fait.
À publier : le script et tout ce qu'il charge, le `games.dat`, les images que
montre le menu (`CABINET`, `MARQUEE` et le clip `ATTRACT`), vos fichiers de
crédits et de licence, et un lisez-moi si vous en avez un.
À laisser de côté : les dessins dont vous avez exporté les images, les prises de
son que vous n'avez pas utilisées, vos notes, les scripts de test, les captures
d'écran, tout ce qui s'appelle `old` ou `backup`, le dossier `data` avec vos
propres sauvegardes et vos propres meilleurs scores dedans, et tout ce que vous
ne voudriez pas qu'un inconnu lise. Tout cela s'empaquette parfaitement bien, et
c'est justement le problème : ça part avec le jeu, et n'importe qui y a accès en
un `--unpack`.
La façon fiable de savoir ce que vous avez vraiment publié, c'est de le
désempaqueter dans un dossier vide et de regarder. Faites-le une fois pour
chaque version publiée. Ça prend une minute, et c'est la seule vérification qui
voit ce que vous avez oublié plutôt que ce dont vous vous souvenez.
[#_credits_and_licenses]
=== Crédits et licences
Si des images, des sons, de la musique ou une police de votre jeu ont été faits
par quelqu'un d'autre, vous publiez son travail, et les conditions auxquelles il
l'a donné sont une obligation plutôt qu'une politesse. Ce n'est pas une
formalité et ce n'est pas une affaire d'avocats : une personne a fait cette
chose et a demandé une seule chose précise en retour, en général son nom.
Tenez un registre pendant que vous travaillez, pas à la fin. Un simple fichier
texte dans le dossier du jeu, avec une ligne par élément : ce qu'est le fichier,
d'où il vient, qui l'a fait, quelle licence, et ce que cette licence exige.
L'écrire au fur et à mesure prend quelques secondes. Le reconstituer la veille
d'une sortie, à partir d'un historique de navigateur, c'est comme ça que des
crédits se perdent.
Quelques choses à savoir avant d'en avoir besoin. « Téléchargement gratuit »
n'est pas une licence ; cherchez les vraies conditions. Creative Commons `BY`
veut dire que vous devez donner le crédit indiqué, et il n'y a pas d'exception
pour les jeux. `CC0` et le domaine public ne demandent rien, et créditer quand
même vous coûte une ligne. Les polices ont leurs propres licences, et certaines
permettent de les embarquer alors que d'autres non. Vous n'avez presque jamais
le droit d'utiliser la musique d'une vidéo. Tout ce qu'un outil a généré n'est
pas plus propre que les conditions de cet outil.
Ensuite, mettez les crédits quelque part où un joueur peut les atteindre, dans
le jeu. Pas seulement dans un fichier texte à côté -- un jeu empaqueté est un
seul fichier, et le fichier texte est dedans, là où personne ne regardera. Le
script à la fin de cette leçon est cet écran-là.
Le `Singe/LICENSES` de Singe lui-même est la forme à copier : une ligne par
bibliothèque, avec sa version, sa licence et sa provenance. La liste de votre
jeu sera plus courte, et le travail est le même.
[#_a_version_number_that_means_something]
=== Un numéro de version qui veut dire quelque chose
Votre jeu aura une deuxième version. La première circulera aussi longtemps
qu'internet existera, et quelqu'un vous enverra un rapport de bogue à son sujet
sans avoir la moindre idée de la version qu'il a.
Alors choisissez un numéro, mettez-le là où un joueur peut le lire, et n'en
réutilisez jamais un. Trois parties, c'est la forme courante -- `1.0.2` -- et ce
que veulent dire les parties compte moins que le fait qu'elles ne font que
monter. Augmentez la dernière pour une correction, celle du milieu pour quelque
chose de nouveau, la première quand vous avez fait un autre jeu. Quoi que vous
choisissiez, notez-le dans votre lisez-moi, pour que le vous du futur s'y tienne
aussi.
`games.dat` n'a pas de clé de version, alors c'est à vous de porter le numéro.
Mettez-le dans le script sous forme de constante, affichez-le sur l'écran des
crédits, et mettez-le dans le nom de ce que vous envoyez (`MyGame-1.0.2.game`,
`MyGame-1.0.2.patch`). Le rapport de bogue donne alors la version affichée à
l'écran, et vous savez de quels fichiers il parle.
[#_testing_on_a_machine_that_is_not_yours]
=== Tester sur une machine qui n'est pas la vôtre
Votre machine est le pire endroit possible pour tester une version. Elle a les
polices que vous avez installées, le dossier que vous étiez en train de
modifier, le fichier de sauvegarde avec vos réglages dedans, et le moteur dans
l'état où un mois de travail l'a laissé.
Le test minimum, dans l'ordre :
. Désempaquetez la version dans un dossier vide et lisez la liste des fichiers.
. Copiez *uniquement* le fichier `.game` dans un autre dossier, avec une copie
neuve de Singe et rien d'autre, et lancez-le depuis le menu comme le fera un
joueur.
. Supprimez le dossier `data` de ce dossier et relancez. C'est un premier
lancement sur une machine neuve : pas de réglages sauvegardés, pas de
meilleurs scores, pas de compte connecté. Un jeu qui ne marche qu'à son
deuxième lancement est un bogue courant, et c'est comme ça qu'on le trouve.
. Donnez-le à une personne qui ne l'a jamais vu, et regardez sans l'aider.
Puis testez la machine autant que le jeu. Elle aura un écran d'une autre forme,
alors regardez votre jeu en fenêtre et en plein écran. Elle n'aura peut-être
aucun périphérique 3D, et c'est pour ça que le menu propose un second moteur de
rendu. La page *System Information* des outils de service affiche sur un seul
écran la version du moteur, le système d'exploitation, le processeur, le moteur
de rendu, les décodeurs et le dossier de données ; une photo de cette page
répond à presque tout un rapport de bogue, et le dire à vos joueurs dans votre
lisez-moi vous épargnera une semaine de devinettes.
[#_the_script_2]
=== Le script
Un écran de crédits et de version. C'est peu de chose à écrire, et c'est la
partie de la publication que la plupart des jeux ratent.
[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
----
Remplacez chaque ligne de `CREDITS` par la vérité sur votre propre jeu. Les
textes provisoires sont là pour montrer la forme : ce que c'est, qui l'a fait,
quelle licence, et d'où ça vient.
[#_what_just_happened_25]
=== Ce qui vient de se passer
[source,lua]
----
local GAME_VERSION = "1.0.2"
----
Un seul endroit, en haut, en majuscules parce que la valeur ne change jamais
pendant que le jeu tourne. Tout ce qui affiche la version lit ceci, si bien que
l'augmenter demande une seule modification plutôt qu'une recherche.
[source,lua]
----
"Music \"Night Drive\" by A. Composer",
----
Ces barres obliques inverses sont la séquence d'échappement que vous avez
rencontrée à la leçon un : un guillemet double qui fait partie du texte au lieu
de terminer la chaîne. Les titres contiennent des guillemets, et c'est pour ça
qu'une liste de crédits tombe dessus dès sa première ligne.
[source,lua]
----
for row = 0, ROWS - 1 do
line = CREDITS[scroll + row + 1]
if line then
----
Dix lignes d'écran qui montrent dix lignes d'une liste plus longue que dix.
`scroll` indique jusqu'où la fenêtre est descendue dans la liste. Le `+ 1` est
là parce que Lua compte une liste à partir de un et que les lignes de l'écran
comptent à partir de zéro, et le `if line then` est là parce que la fin de la
liste vaut `nil` -- lire au-delà de la fin d'une table n'est pas une erreur en
Lua, ça ne vous donne simplement rien, mais afficher ce rien en serait une.
[source,lua]
----
overlayPrint(2, 1, "Singe " .. SINGE_VERSION_STRING .. " id " .. (singeGetGameId() or "none"))
----
Trois faits dont un rapport de bogue a besoin et qu'un joueur ne peut pas
trouver autrement : votre version sur la ligne du dessus, la version du moteur,
et l'identifiant du jeu. `SINGE_VERSION_STRING` est une globale que le moteur
définit avant que votre script tourne. `or "none"` couvre le cas où le jeu n'a
pas d'entrée dans `games.dat`, pour que la ligne s'affiche au lieu de planter
sur un `nil`.
[source,lua]
----
overlayPrint(2, FIRST_ROW + ROWS + 2, singeGetDataPath())
----
L'endroit où ce jeu écrit. Pour un jeu empaqueté, c'est le seul endroit où quoi
que ce soit est écrit, et c'est le dossier qu'un joueur supprime pour repartir
de zéro. L'afficher transforme « où sont mes sauvegardes ? » en une question à
laquelle vous n'aurez jamais à répondre.
Si ce chemin dépasse à droite de l'écran, ce n'est pas cassé : `overlayPrint`
laisse tomber les caractères qui dépasseraient du bord. L'overlay par défaut
fait soixante cellules de caractère de large.
[#_try_it_30]
=== Essayez
. *Empaquetez-le.* Mettez ce script dans le dossier de votre jeu, lancez
`--pack`, et lancez le fichier `.game`. Puis désempaquetez-le dans un dossier
vide et lisez la liste de ce qui en est sorti.
. *Remplacez un fichier.* À côté du fichier empaqueté, créez un dossier qui
porte son nom sans l'extension, et mettez-y un fichier modifié. Lancez le jeu
empaqueté et regardez votre fichier non empaqueté l'emporter.
. *Faites un correctif.* Passez la version à `1.0.3`, mettez seulement le
script dans un dossier `fixes`, empaquetez-le en `.patch`, et appliquez-le au
jeu que vous avez déjà fait. Vérifiez la version à l'écran.
. *Cassez l'empaqueteur.* Renommez votre `games.dat` un instant et essayez
d'empaqueter. Puis remettez-le, faites pointer l'un de ses noms `SCRIPT` vers
un fichier qui n'existe pas, et réessayez. Deux refus différents, tous les
deux avant que quoi que ce soit soit écrit.
. *Écrivez les vrais crédits.* Parcourez le dossier de votre jeu fichier par
fichier et écrivez une ligne pour chacun de ceux que vous n'avez pas faits.
Si vous ne vous rappelez plus d'où vient quelque chose, vous savez maintenant
si vous devriez le publier.
[#_break_it_on_purpose_29]
=== Cassez-le exprès
Lancez Singe une fois depuis l'*intérieur* du dossier de votre jeu, pour que le
moteur y extraie ses fichiers de support, puis empaquetez-le :
----
Singe --pack MyGame MyGame.game
----
----
!!! MyGame has Framework.singe: Singe/Framework.singe
!!! MyGame has sh file: Menu.sh
----
Rien n'est écrit. Chaque ligne donne un fichier qu'il refuse et la raison, et
vous pouvez les recevoir dans n'importe quel ordre, ou en avoir d'autres en
plus. Il y a une copie du dossier de support du moteur lui-même dans votre jeu,
et le script de lancement que le moteur a écrit à côté, parce que vous avez
lancé Singe depuis là à un moment donné. Un jeu ne doit jamais emporter ni l'un
ni l'autre : les fichiers de support appartiennent au Singe que lance le
joueur, quel qu'il soit, pas à votre jeu, et un `Framework.singe` empaqueté
serait chargé à la place du sien. Sous Windows, la seconde ligne nomme
`Menu.bat` à la place.
Supprimez le dossier `Singe` et `Menu.sh` (ou `Menu.bat`) de l'intérieur de
votre jeu, gardez le dossier de votre jeu comme un dossier à côté du programme
Singe plutôt qu'autour de lui, et empaquetez de nouveau. C'est la même organisation que celle que décrivait la
leçon treize, et cette erreur est ce qui arrive quand elle se dérègle.
[#_what_you_learned_30]
=== Ce que vous avez appris
* `Singe --pack folder name.game` transforme un dossier de jeu en un seul
fichier, et copier ce fichier à côté du programme Singe l'installe.
* Chaque nom de fichier d'un jeu empaqueté est cherché d'abord dans un dossier
non empaqueté, puis dans le dossier de données, puis dans la base de données.
* Un dossier non empaqueté qui porte le nom du fichier empaqueté l'emporte sur
lui fichier par fichier, et c'est comme ça que vous continuez à
travailler -- et comme ça que vous publiez une version cassée si vous oubliez qu'il est là.
* Chargez avec `DIR` et n'écrivez que sous `singeGetDataPath()`, sinon votre jeu
marchera non empaqueté et échouera empaqueté.
* `--patch` remplace des fichiers d'un jeu publié en une seule transaction ;
`--unpack` ressort tout.
* L'empaqueteur refuse les fichiers du moteur, les conflits de noms, et un
`games.dat` qui pointe vers des fichiers qu'il n'a pas.
* Désempaquetez votre propre version et lisez ce qu'elle contient avant de
l'envoyer où que ce soit.
* Les images et les sons de quelqu'un d'autre viennent avec des conditions, et
les respecter veut dire un écran de crédits dans le jeu.
* Un numéro de version ne fait que monter, et sa place est à l'écran.
* Testez sur une machine qui n'est pas la vôtre, en commençant sans aucun
dossier `data`.
[#_next_time_30]
=== La prochaine fois
Un seul fichier, installé, crédité et numéroté. La dernière leçon le met dans
une caisse en bois avec une fente à pièces et sans clavier, ce qui change plus
de choses dans votre jeu que vous ne le pensez.