tutorial-02: final take

This commit is contained in:
Lephenixnoir 2020-08-21 17:00:13 +02:00
parent aadaaf4a81
commit 76e2a7ed60
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
2 changed files with 27 additions and 16 deletions

View File

@ -21,7 +21,7 @@ struct anim_frame
/* struct anim_data: Data for currently-running animations */
struct anim_data
{
/* ANimation update function */
/* Animation update function */
anim_function_t *function;
/* Frame to draw */
struct anim_frame img;

View File

@ -12,7 +12,7 @@ Pour l'instant la map sera juste un rectangle vide avec des bords, le minimum n
[i]Image 1 : Full graphismes et zéro gameplay. Que demande le peuple ?[/i][/center]
Ce tutoriel contient pas mal de code spécifique au jeu qu'on est en train de créer et un peu moins de code gint que le précédent. Cependant, je pense qu'il est utile de réfléchir ensemble à comment séparer les différentes parties du jeu pour rendre le code flexible et élégant à l'échelle du projet complet. Ça peut vous sembler superflu à ce stade, mais il faut réaliser que vous transformez sans cesse le code de votre jeu pour ajouter ou modifier des fonctions, et s'il est mal codé la structure ne [i]résistera[/i] pas. Et donc autant commencer tout de suite à faire les choses bien. ;)
On arrivera vite au point où le code et les fonctions de gint se complexifient par rapport au programme simple de la dernière fois. Je continuerai à expliquer les aspects du langage C au fur et à mesure, mais ce tutoriel ne peut pas être un cours complet de C, donc n'hésitez pas à en consulter un (par exemple [url=https://zestedesavoir.com/tutoriels/755/le-langage-c-1/]celui de Zestes de Savoir[/url]) ou à poser des questions [url=http://www.planet-casio.com/Fr/forums/lecture_sujet.php?id=14915&page=1]sur le topic des commentaires[/url] si quelque chose vous échappe. ^^
On arrivera vite au point où le code et les fonctions de gint se complexifient par rapport au programme simple de la dernière fois. Je continuerai à expliquer les aspects du langage C au fur et à mesure, mais ce tutoriel ne peut pas être un cours complet de C, donc n'hésitez pas à en consulter un (par exemple [url=https://zestedesavoir.com/tutoriels/755/le-langage-c-1/]celui de Zestes de Savoir[/url]) ou à poser des questions [url=https://www.planet-casio.com/Fr/forums/topic14915-1-tutoriels-dutilisation-de-gint-commentaires.html]sur le topic des commentaires[/url] si quelque chose vous échappe. ^^
[brown][b]Les bases du moteur : joueurs, niveaux et parties[/b][/brown]
@ -63,11 +63,10 @@ Quant à la définition de la structure à proprement parler, rien de bien impre
Si vous n'êtes pas encore tout à fait familier·ère avec les structures, c'est le bon moment de se rappeler que cette définition ne crée aucune variable, elle explique simplement ce que les variables de type `struct player` contiendront quand on créera.
On a également besoin de la spritesheet pour pouvoir faire notre affichage. Voici à quoi elle ressemble ; comme d'habitude, ne récupérez pas cette image (qui est agrandie), prenez plutôt la version originale sur le dépôt. Enregistrez-la sous le nom `assets-fx/spritesheet.png`.
On a également besoin de la spritesheet pour pouvoir faire notre affichage. Voici à quoi elle ressemble ; comme d'habitude, ne récupérez pas cette image (qui est agrandie), prenez plutôt [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/src/commit/8dba2daeb3c9321d8eb284df0b9076e18addf0c4/assets-fx/spritesheet.png]la version originale sur le dépôt[/url]. Enregistrez-la sous le nom `assets-fx/spritesheet.png`.
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-02-spritesheet.png[/img]
[i]Image 2 : Pour les diagonales, on repassera.[/i][/center]
Je profite de l'occasion pour améliorer le `fxconv-metadata.txt`. Pour l'instant, on a listé tous les fichiers à la main dedans, mais ça devient vite répétitif, surtout qu'il n'y a pas de paramètres particuliers. Nos images ressemblent à ça :
[code]levels.png:
@ -179,7 +178,6 @@ Rien de bien inattendu ici, juste une utilisation un peu maline de `dsubimage()`
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-02-spritesheet-frame1.png[/img]
[i]Image 3 : Tout ce travail et j'utilise qu'un seul frame ![/i][/center]
Pour information, le choix de `CELL_X()` et `CELL_Y()` donne une grille qui a la tête suivante à l'écran. Une partie des cellules est masquée par le bord de l'écran pour gagner de la place.
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-02-mapgrid.png[/img]
@ -230,7 +228,7 @@ Notez l'astuce consistant à calculer le déplacement en x et en y avec une diff
[brown][b]Acquisition des entrées[/b][/brown]
La gestion des entrées sera très basique pour l'instant donc je l'ai laissée dans `main.c`. Le but de cette fonction est simplement de lire les entrées clavier et de renvoyer la direction dans laquelle le joueur demande à se déplacer. On va donc juste chercher `KEY_DOWN`, `KEY_RIGHT`, `KEY_UP` et `KEY_LEFT` et les renvoyer vers leurs `DIR_*` associés. La paragraphe suivant est long simplement parce que c'est une bonne occasion de parler de la fonction `getkey_opt()`. :)
La gestion des entrées sera très basique pour l'instant donc je l'ai laissée dans `main.c`. Le but de cette fonction est simplement de lire les entrées clavier et de renvoyer la direction dans laquelle le joueur demande à se déplacer. On va donc juste chercher `KEY_DOWN`, `KEY_RIGHT`, `KEY_UP` et `KEY_LEFT` et les renvoyer vers leurs `DIR_*` associés. La paragraphe qui vient après est long simplement parce que c'est une bonne occasion de parler de la fonction `getkey_opt()`. :)
[code]/* Returns a direction to move in */
static int get_inputs(void)
@ -251,13 +249,13 @@ Une des fonctionnalités assurées par `getkey()` est la répétition des touche
C'est ici que la fonction `getkey_opt()` entre en jeu. `getkey_opt()` est une version générale de `getkey()` (définie dans `<gint/keyboard.h>`) qui possède plein d'options pour personnaliser la façon dont vous lisez le clavier. Il y a deux arguments : d'abord les options et ensuite le timeout. Expliquer tout le comportement de la fonction serait long, donc je vais juste présenter rapidement les options.
• `GETKEY_MOD_SHIFT` et `GETKEY_MOD_ALPHA` activent l'utilisation de SHIFT et ALPHA comme des modifieurs, pour former des combinaisons comme SHIFT+sin → asin. Lorsque ces options sont activées, `getkey_opt()` ne renvoie jamais d'événement ayant `.key == KEY_SHIFT` ou `.key == KEY_ALPHA` et attend à la place que vous appuyiez sur une autre touche avant de s'arrêter. Dans ce cas, l'événement renvoyé contient `.shift == 1` ou `.alpha == 1` pour indiquer l'état des modifieurs. Une application peut par exemple interpréter un événement ayant `.key == KEY_SIN` et `.shift == 1` comme asin. Activé dans `getkey()`.
• `GETKEY_MOD_SHIFT` et `GETKEY_MOD_ALPHA` activent l'utilisation de SHIFT et ALPHA comme des modifieurs, pour former des combinaisons comme SHIFT+sin → asin. Lorsque ces options sont activées, `getkey_opt()` ne renvoie jamais d'événement ayant `.key == KEY_SHIFT` ou `.key == KEY_ALPHA` et attend à la place que vous appuyiez sur une autre touche avant de s'arrêter. Dans ce cas, l'événement renvoyé contient `.shift == 1` ou `.alpha == 1` pour indiquer l'état des modifieurs. Une application peut par exemple interpréter un événement ayant `.key == KEY_SIN` et `.shift == 1` comme asin. Activés dans `getkey()`.
• `GETKEY_BACKLIGHT` active la combinaison SHIFT+OPTN pour allumer et éteindre le rétroéclairage sur les modèles monochromes qui le supportent (essentiellement la Graph 75+E et ses prédécesseurs). Activé dans `getkey()`.
• `GETKEY_MENU` autorise le retour au menu en appuyant sur la touche `MENU`. Activé dans `getkey()`.
• `GETKEY_REP_ARROWS` et `GETKEY_REP_ALL` activent la répétition des touches directionnelles et de toutes les touches, respectivement. Le délai de répétition est contrôlé par la fonction `getkey_repeat()`. Par défaut, la première répétition se produit après 400 et les suivantes toutes les 40 ms. Le premier est activé dans `getkey()`, pas le second.
• `GETKEY_REP_ARROWS` et `GETKEY_REP_ALL` activent la répétition des touches directionnelles et de toutes les touches, respectivement. Le délai de répétition est contrôlé par la fonction `getkey_repeat()`. Par défaut, la première répétition se produit après 400 ms et les suivantes toutes les 40 ms. Le premier est activé dans `getkey()`, pas le second.
• `GETKEY_REP_FILTER` active le filtre de répétitions, un outil puissant qui vous permet de contrôler la répétition des touches avec finesse. Vous pouvez contrôler quelles touches se répétent, combien de fois, sous quel délai, et même changer les délais d'une fois sur l'autre. Activé dans `getkey()`, mais n'a aucun effet tant que vous n'appelez pas `getkey_repeat_filter()` pour configurer tout ça. J'aurai peut-être l'occasion d'en reparler.
@ -321,7 +319,7 @@ Le jeu est au tour par tour et les portes changent d'état entre les tours (acti
Vous noterez que `level_finished` n'est jamais mis à 1 donc la boucle ne se termine jamais ; ce n'est pas grave parce qu'on peut toujours fuir vers le menu principal durant les appels à `getkey_opt()` en appuyant sur MENU. ^^
Et voilà le résultat. Le code à cet étape est celui du commit [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/commit/8dba2daeb3c9321d8eb284df0b9076e18addf0c4]`8dba2daeb`[/url] dans l'historique du dépôt.
Et voilà le résultat ! Le code à cette étape est celui du commit [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/commit/8dba2daeb3c9321d8eb284df0b9076e18addf0c4]`8dba2daeb`[/url] dans l'historique du dépôt.
[center][img]https://gitea.planet-casio.com/Lephenixnoir/mystnb/raw/branch/master/tutorials/gint-tuto-02-basic-movement.gif[/img]
[i]Image 6 : ... ouais en fait non, c'est tout nul.[/i][/center]
@ -370,7 +368,7 @@ Voici à quoi ressemble le callback. Je prends un argument de type `volatile int
*tick = 1;
return TIMER_CONTINUE;
}[/code]
Pour faire simple, ce callback affecte une variable à 1 et relance le timer. Quelle variable ? Eh bien celle dont on donnera un pointeur en argument quand on va former le callback. Voici l'appel complet.
Pour faire simple, ce callback affecte une variable à 1 et relance le timer. La variable concernée est celle dont on donnera un pointeur en argument quand on va former le callback. Voici l'appel complet.
[code]/* Global tick clock */
static volatile int tick = 1;
@ -393,7 +391,7 @@ Ensuite on aura la boucle principale, et après ça on pourra arrêter le timer
[code]if(t >= 0) timer_stop(t);
return 1;[/code]
Notez que si lors d'une interruption le callback renvoie `TIMER_STOP`, gint arrête immédiatement le timer avec `timer_stop()` donc vous n'avez pas à le faire vous-même (et ne devez pas essayer puisqu'il a pu être de nouveau configuré entre temps).
Notez que si le callback renvoie `TIMER_STOP` lors d'une interruption, gint arrête immédiatement le timer avec `timer_stop()` donc vous n'avez pas à le faire vous-même (et ne devez pas essayer puisqu'il a pu être de nouveau configuré entre temps).
[brown][b]Boucle de jeu principale animée[/b][/brown]
@ -446,13 +444,13 @@ static int get_inputs(void)
}[/code]
Le changement majeur ici est le deuxième argument à `getkey_opt()`, celui qui permet d'interrompre l'attente. Avant de se mettre en attente, `getkey_opt()` vérifie ce deuxième argument et s'arrête si la valeur est autre chose que 0. Ici, comme on l'a carrément initialisé à 1, `getkey_opt()` n'attendra pas du tout et s'arrêtera immédiatement s'il n'y a aucun événement clavier à traiter. Cette simple modification en fait une sorte de `Getkey` du Basic Casio mais en beaucoup plus puissant. ;)
Si jamais `getkey_opt()` se fait interrompre, elle renvoie un événement donc le type est `KEYEV_NONE` (alors que tous les événements qu'on avait vus jusqu'à présent avait le type `KEYEV_DOWN` indiquant qu'une touche a été pressée). Comme j'ai besoin de tester le type et de la touche à la fois, je stocke l'événement dans une variable, ce qui est une bonne occasion de donner quelques détails.
Si jamais `getkey_opt()` se fait interrompre, elle renvoie un événement dont le type est `KEYEV_NONE` (alors que tous les événements qu'on avait vus jusqu'à présent avaient le type `KEYEV_DOWN` indiquant qu'une touche a été pressée). Comme j'ai besoin de regarder le type et la touche à la fois, je stocke l'événement dans une variable, ce qui est une bonne occasion de donner quelques détails.
Comme vous pouvez le voir, le type d'un événement clavier est `key_event_t` (comme d'habitude le "`_t`" à la fin est une convention signifiant que c'est un type pour ne pas le confondre avec un nom de variable). Il est défini dans `<gint/keyboard.h>` et possède les champs suivants :
• `type` : le type d'événement, qui peut être `KEYEV_NONE` (rien), `KEYEV_DOWN` (pression), `KEYEV_UP` (relâchement) ou `KEYEV_HOLD` (répétition). Toutes les fonctions ne génèrent pas tous les types d'événements, par exemple `getkey_opt()` ne renvoie jamais un événement de type `KEYEV_UP`. De même, aucune autre fonction que `getkey()` et `getkey_opt()` ne renvoie d'événements de type `KEYEV_HOLD`.
• `key` : la touche qui a été pressées. La liste complète est dans `<gint/keycodes.h>`.
• `key` : la touche qui a été pressée. La liste complète est dans `<gint/keycodes.h>`.
• `mod`, `shift` et `alpha` : l'état des modifieurs. Si `mod` vaut 1 alors `shift` et `alpha` indiquent si les touches SHIFT et ALPHA ont été pressées pour modifier la touche ; `getkey()` et `getkey_opt()` font ça. Si `mod` vaut 0 alors les modifieurs sont ignorés, c'est ce qui se passe dans toutes les autres fonctions.
@ -471,7 +469,7 @@ Pour conclure ce tutoriel, voici une présentation rapide du système d'animatio
[code]/* struct anim_data: Data for currently-running animations */
struct anim_data
{
/* ANimation update function */
/* Animation update function */
anim_function_t *function;
/* Frame to draw */
struct anim_frame img;
@ -505,9 +503,9 @@ Le travail de `engine_tick()` consiste simplement à réduire `duration` du temp
player->idle = !player->anim.function(&player->anim, 0);
}
}[/code]
La fonction en elle-même s'occupe principalement de passer au sprite suivant et de recharger `duration`, et renvoie un entier pour indiquer si le joueur est « occupé » : c'est utilisé pour refuser le mouvement durant une animation de marche. L'animation de marche change aussi les valeurs de `dx` et `dy` pour déplacer visuellement le joueur au cours du temps, et effectue une transition vers l'animation par défaut lorsque le joueur arrive à sa destination.
La fonction d'animation en elle-même s'occupe principalement de passer au sprite suivant et de recharger `duration`, et renvoie un entier pour indiquer si le joueur est « occupé » : c'est utilisé pour refuser le mouvement durant une animation de marche. L'animation de marche change aussi les valeurs de `dx` et `dy` pour déplacer visuellement le joueur au cours du temps, et effectue une transition vers l'animation par défaut lorsque le joueur arrive à sa destination.
Le code de cette nouvelle version du programme se trouve au commit [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/commit/b8ae9615b84bd5b50c10138e93af543cf3187a50]`b8ae961`[/url] sur le dépôt. Voyez notamment [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/src/commit/b8ae9615b84bd5b50c10138e93af543cf3187a50/src/animation.c]`animation.c`[/url] si le code détaillé des animations vous intéresse.
Le code de cette nouvelle version du programme se trouve au commit [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/commit/30ab7bae0a6746365c86c1ca81b9aa1ea98a4775]`30ab7bae0`[/url] sur le dépôt. Voyez notamment [url=https://gitea.planet-casio.com/Lephenixnoir/mystnb/src/commit/30ab7bae0a6746365c86c1ca81b9aa1ea98a4775/src/animation.c]`animation.c`[/url] si le code détaillé des animations vous intéresse.
Il est temps de regarder ce que tout ça nous donne sur la calculatrice... !
@ -515,3 +513,16 @@ Il est temps de regarder ce que tout ça nous donne sur la calculatrice... !
[i]Image 7 : Toujours pas de gameplay, mais fichtre c'est beau.[/i][/center]
Magnifique ! :D
[brown][b]Conclusion[/b][/brown]
Ce tutoriel introduit la plupart des notions du noyau nécessaires pour coder des jeux en temps réel. Il y a aussi des aspects inhérents au fxSDK comme la conversion des maps, et quelques subtilités comme l'utilisation du système de fichiers. Mais dans l'ensemble, le plus dur est de structurer votre code d'une façon qui résistera aux multiples itérations que vous ferez sur vos programmes. ^^
Petit résumé de ce qu'on a vu ici !
• Utilisation de `dsubimage()` pour former une spritesheet
• `<gint/keyboard.h>` : `getkey_opt()` et ses options
• Cas simple d'utilisation d'un timer
• `<gint/timer.h>` : `timer_setup()`, `timer_start()`, `timer_stop()`
• `<gint/clock.h>` : `sleep()`
• Les événements claviers et leurs types
• Description rapide de `pollevent()`, `waitevent()`, `clearevents()` et `keydown()`