mystnb/tutorials/01-du-mystere-au-menu.txt

463 lines
40 KiB
Plaintext

[center][big][brown][b]01 : Du mystère au menu ![/b][/brown][/big][/center]
Je vous propose de commencer cette série par l'écran d'accueil du jeu et un peu de gestion de projet. Normalement il faudrait coder le moteur d'abord, mais ce serait trop brutal. Voilà ce qu'on va réaliser à la place :
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-menu-animated.gif[/img]
[i]Image 1 : la pub ne mentait pas sur le « noir et blanc ».[/i][/center]
Une police facile à dessiner, un titre qui en promet plus que le jeu n'en propose, et un joli effet graphique pour vous surprendre un peu, rien de bien extravagant. J'ai fait sobre dans le menu pour rester dans le minimalisme de [url=https://www.planet-casio.com/Fr/forums/topic16318-1-1kbcj3-le-theme.html]la 1kBCJ#3[/url] dont ce jeu est issu, mais vous allez voir que ce menu tout simple va déjà nous occuper un bon moment.
On a donc une image fixe et une sélection de niveaux tout ce qu'il y a de plus classique. Le but c'est d'avoir un fichier de sauvegarde qui dit quels niveaux on a débloqués, mais ça ce sera pas pour tout de suite, donc pour l'instant on va automatiquement débloquer tous les niveaux sauf le dernier pendant les tests. :)
Avant de commencer, notez que tout ce tutoriel est suivi sur [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb]un dépôt Git[/url]. Si vous ne voulez pas vous lancer dans un projet de zéro tout de suite, vous pouvez clôner celui du jeu et vous déplacer dans l'historique tout en suivant le tutoriel. Dans tous les cas, vous y trouverez les images et ressources qu'on va utiliser tout le long ; je donnerai donc régulièrement des liens qui y pointent.
[brown][b]Création du projet[/b][/brown]
Commençons par créer les fichiers dont on a besoin. Le fxSDK fournit un modèle de projet avec tout ce qu'il faut pour compiler, via l'outil [b][brown]`fxsdk`[/brown][/b]. Le projet sera compilé avec CMake ; vous n'êtes pas obligés de comprendre les fins détails mais je vous invite à lire le [url=https://www.planet-casio.com/Fr/forums/topic16647-1-tutoriel-compiler-des-add-ins-avec-cmake-fxsdk.html]tutoriel de compilation d'add-ins avec CMake[/url] qui explore cet aspect en détail. Pour l'instant, mettez-vous dans un dossier de votre choix (pour moi `~/Programs`) et créez un nouveau projet :
[code]% fxsdk new mystere-noir-et-blanc
Created a new project Mystere- (build system: CMake).
Type 'fxsdk build-fx' or 'fxsdk build-cg' to compile the program.[/code]
Le fxSDK a créé l'add-in automatiquement. Voyons voir ce qu'il y a dans ce dossier pour commencer ! Vous pouvez le parcourir interactivement sur Gitea en consultant le commit [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/src/commit/fd94704d0ce1f656ea130b35ab16465f7cddd26d]`fd94704d0`[/url].
[i](Note : Ce tutoriel a été commencé en 2020, à une époque où le fxSDK produisait un projet avec un Makefile. J'ai ensuite réécrit l'historique en 2021 quand CMake a été ajouté. Si vous voulez la version Makefile du début de ce tutoriel, consultez la branche [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/src/branch/makefile]`makefile`[/url].)[/i]
[code]% tree mystere-noir-et-blanc
mystere-noir-et-blanc
├── assets-cg
│   ├── example.png
│   ├── fxconv-metadata.txt
│   ├── icon-sel.png
│   └── icon-uns.png
├── assets-fx
│   ├── example.png
│   ├── fxconv-metadata.txt
│   └── icon.png
├── CMakeLists.txt
└── src
└── main.c[/code]
Le fxSDK a créé un certain nombre de dossiers et fichiers. Voici à quoi ils servent :
• `assets-cg` contient toutes les images, polices et autres ressources pour la Graph 90+E. Le fxSDK permet de programmer pour la Graph 90+E, et même de faire un add-in pour les Graph mono et la Graph 90+E en même temps. Ici, on va programmer uniquement sur Graph mono, donc je vais l'ignorer.
• `assets-fx` contient les images, polices et autres ressources pour les Graph mono. C'est là qu'on va mettre la plupart de nos données ! Toutes les Graph mono sont identiques du point de vue de gint, de la Graph 75+E que j'utilise aux Graph 35+ USB, 35+E, 35+E II, et même les vieilles SH3, tout est compatible.
• Le `CMakeLists.txt` est un fichier indiquant comment compiler l'application. Quand vous tapez "`fxsdk build-fx`" dans le terminal, c'est lui qui donne toutes les instructions.
• `src` contient comme d'habitude tous les fichiers de code. Le fxSDK a copié un `main.c` avec un code d'exemple.
Je ne vais pas beaucoup utiliser le CMakeLists.txt dans ce tutoriel, mais on va prendre un instant pour y indiquer des noms plus appropriés pour l'add-in et le fichier g1a que ce que le fxSDK a mis par défaut. Dans le fichier, cherchez l'appel à `generate_g1a()`, qui ressemble à ça :
[code] generate_g1a(TARGET myaddin OUTPUT "MyAddin.g1a"
NAME "MyAddin" ICON assets-fx/icon.png)[/code]
Le paramètre `NAME` est le nom de l'add-in dans le menu SYSTEM, et le paramètre `OUTPUT` est le nom du fichier g1a. On va remplacer `"MyAddin"` par `MystNB` :
[code] generate_g1a(TARGET myaddin OUTPUT "MystNB.g1a"
NAME "MystNB" ICON assets-fx/icon.png)[/code]
Et voilà, on peut attaquer le code. ^^
[brown][b]Hello, World![/b][/brown]
C'est parti ! Commençons avec le "Hello, World!" de gint que le fxSDK a copié dans `src/main.c` pour vous.
[code]#include <gint/display.h>
#include <gint/keyboard.h>
int main(void)
{
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "Sample fxSDK add-in.");
dupdate();
getkey();
return 1;
}[/code]
Pour le réaliser, on a besoin d'utiliser des fonctions de gint qui sont décrites par deux en-têtes, `<gint/display.h>` (le partie dessin et l'affichage) et `<gint/keyboard.h>` (la gestion du clavier). Lorsque vous les incluez, ces en-têtes expliquent au compilateur quelles sont les fonctions proposées par gint, mais aussi quels sont les noms des couleurs et comment reconnaître les images et les polices. Si vous essayez d'utiliser les fonctionnalités de gint sans avoir inclus les en-têtes correspondants, le compilateur se plaindra qu'il ne sait pas de quoi vous parlez. ^^
Et donc une fois les en-têtes inclus on commence tout de suite à sortir les pinceaux.
• `dclear(C_WHITE)` efface la VRAM et remplit tout en blanc. C'est comme `Bisp_AllClr_VRAM()` excepté que vous pouvez changer de couleur. Les couleurs sont définies dans `<gint/display-fx.h>` et `<gint/display-cg.h>`, j'en reparle dans un instant.
• `dtext(x,y,fg,str)` affiche la chaîne de caractères `str` à la position indiquée et avec la couleur spécifiée. La position est en pixels, avec `(0,0)` en haut à gauche. Ce sera le cas pour toutes les fonctions de dessin, sans exception aucune ! `fg` est la couleur du texte. Cette fonction ressemble à `PrintXY()`, sauf qu'on a plus de choix de couleurs et que plus tard on pourra changer la couleur de fond, l'alignment du texte et la police !
• `dupdate()` affiche les contenus de la VRAM à l'écran, c'est l'équivalent de `Bdisp_PutDisp_DD()` tant qu'on n'active pas le moteur de gris.
En plus des informations importantes pour le compilateur, les en-têtes de gint contiennent aussi des informations importantes pour vous, avec la liste des fonctions, leurs paramètres et leurs rôles. Je vous conseille de prendre l'habitude d'aller les lire si vous avez besoin d'informations. Par exemple, la liste des couleurs est dans les en-têtes `<gint/display-fx.h>` et `<gint/display-cg.h>`. (Comme le dessin est très différent entre Graph mono et Graph 90+E, `<gint/display.h>` est séparé en deux versions.) Tous ces en-têtes sont dans le dossier `include` de gint. Vous pouvez les trouver dans le dossier où vous avez clôné gint lors de l'installation, ou [url=https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint]en ligne sur le dépôt Gitea[/url].
Vous savez certainement que les add-ins s'exécutent vite, c'est sans doute pour ça que vous êtes ici. Dans ce programme, l'affichage va prendre entre 1 et 2 ms. (Sur Graph 90+E, il faudrait compter entre 15 et 20 ms, tout simplement parce qu'il y a 170 fois plus de donnés graphiques à manipuler.) On ne veut pas que l'add-in s'arrête maintenant, parce que contrairement à un programme Basic il reviendrait directement au menu de la calculatrice et on ne verrait rien.
On utilise pour éviter ça la fonction `getkey()`, qui met le programme en pause jusqu'à ce que l'utilisateur appuie sur une touche. `getkey()` renvoie un [i]« événement »[/i] indiquant quelle touche a été pressée, quand, et d'autres informations utiles. Chaque touche a un nom, que vous pouvez trouver [url=https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/master/include/gint/keycodes.h]dans `<gint/keycodes.h>`[/url] (qui est inclu par `<gint/keyboard.h>`, c'est pour ça que je ne vous l'ai pas fait inclure tout à l'heure).
La fonction `getkey()` est centrale, vous l'utiliserez pour toutes les entrées clavier sur tous les écrans qui ne sont pas en temps réel (comme les menus ou les applications utilitaires), et parfois même pour les écrans en temps réel dans les jeux. J'aimerais donc éclaircir trois choses au sujet de `getkey()` pour éviter toute confusion avec la fonction `GetKey()` que l'on trouve dans fxlib et que vous avez peut-être déjà utilisée.
1. `getkey()` [b]attend[/b]. Le code qui suit (le `return`) ne sera pas exécuté tant que l'utilisateur n'aura pas appuyé sur une touche, peu importe si ça lui prend 20 minutes !
2. `getkey()` [b]renvoie[/b] un événement, alors que `GetKey()` modifie un pointeur qu'on lui passe en argument.
3. `getkey()` [b]ne rafraîchit pas l'écran[/b], contrairement à `GetKey()` qui appelle `Bdisp_PutDisp_DD()` avant de se mettre en attente. Il faut appeler `dupdate()` explicitement.
Pour l'instant on ignore complètement la valeur de retour de `getkey()` (l'événement qui nous dit, entre autres, quelle touche a été pressée) donc on peut appuyer sur n'importe quelle touche pour quitter. On changera ça bientôt !
[brown][b]Compiler et tester[/b][/brown]
La compilation d'un add-in mérite des tutoriels complets ; vous pouvez découvrir les grands principes dans le [url=https://www.planet-casio.com/Fr/forums/topic15930-1-tdm-n016-grands-principes-de-compilation.html]Tutoriel du Mercredi #16[/url] et les spécificités de CMake dans le [url=https://www.planet-casio.com/Fr/forums/topic16647-1-tutoriel-compiler-des-add-ins-avec-cmake-fxsdk.html]tutoriel de compilation d'add-ins avec CMake[/url]. Pour l'instant, on va ignorer tous ces détails et utiliser le système que le fxSDK a copié pour nous. Pour compiler votre application pour la famille des Graph mono (Graph 35+E et affiliées), utilisez la commande "`fxsdk build-fx`". (Dans le fxSDK, "`fx`" représente la famille des Graph mono, tandis que "`cg`" représente les Prizm et Graph 90+E. Naturellement il existe aussi "`fxsdk build-cg`" pour compiler une version Graph 90+E.)
[code]% fxsdk build-fx
-- The C compiler identification is GNU 10.2.0
-- The CXX compiler identification is GNU 10.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/el/.local/bin/sh-elf-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/el/.local/bin/sh-elf-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Gint: TRUE (found suitable version "2.2.1-6", minimum required is "2.1")
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/mystere-noir-et-blanc/build-fx
Scanning dependencies of target myaddin
[ 33%] Building C object CMakeFiles/myaddin.dir/src/main.c.obj
[ 66%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/example.png
[100%] Linking C executable myaddin
[100%] Built target myaddin[/code]
Il y a pas mal de détails sordides ici, mais vous devez pouvoir comprendre une partie du texte qui apparaît à l'écran, car c'est ici que vous aurez quasiment tous vos messages d'erreur pendant le développement. Bien comprendre les erreurs et qui vous les a envoyées vous évitera beaucoup de frustration et de temps perdu.
La première étape du processus est la [i]configuration[/i], où CMake analyse le fichier CMakeLists.txt, vérifie que le compilateur marche, que les bibliothèques comme gint sont bien là, et produit un Makefile qui permet de compiler l'application. Toutes les lignes au début qui ont un `--` sont des messages affichés par CMake durant la configuration.
La deuxième étape du processus est la [i]compilation[/i], où le Makefile produit par CMake est lancé. Ce Makefile appelle GCC et affiche notamment les messages avec un pourcentage devant. À chacune des trois lignes en vert, make a lancé une commande pour compiler un fichier (les deux "Building ...") ou produire l'add-in final (le "Linking ...").
Par défaut, les commandes ne sont pas affichées, et on n'a que les lignes en vert. Mais si vous tapez `fxsdk build-fx -B VERBOSE=1` elles seront affichées. `VERBOSE=1` demande au Makefile d'afficher les commandes, et `-B` force à recompiler même si les sources n'ont pas changé (normalement si rien n'a changé rien n'est recompilé). Si vous faites ça, vous aurez (notamment) les lignes suivantes :
[code]% fxsdk build-fx -B VERBOSE=1
(...)
[ 33%] Building C object CMakeFiles/myaddin.dir/src/main.c.obj
/home/el/.local/bin/sh-elf-gcc -DFX9860G -DTARGET_FX9860G -mb -ffreestanding -nostdlib -Wa,--dsp -Wall -Wextra -Os -fstrict-volatile-bitfields -o CMakeFiles/myaddin.dir/src/main.c.obj -c /tmp/mystere-noir-et-blanc/src/main.c
[ 66%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/example.png
fxconv /tmp/mystere-noir-et-blanc/assets-fx/example.png -o CMakeFiles/myaddin.dir/assets-fx/example.png --toolchain=sh-elf --fx
[100%] Linking C executable myaddin
/usr/bin/cmake -E cmake_link_script CMakeFiles/myaddin.dir/link.txt --verbose=1
/home/el/.local/bin/sh-elf-gcc -nostdlib -T fx9860g.ld CMakeFiles/myaddin.dir/src/main.c.obj CMakeFiles/myaddin.dir/assets-fx/example.png -o myaddin -lgcc -lgcc /home/el/.local/share/giteapc/Lephenixnoir/sh-elf-gcc/lib/gcc/sh3eb-elf/10.2.0/libgint-fx.a -lgcc
/home/el/.local/bin/sh-elf-objcopy -O binary -R .bss -R .gint_bss myaddin myaddin.bin
fxg1a myaddin.bin -n MyAddin -i /tmp/mystere-noir-et-blanc/assets-fx/icon.png -o /tmp/mystere-noir-et-blanc/MyAddin.g1a
[100%] Built target myaddin
(...)[/code]
Chaque ligne (excepté les messages du Makefile en vert) est une commande que le Makefile a lancé et qui contribue à compiler votre add-in. Le premier mot de chaque commande est le nom d'un outil qui a travaillé pour vous, les autres mots sont des options.
La première commande fait appel à [brown][b]sh-elf-gcc[/b][/brown] : c'est le compilateur. Il compile `src/main.c` (qui est mentionné tout à la fin de la ligne). La seconde fait appel à [brown][b]fxconv[/b][/brown], qui convertit l'image `assets-fx/example.png`. Les autres commandes utilisent [brown][b]sh-elf-gcc[/b][/brown], [brown][b]sh-elf-objcopy[/b][/brown] et [brown][b]fxg1a[/b][/brown] pour rassembler tous les résultats et créer un fichier g1a.
C'est pas grave si tout cela vous échappe un peu, j'y reviendrai de temps en temps. Pour l'instant, vous avez deux nouvelles choses dans le dossier de votre projet :
• Un dossier `build-fx` qui contient tous les fichiers compilés. Vous pouvez le supprimer à tout moment, mais il permet de recompiler l'application plus vite en récupérant le code déjà compilé quand vous ne l'avez pas modifié. Par exemple, si vous retapez `fxsdk build-fx` il ne se passe rien car vous n'avez rien modifié depuis la dernière compilation. En général vous voulez donc le laisser tranquille. Toutefois il n'est pas apprécié sur un dépôt Git donc ajoutez-le dans votre `.gitignore`. ;)
• Le fichier `MystNB.g1a` qui est notre add-in compilé !
Et c'est terminé ! :D Il ne reste qu'à envoyer l'add-in sur votre calculatrice par votre méthode préférée. Sur ma Graph 35+E II je l'enverrai par USB avec `fxlink -sw MystNB.g1a`. Pour les autres Graph, un outil de choix sous Linux est [url=https://www.planet-casio.com/Fr/forums/topic14487-1--GNU-Linux--P7,-pour-des-transferts-a-repasser.html]l'utilitaire P7 de Cakeisalie5[/url] (que `fxsdk send-fx` appelle pour vous !). Vous pouvez aussi utiliser FA-124, mais si vous en arrivez là je vous plains. ^^"
Une fois transféré, l'add-in apparaît dans le menu et on peut observer le résultat attendu :
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-sample-add-in.png[/img]
[i]Image 2 : Yeah![/i][/center]
Sur ce, il est temps de passer aux choses sérieuses !
[brown][b]Les assets[/b][/brown]
Voici les assets que l'on va utiliser : une icône pour l'add-in, l'image du titre, les icônes pour les niveaux, et la police de caractères pour écrire le texte du jeu et les numéros des niveaux. On ne va pas utiliser celle par défaut de gint, qui manque un peu de style. Le PNG est fortement conseillé et même obligatoire pour l'icône. :)
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-icon.png[/img]
[i]Image 3 : La clé du mystère était dans l'icône depuis le début ![/i]
[img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-title.png[/img]
[i]Image 4 : Aujourd'hui on repousse les limites du lisible.[/i]
[img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-levels.png[/img]
[i]Image 5 : Il y aura plus que deux niveaux quand même.[/i]
[img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-font.png[/img]
[i]Image 6 : Une police qui a du caractère ![/i][/center]
Les images ci-dessus sont agrandies, bien sûr pour le projet il vous faut les originaux. Vous pouvez les télécharger directement sur le dépôt :
[center][url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/src/commit/f8a880bc8a49a53b3f094d6836fe31f38929c911/assets-fx][b]» Dossier `assets-fx` sur le dépôt à ce stade «[/b][/url][/center]
Comme vous pouvez le voir, la police qu'on utilise est vraiment une image, c'est juste une grille de caractères. Le fxSDK va la convertir en une police utilisable avec `dtext()` en utilisant un de ses outils, [brown][b]`fxconv`[/b][/brown].
Téléchargez ou copiez les trois images dans `assets-fx` (celles du dépôt, pas les versions agrandies visibles sur cette page !). Au passage, je vais supprimer le fichier d'exemple `example.png`. Vous devez obtenir ceci :
[code]% tree assets-fx
assets-fx
├── fxconv-metadata.txt
├── font_mystere.png
├── icon.png
├── levels.png
└── title.png[/code]
Pour pouvoir convertir tout cela avec [brown][b]fxconv[/b][/brown], il va falloir fournir quelques informations. Le résultat de la conversion sera une variable dans le programme C, il faut donc lui donner un nom. Et on a aussi plein d'informations à fournir sur la police, notamment quels caractères sont dessinés et à quel endroit. Toutes ces [i]métadonnées[/i] sont à indiquer dans un fichier `fxconv-metadata.txt`, et le fxSDK en a créé un pour nous. Voyons ce qu'il contient.
[code]example.png:
type: bopti-image
name: img_example[/code]
`fxconv-metadata.txt` contient une liste de blocs (ici 1). Chaque bloc sert à donner des informations sur un ou plusieurs fichiers : on indique d'abord le nom du fichier, et ensuite les paramètres qui s'y appliquent (avec une indentation). Les deux paramètres essentiels sont `type`, qui indique quelle conversion on veut faire, et `name`, qui indique le nom de la variable résultante. Ici, on convertit `example.png` comme une image bopti et on produit une variable `img_example` en résultat. bopti est un module de gint chargé de l'affichage des images, c'est lui qui fournit la fonction `dimage()` dont on se servira dans un instant.
On va modifier ça et donner les paramètres pour nos images et pour la police `font_mystere.png`
[code]levels.png:
type: bopti-image
name: img_levels
title.png:
type: bopti-image
name: img_title
font_mystere.png:
type: font
charset: print
grid.size: 5x7
grid.padding: 1
proportional: true[/code]
Pour les images, rien de très nouveau. Pour la police il y a plus d'informations ; voyons ce qu'elles veulent dire. :)
• "`charset:print`" indique quels sont les caractères qui sont dessinés. "`print`" c'est l'ensemble des caractères affichables de l'ASCII, il y en a 95 qui commencent par l'espace et se terminent par le tilde. (Le carré en bas à droite sera ignoré.) Avec cette information, [brown][b]`fxconv`[/b][/brown] sait que le 35ème caractère est "B".
• "`grid.size:5x7`" indique quelle taille fait chaque caractère. Comme vous pouvez le voir, on donne la même taille pour tout le monde, donc il faut indiquer une taille assez grande. La plupart des caractères font moins de 5 pixels de large, mais `M`, `W` et quelques autres nous obligent à utiliser une grille de largeur au moins 5. Pour la hauteur, la plupart des caractères font 6 pixels mais il y a des caractères comme `g` ou la virgule qui descendent une ligne plus bas que les autres, portant le total à 7. Cette grille dit à [brown][b]fxconv[/b][/brown] où les caractères sont dessinés dans notre PNG, mais ce n'est pas forcément la taille finale à l'écran de la calculatrice.
• "`grid.padding:1`" indique qu'autour de chaque caractère, j'ai laissé un cadre blanc de 1 pixel de large. Je l'ai fait pour conserver un espacement confortable pendant l'édition. Vous pouvez le voir sur l'image suivante où j'ai mis le padding en valeur en bleu et jaune :
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-font-padding.png[/img]
[i]Image 6 : Il y a un pixel de padding autour de chaque caractère.[/i][/center]
• "`proportional:true`" signifie qu'on veut une police à largeur variable : on veut que chaque caractère prenne juste la place qui est nécessaire pour le dessiner sur la calculatrice. Ainsi, même si `M` ou `W` prennent 5 pixels, `I` fera quand même 1 pixel et `A` 4 pixels. En pratique, après avoir isolé chaque caractère en utilisant la grille, [brown][b]fxconv[/b][/brown] va encore éliminer le blanc à gauche et à droite.
Vous n'avez pas besoin de comprendre tous les détails de comment les polices marchent pour l'instant. Si des choses vous échappent, vous aurez l'occasion d'y revenir plus tard. ;)
Avant de pouvoir tester, il faut qu'on pense à modifier la liste des fichiers du projet dans `CMakeLists.txt`. Ici, j'ai simplement ajouté des assets pour Graph mono, donc je modifie la variable `ASSETS_fx`. Si vous n'êtes pas à l'aise avec cete partie de CMake, je vous invite à lire le [url=https://www.planet-casio.com/Fr/forums/topic16647-1-tutoriel-compiler-des-add-ins-avec-cmake-fxsdk.html]tutoriel d'introduction à CMake[/url] qui est très détaillé.
[code]set(ASSETS_fx
assets-fx/levels.png
assets-fx/title.png
assets-fx/spritesheet.png
assets-fx/font_mystere.png
# ...
)[/code]
Vous pouvez maintenant recompiler avec "`fxsdk build-fx`" et observer que l'image et la police sont converties !
[code]% fxsdk build-fx
-- Configuring done
-- Generating done
-- Build files have been written to: /home/el/Programs/mystere-noir-et-blanc/build-fx
Scanning dependencies of target myaddin
Consolidate compiler generated dependencies of target myaddin
[ 20%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/levels.png
[ 40%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/title.png
[ 60%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/font_mystere.png
[ 80%] Linking C executable myaddin
[100%] Built target myaddin[/code]
Vous pouvez voir qu'il y a de nouveau des lignes avec `--`. C'est parce qu'on a modifié `CMakeLists.txt`, du coup CMake reconfigure et recompile au lieu de simplement recompiler. De nouveau, si vous ajoutez `VERBOSE=1`, vous verrez le détail des commandes (`-B` là aussi est nécessaire pour forcer à recompiler) :
[code]% fxsdk build-fx -B VERBOSE=1
(...)
[ 40%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/levels.png
fxconv /home/el/Programs/mystere-noir-et-blanc/assets-fx/levels.png -o CMakeFiles/myaddin.dir/assets-fx/levels.png --toolchain=sh-elf --fx
[ 60%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/title.png
fxconv /home/el/Programs/mystere-noir-et-blanc/assets-fx/title.png -o CMakeFiles/myaddin.dir/assets-fx/title.png --toolchain=sh-elf --fx
[ 80%] Building FXCONV object CMakeFiles/myaddin.dir/assets-fx/font_mystere.png
fxconv /home/el/Programs/mystere-noir-et-blanc/assets-fx/font_mystere.png -o CMakeFiles/myaddin.dir/assets-fx/font_mystere.png --toolchain=sh-elf --fx
[100%] Linking C executable myaddin
/usr/bin/cmake -E cmake_link_script CMakeFiles/myaddin.dir/link.txt --verbose=1
/home/el/.local/bin/sh-elf-gcc -nostdlib -T fx9860g.ld CMakeFiles/myaddin.dir/src/main.c.obj CMakeFiles/myaddin.dir/assets-fx/levels.png CMakeFiles/myaddin.dir/assets-fx/title.png CMakeFiles/myaddin.dir/assets-fx/font_mystere.png -o myaddin -lgcc /home/el/.local/share/giteapc/Lephenixnoir/sh-elf-gcc/lib/gcc/sh3eb-elf/10.2.0/libgint-fx.a -lopenlibm -lgcc
/home/el/.local/bin/sh-elf-objcopy -O binary -R .bss -R .gint_bss myaddin myaddin.bin
fxg1a -n MystNB -i /home/el/Programs/mystere-noir-et-blanc/assets-fx/icon.png -o /home/el/Programs/mystere-noir-et-blanc/MystNB.g1a myaddin.bin
[100%] Built target myaddin[/code]
Reprenons un instant pour voir ce qui s'est passé. [brown][b]`fxconv`[/b][/brown] a été appelé deux fois, une pour convertir la police et une pour convertir l'image. Ensuite [brown][b]`sh-elf-gcc`[/b][/brown] a été appelé pour fusionner le résultat de la conversion avec le résultat de la compilation de `main.c`. Enfin, [brown][b]`sh-elf-objcopy`[/b][/brown] et [brown][b]`fxg1a`[/b][/brown] ont été appelés de nouveau pour recréer le fichier g1a. C'est un peu difficile à lire parce que les commandes sont compliquées, mais voici quelques pistes :
• Les lignes qui commencent par `/usr/bin/cmake -E` peuvent être ignorées.
• Les noms de fichiers à rallonge comme `CMakeFiles/myaddin.dir/src/main.c.obj` n'ont pas vraiment d'importance : regardez seulement le dernier morceau, `main.c.obj`. C'est ce dont vous avez besoin pour comprendre de quel fichier il s'agit.
[brown][b]Dessiner un menu principal[/b][/brown]
Grâce à notre image de titre et à notre police, on peut maintenant réaliser le début de notre menu principal :
[code]#include <gint/display.h>
#include <gint/keyboard.h>
int main(void)
{
extern bopti_image_t img_title;
extern bopti_image_t img_levels;
extern font_t font_mystere;
dfont(&font_mystere);
dclear(C_WHITE);
dimage(0, 2, &img_title);
for(int i = 1; i <= 8; i++)
{
int x = 20 + 11*(i-1);
int y = 36;
if(i != 8)
{
dsubimage(x, y, &img_levels, 0,0,10,10, DIMAGE_NONE);
dprint(x+3, y+2, C_BLACK, "%d", i);
}
else
{
dsubimage(x, y, &img_levels, 11,0,10,10, DIMAGE_NONE);
}
}
dupdate();
getkey();
return 1;
}[/code]
Il y a pas mal de choses à dire sur cette nouvelle fonction. D'abord, les trois premières lignes :
[code]extern bopti_image_t img_title;
extern bopti_image_t img_levels;
extern font_t font_mystere;[/code]
Ce sont des déclarations de variables. Les deux premières variables sont du type `bopti_image_t`, ce sont les images du titre et des niveaux. ([i]bopti[/i] c'est le nom d'un composant de gint chargé de l'affichage des images, c'est lui qui est derrière `dimage()` et `dsubimage()` qu'on va voir très vite. Le "`_t`" à la fin est une convention qui signifie "type".) La dernière est du type `font_t`, c'est notre police (pas fou je sais :p ).
Les deux sont marquées `extern`, ce qui est très important : ça signifie qu'on ne crée pas de variable (on les [i]déclare[/i] mais on ne les [i]définit[/i] pas, dans le jargon) ; on indique seulement au compilateur que ces variables existent [i]ailleurs[/i] et on lui promet de les lui fournir quand il en aura besoin. Ces variables sont en fait créées par [brown][b]`fxconv`[/b][/brown] lorsqu'il convertit les images et polices. :)
Désormais, plus besoin de tableaux longs et moches du genre `const unsigned char title[256] = { }` à copier-coller partout dans votre code, il vous suffit d'enregistrer votre image ou police dans `assets-fx`, de la déclarer dans `fxconv-metadata.txt` et `CMakeLists.txt`, et vous pouvez tout de suite l'utiliser dans le code avec `extern`. Le fxSDK se charge de la convertir et de l'ajouter à votre add-in durant la compilation. On verra plus tard comment nommer automatiquement les images pour éviter d'avoir à modifier `fxconv-metadata.txt`. ;)
[code]dfont(&font_mystere);[/code]
Ensuite, on change de police avec un appel à la fonction `dfont()`, qui prend en paramètre la nouvelle police à utiliser. Vous noterez qu'il faut donner [i]« l'adresse »[/i] de la variable (c'est ce petit "`&`" devant le nom). Si vous ne savez pas ce que c'est, pas grave. Retenez juste que dès que vous utiliserez une image ou une police de gint provenant de l'extérieur du programme il faudra systématiquement mettre ce "`&`" (libimg est la seule exception jusqu'ici).
Et c'est tout en fait, après cet appel à `dfont()`, `dtext()` affiche du texte avec notre police personnalisée. On peut alors effacer l'écran et dessiner l'image du titre :
[code]dclear(C_WHITE);
dimage(0, 2, &img_title);[/code]
`dimage()` est une fonction qui dessine une image complète à la position indiquée. Comme pour `dtext()`, les coordonnées sont dans l'ordre (x,y), en pixels en partant d'en haut à gauche, et ce sont les deux premiers paramètres. Comme avec la police à l'instant, il faut donner l'adresse de l'image, donc il y a encore un "`&`" devant "`img_title`". Contrairement à MonochromeLib vous n'avez plus besoin d'indiquer la dimension de l'image parce qu'elle est stockée avec les pixels !
Ensuite on a la partie dans laquelle on dessine les rectangles des niveaux. Pour les niveaux débloqués, on met le rectangle vide avec le numéro du niveau, et pour les autres on met le cadenas. Bon ici on n'a pas de sauvegarde donc on va prétendre que tous les niveaux sont débloqués sauf le dernier niveau, le 8. La boucle complète ressemble à ça.
[code]for(int i = 1; i <= 8; i++)
{
int x = 20 + 11*(i-1);
int y = 36;
if(i != 8)
{
dsubimage(x, y, &img_levels, 0,0,10,10, DIMAGE_NONE);
dprint(x+3, y+2, C_BLACK, "%d", i);
}
else
{
dsubimage(x, y, &img_levels, 11,0,10,10, DIMAGE_NONE);
}
}[/code]
Rien de très inattendu ici, on a 8 niveaux donc notre variable `i` varie de 1 à 8. Les variables `x` et `y` sont les coordonnées du coin haut gauche du carré pour chaque niveau ; la hauteur y=36 est constante et la position horizontale varie de 11 pixels à chaque fois, c'est calculé pour que le résultat soit centré.
Dans la boucle, on continue de dessiner. On utilise une nouvelle fonction `dsubimage()` permettant de dessiner une partie seulement d'une image, c'est comme ça qu'on se débrouille pour avoir uniquement le rectangle ou uniquement le rectangle avec cadenas.
`dsubimage()` est une version plus générale de `dimage()` qui permet de dessiner n'importe quelle partie d'une image et possède quelques options. Comme `dimage()`, on commence par spécifier la position où le résultat doit apparaître à l'écran et l'image source. Ensuite on précise quelle partie de l'image (`img_levels`) on veut dessiner sous la forme d'un quadruplet (x, y, largeur, hauteur). Par exemple le cadenas est à la position (11,0) dans `img_levels` et sa taille est 10x10 pixels. Enfin il y a les options, mais pour l'instant on ne va pas s'y intéresser donc on écrit `DIMAGE_NONE`.
Lorsque le niveau est débloqué, on veut afficher le numéro avec `dtext()`, sauf qu'on n'a le numéro que sous forme d'entier (notre `int i`) et qu'on n'a pas sa représentation textuelle. Si la différence vous paraît douteuse, sachez que les bits de l'entier 4 ne sont pas du tout les mêmes que ceux du texte `"4"`, donc il y a un [i]calcul[/i] à faire pour passer de l'un à l'autre. C'est vrai dans tous les langages, même si beaucoup vous le cachent (Python par exemple fait automatiquement le calcul dans `print()` sans vous le dire). Si vous avez déjà fait du C, vous savez que les fonctions de la famille de `printf()` sont chargées de calculer le texte pour plein de types de variables.
Sans rentrer dans les détails, les fonctions de la famille de `printf()` utilisent un [i]format[/i] qui décrit le texte qu'on veut générer et à quels endroits il faut calculer la représentation textuelle de variables. Ces substitutions s'écrivent `%<lettre>` (dans leur forme simple) avec une lettre différente pour chaque type de données. Par exemple le format "`x=%d`" représente un texte contenant "`x=`" suivi de la valeur d'un entier (`d` est la lettre qui représente les entiers dans une substitution). Sur un ordinateur, si vous appelez `printf("x=%d", 42)`, le `%d` est remplacé par la représentation textuelle de 42 et vous obtenez "`x=42`" dans votre terminal.
Sur la calculatrice on n'a pas de terminal donc on n'a pas `printf()`, mais gint fournit une fonction `dprint()` qui fait quasiment pareil. `dprint(x,y,fg,format,...)` affiche à la position `(x,y)` et avec la couleur `fg` le résultat du calcul du format selon les règles de substitution de la famille de `printf()`.
L'appel à `dprint()` dans la boucle sert donc à écrire la valeur de `i` dans la case de chaque niveau. Il y a pas mal de paramètres, mais vous pouvez vous souvenir que c'est comme `dtext()` à part que les substitution sont autorisées (et du coup pour chaque `%<lettre>` dans le format vous devez ajouter un argument qui indique la valeur à représenter).
C'était un peu gros mais on en reverra. Une fois tout dessiné, on n'oublie pas d'actualiser l'écran avec `dupdate()` (ce que `getkey()` ne fait pas pour nous). Et c'est gagné ! :D
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-menu.png[/img]
[i]Image 7 : Le menu principal prend forme ![/i][/center]
Cet état d'avancement correspond au commit [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/commit/f8a880bc8a49a53b3f094d6836fe31f38929c911]`f8a880bc8`[/url] sur le dépôt du projet.
[brown][b]Le curseur interactif[/b][/brown]
Il nous reste encore à rendre ce menu interactif. On va créer un curseur simple pour sélectionner les niveaux en inversant les contenus des rectangles, et ensuite on va la faire bouger avec les flèches gauche et droite. Appuyer sur EXE validera la sélection et on quittera l'add-in. Lorsqu'il y aura des sauvegardes, on pourra afficher correctement les numéros des niveaux débloqués mais pour l'instant on va se contenter de tous les débloquer sauf le dernier.
La première chose qu'on va faire, est qui devrait devenir un réflexe pour vous, c'est [i]séparer le code de dessin de la logique du menu[/i]. Pour ça, on va se créer un petite fonction `draw_menu()`, et notre boucle principale pourra se concentrer sur la gestion du clavier.
[code]static void draw_menu(int selected)
{
extern bopti_image_t img_title;
extern bopti_image_t img_levels;
dclear(C_WHITE);
dimage(0, 2, &img_title);
for(int i = 1; i <= 8; i++)
{
int x = 20 + 11*(i-1);
int y = 36;
if(i != 8)
{
dsubimage(x, y, &img_levels, 0,0,10,10, DIMAGE_NONE);
dprint(x+3, y+2, C_BLACK, "%d", i);
}
else
{
dsubimage(x, y, &img_levels, 11,0,10,10, DIMAGE_NONE);
}
if(i == selected)
{
drect(x+1, y+1, x+8, y+8, C_INVERT);
}
}
}[/code]
Si vous ne maîtrisez pas encore les [i]prototypes de fonctions[/i] ou les fichiers d'en-tête, prenez soin de mettre cette nouvelle fonction avant `main()`, parce que le compilateur lit les fichiers de haut en bas et ne serait pas très content de voir `main()` appeler une hypothétique fonction `draw_menu()` qu'il n'a encore jamais rencontrée.
Il n'y a pas grand-chose de nouveau à voir dans cette fonction, c'est quasiment la même qu'avant. Je l'ai qualifiée de `static`, cela veut dire qu'elle ne sera visible que dans `main.c` quand on ajoutera d'autres fichiers. Après tout, aucune autre partie de l'add-in n'en aura besoin, donc ça ne sert à rien de leur montrer qu'elle existe. Je n'ai pas mis le `dfont()` ici car il suffit de l'appeler une seule fois au début de l'add-in pour changer la police de façon permanente.
On rencontre une nouvelle fonction, qu'on utilise ici pour inverser la couleur des rectangles. `drect(x1,y1,x2,y2,color)` remplit le rectangle allant de `(x1,y1)` jusqu'à `(x2,y2)` (tous les deux inclus) avec la couleur spécifiée. Ici on utilise la couleur spéciale `C_INVERT` qui inverse le noir et le blanc.
Regardons donc plutôt les choses intéressantes qui se passent dans `main()`.
[code]int main(void)
{
extern font_t font_mystere;
dfont(&font_mystere);
int selected = 1;
int key = 0;
while(key != KEY_EXE)
{
draw_menu(selected);
dupdate();
key = getkey().key;
if(key == KEY_LEFT && selected > 1)
selected--;
if(key == KEY_RIGHT && selected < 8)
selected++;
}
return 1;
}[/code]
Il y a pas mal de choses nouvelles à regarder ici.
• D'abord on a commencé à s'intéresser aux touches ! Pour ça, j'ai stocké la valeur de `getkey().key` dans une variable. Comme je l'ai mentionné tout à l'heure, `getkey()` renvoie un [i]événement[/i] qui contient plusieurs informations. Il vous dit notamment quelle touche est concernée, si c'est un nouvel appui ou une répétition (les touchées fléchées sont répétées si vous appuyez longtemps dessus), si `SHIFT` ou `ALPHA` ont été activés avant l'événement, le moment où l'événement s'est produit... ici j'ignore tout sauf la touche exacte. On reviendra sur les informations utiles renvoyées par `getkey()` et les options disponibles.
• Ensuite on voit apparaître la logique du menu. La boucle `while` continue jusqu'à ce qu'on essaie d'entrer dans un niveau avec `EXE`. On peut se déplacer horizontalement mais bien sûr on vérifie qu'on ne se déplace pas avant le niveau 1 ou après le niveau 8.
• L'écran est redessiné même si on presse une touche inutile comme `DEL`. C'est pas top, mais c'est loin d'être dramatique comme en Basic Casio.
• Même si la boucle est infinie, on peut toujours quitter l'add-in en appuyant sur `MENU` puis revenir ensuite, comme avec le `GetKey()` de fxlib. Par contre si on quitte en sortant de `main()`, c'est définitif !
Et voilà ! :bounce:
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-01-menu-animated.gif[/img]
[i]Image 8 : C'était pas si difficile ![/i][/center]
Le projet à ce stade peut être consulté au commit [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/src/commit/87c9bc3dfeff761f293dc047d83b73421c685b60]`87c9bc3df`[/url] sur le dépôt.
[brown][b]Conclusion[/b][/brown]
C'est un menu très schématique et qu'on pourrait facilement améliorer... quelques animations, un peu de décoration, utiliser la touche `SHIFT` pour valider, et plus encore. Ce tutoriel n'est qu'une brève introduction sur les fonctions fournies par gint mais décrit la majorité de la gestion routinière d'un projet. J'en parlerai moins dans la suite, et ça devrait être plus fun. :)
Voilà un petit récapitulatif de ce qu'on a vu.
:here: Créer et compiler un projet avec le fxSDK
:here: Ajouter des assets et spécifier leurs paramètres pour [brown][b]fxconv[/b][/brown] dans `fxconv-metadata.txt`
:here: Dessin général (`<gint/display.h>`) : `dclear()`, `drect()`, `dupdate()`
:here: Dessin d'images (`<gint/display.h>`) : `dimage()`, `dsubimage()`
:here: Dessin de texte (`<gint/display.h>`) : `dtext()`, `dprint()`, `dfont()`
:here: Gestion du clavier (`<gint/keyboard.h>`) : `getkey()`