Yodajr
10/04/2006, 02h56
Jour 6 : Animation des sprites
Aujourd'hui on commence les choses sérieuses B)
Car au dela de la juste mise en pratique des fonctions d'oslib, nous allons beaucoup plus parler à partir de maintenant de programmation de jeu vidéo à proprement parler.
Aussi, je m'en vais vous expliquer MA facon de faire, qui n'est certainement pas la meilleure, mais voila quoi, ca fonctionne plutot bien :p
Je veux juste que vous compreniez que ce n'est pas la seule et unique facon de faire ;)
Trève de blabla, let's go !
1) Le Spriteset
Vu que nous voulons animer notre sprite, il faut faire comme pour les dessins animés : dessiner toutes les étapes de l'animation (ou en tout cas un certain nombre en fonction du degré de fluidité voulu : le nombre d'étape d'animation d'un mario marchant est risible face à celui d'un aladdin MD)
Pour ce tuto, nous aurons besoin de 4 séquences d'animation : perso marchant vers la droite, vers la gauche, vers le haut et enfin vers le bas.
Ainsi que les 4 positions statiques.
Une fois les dessins (ou les rips) effectués, il faut établir les bases du spriteset :
Cherchez dans toutes les étapes d'anims celle qui est la plus large en pixels ainsi que celle qui est la plus haute : vous aurez ainsi les dimensions du cadre référence.
Par exemple, pour ce tuto, j'ai rippé l'anim de marche du héros de chrono trigger (vous aviez reconnu ? trop forts ! :D).
Et dans le tas, j'ai trouvé l'étape la plus large (22 pixels de large) : http://hothmoon.free.fr/psp/dev/jour06_img1.png ainsi que la plus haute (35 pixels de haut) : http://hothmoon.free.fr/psp/dev/jour06_img2.png
J'obtiens donc pour mon cadre référence 22x35.
Ce qui veux dire que tous mes sprites constituant les differentes animations doivent dorénavant tous mesurer 22x35 : à vous d'agrandir, mais pas en resizant le sprite, juste en ajoutant du vide aux bons endroits, tout en gardant un même repère fixe (le sol par exemple) :
http://hothmoon.free.fr/psp/dev/jour06_img3.png deviens : http://hothmoon.free.fr/psp/dev/jour06_img4.png
(j'ai agrandi un poil afin que vous puissiez voir les changements apportés, changements que j'ai coloré en vert pour l'exemple)
Une fois que j'ai tous mes sprites à la bonne taille, je les assemble en une seule et même image : le spriteset.
Pour faciliter le code plus tard, je les dispose sur autant de lignes que de directions, chaque colonne représentant une étape d'animation.
Mon spriteset ressemble alors à cela :
http://hothmoon.free.fr/psp/dev/jour06_img5.png
C'est beau hein ? :D
Donc mon spriteset à moi possède 4 directions, une étape statique et 6 étapes de marche, retenez cela pour mieux comprendre le code qui suit :)
2) Le code
//La librairie principale OSLib
#include <oslib/oslib.h>
//les callbacks
PSP_MODULE_INFO("OSLib Sample", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);
//definition des pointeurs vers nos images
OSL_IMAGE *chrono;
//defines
#define BAS 0
#define HAUT 35
#define DROITE 70
#define GAUCHE 105
//variables
int chrono_position;
int anim_marche;
int temp_anim_marche;
//fonctions
void Touches();
void AnimMarche();
int main()
{
//Initialisation de la librairie
oslInit(0);
//Initialisation du mode graphique
oslInitGfx(OSL_PF_8888, 1);
//definition de la transparence
oslSetTransparentColor(RGB(255,0,255));
//chargement de nos images (oui, le "loading" :p)
chrono = oslLoadImageFile("chrono_sheet.png", OSL_IN_RAM, OSL_PF_5551);
//plus de transparence
oslDisableTransparentColor();
//vérification
if (!chrono)
oslDebug("Verifiez que tous les fichiers sont bien copiés dans le répertoire du jeu.");
//place chrono vers le centre de l'écran et dans sa position de départ
chrono->x = 240;
chrono->y = 130;
chrono_position = BAS;
//boucle principale
while (!osl_quit)
{
//Permet de dessiner
oslStartDrawing();
//check des touches
Touches();
//dessine un dégradé
oslDrawGradientRect(0,0,480,272,RGB(0,128,0), RGB(0,128,0), RGB(128,255,128), RGB(128,255,128));
//dessine notre sprite
oslDrawImage(chrono);
//Fin du dessin
oslEndDrawing();
//Synchronise l'écran
oslSyncFrame();
}
//on quitte l'application
oslEndGfx();
oslQuit();
return 0;
}
void Touches()
{
//Lit les touches
oslReadKeys();
if (osl_keys->held.down)
{
//deplace le sprite
chrono->y += 2;
//affiche la bonne ligne dans le spriteset
chrono_position = BAS;
//on anime le sprite
AnimMarche();
}
if (osl_keys->held.up)
{
chrono->y -= 2;
chrono_position = HAUT;
AnimMarche();
}
if (osl_keys->held.left)
{
chrono->x -= 2;
chrono_position = GAUCHE;
AnimMarche();
}
if (osl_keys->held.right)
{
chrono->x += 2;
chrono_position = DROITE;
AnimMarche();
}
//si aucune touche n'est appuyée
if (!osl_keys->held.value)
{
//reinitialisation des variables, pour la prochaine fois
anim_marche = 0;
temp_anim_marche = 0;
//affiche chrono dans sa derniere position
oslSetImageTileSize(chrono,0,chrono_position,22,35 );
}
}
void AnimMarche()
{
//tempo de l'anim
temp_anim_marche++;
if (temp_anim_marche == 6)
{
//relance la tempo
temp_anim_marche = 0;
//prochaine étape de l'anim
anim_marche++;
//affiche la position adequate
oslSetImageTileSize(chrono,(anim_marche * 22),chrono_position,22,35);
//relance l'anim
if (anim_marche == 6) anim_marche = 0;
}
}
3) Explications
#define BAS 0
#define HAUT 35
#define DROITE 70
#define GAUCHE 105
Ici je met en define 4 nombres. Mais pourquoi ceux là ? me demandrez vous... et pourquoi pas ? vous répondrais je :rolleyes:
Non sérieusement, il s'agit du Y correspondant à chacune des 4 directions de mon spriteset. Vous vous rappelez que mon cadre référence fait 35 de haut ? donc le Y de la 1ere direction est à 0, le 2ème à 35, etc...
Donc quand je dirai : positionne toi à GAUCHE, il saura que ca correspond au nombre 105, qui lui même correpond à la dernière direction de mon spriteset ;)
chrono_position = BAS;
Bon ben voila, on y est, intero surprise, après cette commande, à quoi est égal la variable chrono_position ?
Ceux qui ont répondu 67 vont au coin, et ceux qui ont répondu 0 ont le droit de continuer :P
(au fait, retenez qu'au début chrono_position = 0, ca facilitera une explication plus loin)
oslDrawGradientRect(0,0,480,272,RGB(0,128,0), RGB(0,128,0), RGB(128,255,128), RGB(128,255,128));
Dessine un beau dégradé à 4 points. Comme mes 2 premiers et mes 2 derniers points sont identiques, ici ce n'est qu'un dégradé à 2 points (vert foncé vers vert clair)
if (!osl_keys->held.value)
La valeur "value" de la structure osl_keys passe à 1 quand une touche est pressée ou maintenue (pressed ou held).
Donc il me suffit de tester si elle est à 0 pour savoir si le joueur n'appuie sur aucune touche ;)
oslSetImageTileSize(chrono,0,chrono_position,22,35 );
Bon, voici la commande d'oslib la plus importante à comprendre pour ce tuto.
En fait cette fonction permet d'afficher le morceau que l'on veut d'une image, prenons pour mieux comprendre la formulation générique :
oslSetImageTileSize(image,x,y,largeur,hauteur);
Vous vous rappelez notre spriteset ? sans cette commande, il s'afficherai entierement sur l'écran, ce serai pas cool admettez :P
Donc ici, on dit à la PSP : "affiche moi l'image certe, mais seulement le morceau qui commence au point x,y et qui mesure largeur sur hauteur"
Une fois ceci fait, à chaque appel de oslDrawImage(image), seul ce fameux morceau sera affiché.
Compris ?
Donc ici, je dit à la PSP : "affiche moi le spriteset de chrono, mais seulement le morceau qui commence à 0,chrono_position et qui fait 22 pixels de large sur 35 pixels de haut"
Un peu confus toujours ? bon, considérons que c'est la premiere execution et que chrono_position = 0 (bravo à ceux qui s'en sont rappelé, un bon point), la commande deviens :
"affiche moi le spriteset de chrono, mais seulement entre les points 0,0 et 22,35"
C'est plus clair là, non ? elles ne vous disent rien ces coordonnées ?
Hé oui, il s'agit de ma toute premiere étape, le 1er sprite du spriteset.
Ce sprite correspond donc bien à la position BAS.
Vous comprenez un peu mieux la ligne du code ?
Remplacez mentalement la valeur de chrono_position par la valeur correspondant à la position GAUCHE (105), vous pouvez savoir quelle sprite va s'afficher : le dernier de la 1ere colonne (0,105 - 22,140)
Comprennez bien, car la même ligne reviens, mais en un peu plus compliqué :P
temp_anim_marche++;
if (temp_anim_marche == 6)
{
temp_anim_marche = 0;
....
Ici je temporise un peu, sinon nous n'aurions pas le temps de voir les differentes étapes de l'animation. Il faut juste ne pas oublier de repasser la variable temp à 0 :)
oslSetImageTileSize(chrono,(anim_marche * 22),chrono_position,22,35);
Oui voila, je vous avais prévenus :lol:
Mais c'est pas si compliqué :
Observez bien le code : à chaque fois que la tempo est atteinte, on incrémente la variable anim_marche.
Considérons que nous sommes au début et qu'elle était à 0, donc elle est maintenant égale à 1.
Considérons également que chrono_position est toujours égal à 0 (BAS)
Remplacons maintenant toutes les variables par leurs valeurs, nous obtenons :
oslSetImageTileSize(chrono,22,0,22,35);
Ce qui correspond à demander à la PSP d'afficher le 2ème sprite de la 1ere ligne de mon spriteset, c'est à dire la 1ere étape de l'anim de marche vers l'avant !
On peut ainsi continuer mentalement d'incrementer anim_marche pour voir que l'on pointe à chaque fois sur l'étape suivante !
Même chose en changeant la valeur de chrono_position !
Regardez donc le code dans sa globalité : quand le joueur maintient la direction droite appuyée, on déplace le sprite vers la droite, puis on dit que chrono_position est maintenant égal à DROITE ( 3ème ligne d'anim du spriteset) et on lance l'anim.
Tant qu'il maintient la touche, la fonction AnimMarche est lancée à chaque frame, la variable anim_marche est incrémentée toutes les 6 frames, ce qui correspond à une nouvelle position du sprite à afficher. Et enfin quand la derniere étape est atteinte, on revient au début de l'anim et c'est reparti (if (anim_marche == 6) anim_marche = 0;)
Vous n'avez toujours rien compris ? c'est pas grave, remarquez les valeurs numériques de la commande, elles ne vous disent rien ?
Oui c'est ca, il s'agit des valeurs de mon cadre de référence (tout au début). Vous pouvez donc vous contentez de copier/coller le code et de remplacer les valeurs par les votres ;)
Pour les pros : On peut également utiliser oslSetImageTile(image,x0,y0,x1,y1) qui permet d'afficher un morceau de l'image de facon plus précise ;)
Il est possible aussi qu'au lieu d'avoir dans le spriteset les 2 directions droite et gauche, n'en avoir qu'une seule et effectuer un "miroir" sur le sprite au bon moment (oslMirrorImageH(image))
4) Screen et eboot
Voila, vous devez obtenir un truc comme ça :
http://hothmoon.free.fr/psp/dev/jour06_screen.png
Téléchargez l'eboot compilé pour 1.5 ici (http://hothmoon.free.fr/psp/dev/jour06_eboot.zip)
Aujourd'hui on commence les choses sérieuses B)
Car au dela de la juste mise en pratique des fonctions d'oslib, nous allons beaucoup plus parler à partir de maintenant de programmation de jeu vidéo à proprement parler.
Aussi, je m'en vais vous expliquer MA facon de faire, qui n'est certainement pas la meilleure, mais voila quoi, ca fonctionne plutot bien :p
Je veux juste que vous compreniez que ce n'est pas la seule et unique facon de faire ;)
Trève de blabla, let's go !
1) Le Spriteset
Vu que nous voulons animer notre sprite, il faut faire comme pour les dessins animés : dessiner toutes les étapes de l'animation (ou en tout cas un certain nombre en fonction du degré de fluidité voulu : le nombre d'étape d'animation d'un mario marchant est risible face à celui d'un aladdin MD)
Pour ce tuto, nous aurons besoin de 4 séquences d'animation : perso marchant vers la droite, vers la gauche, vers le haut et enfin vers le bas.
Ainsi que les 4 positions statiques.
Une fois les dessins (ou les rips) effectués, il faut établir les bases du spriteset :
Cherchez dans toutes les étapes d'anims celle qui est la plus large en pixels ainsi que celle qui est la plus haute : vous aurez ainsi les dimensions du cadre référence.
Par exemple, pour ce tuto, j'ai rippé l'anim de marche du héros de chrono trigger (vous aviez reconnu ? trop forts ! :D).
Et dans le tas, j'ai trouvé l'étape la plus large (22 pixels de large) : http://hothmoon.free.fr/psp/dev/jour06_img1.png ainsi que la plus haute (35 pixels de haut) : http://hothmoon.free.fr/psp/dev/jour06_img2.png
J'obtiens donc pour mon cadre référence 22x35.
Ce qui veux dire que tous mes sprites constituant les differentes animations doivent dorénavant tous mesurer 22x35 : à vous d'agrandir, mais pas en resizant le sprite, juste en ajoutant du vide aux bons endroits, tout en gardant un même repère fixe (le sol par exemple) :
http://hothmoon.free.fr/psp/dev/jour06_img3.png deviens : http://hothmoon.free.fr/psp/dev/jour06_img4.png
(j'ai agrandi un poil afin que vous puissiez voir les changements apportés, changements que j'ai coloré en vert pour l'exemple)
Une fois que j'ai tous mes sprites à la bonne taille, je les assemble en une seule et même image : le spriteset.
Pour faciliter le code plus tard, je les dispose sur autant de lignes que de directions, chaque colonne représentant une étape d'animation.
Mon spriteset ressemble alors à cela :
http://hothmoon.free.fr/psp/dev/jour06_img5.png
C'est beau hein ? :D
Donc mon spriteset à moi possède 4 directions, une étape statique et 6 étapes de marche, retenez cela pour mieux comprendre le code qui suit :)
2) Le code
//La librairie principale OSLib
#include <oslib/oslib.h>
//les callbacks
PSP_MODULE_INFO("OSLib Sample", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);
//definition des pointeurs vers nos images
OSL_IMAGE *chrono;
//defines
#define BAS 0
#define HAUT 35
#define DROITE 70
#define GAUCHE 105
//variables
int chrono_position;
int anim_marche;
int temp_anim_marche;
//fonctions
void Touches();
void AnimMarche();
int main()
{
//Initialisation de la librairie
oslInit(0);
//Initialisation du mode graphique
oslInitGfx(OSL_PF_8888, 1);
//definition de la transparence
oslSetTransparentColor(RGB(255,0,255));
//chargement de nos images (oui, le "loading" :p)
chrono = oslLoadImageFile("chrono_sheet.png", OSL_IN_RAM, OSL_PF_5551);
//plus de transparence
oslDisableTransparentColor();
//vérification
if (!chrono)
oslDebug("Verifiez que tous les fichiers sont bien copiés dans le répertoire du jeu.");
//place chrono vers le centre de l'écran et dans sa position de départ
chrono->x = 240;
chrono->y = 130;
chrono_position = BAS;
//boucle principale
while (!osl_quit)
{
//Permet de dessiner
oslStartDrawing();
//check des touches
Touches();
//dessine un dégradé
oslDrawGradientRect(0,0,480,272,RGB(0,128,0), RGB(0,128,0), RGB(128,255,128), RGB(128,255,128));
//dessine notre sprite
oslDrawImage(chrono);
//Fin du dessin
oslEndDrawing();
//Synchronise l'écran
oslSyncFrame();
}
//on quitte l'application
oslEndGfx();
oslQuit();
return 0;
}
void Touches()
{
//Lit les touches
oslReadKeys();
if (osl_keys->held.down)
{
//deplace le sprite
chrono->y += 2;
//affiche la bonne ligne dans le spriteset
chrono_position = BAS;
//on anime le sprite
AnimMarche();
}
if (osl_keys->held.up)
{
chrono->y -= 2;
chrono_position = HAUT;
AnimMarche();
}
if (osl_keys->held.left)
{
chrono->x -= 2;
chrono_position = GAUCHE;
AnimMarche();
}
if (osl_keys->held.right)
{
chrono->x += 2;
chrono_position = DROITE;
AnimMarche();
}
//si aucune touche n'est appuyée
if (!osl_keys->held.value)
{
//reinitialisation des variables, pour la prochaine fois
anim_marche = 0;
temp_anim_marche = 0;
//affiche chrono dans sa derniere position
oslSetImageTileSize(chrono,0,chrono_position,22,35 );
}
}
void AnimMarche()
{
//tempo de l'anim
temp_anim_marche++;
if (temp_anim_marche == 6)
{
//relance la tempo
temp_anim_marche = 0;
//prochaine étape de l'anim
anim_marche++;
//affiche la position adequate
oslSetImageTileSize(chrono,(anim_marche * 22),chrono_position,22,35);
//relance l'anim
if (anim_marche == 6) anim_marche = 0;
}
}
3) Explications
#define BAS 0
#define HAUT 35
#define DROITE 70
#define GAUCHE 105
Ici je met en define 4 nombres. Mais pourquoi ceux là ? me demandrez vous... et pourquoi pas ? vous répondrais je :rolleyes:
Non sérieusement, il s'agit du Y correspondant à chacune des 4 directions de mon spriteset. Vous vous rappelez que mon cadre référence fait 35 de haut ? donc le Y de la 1ere direction est à 0, le 2ème à 35, etc...
Donc quand je dirai : positionne toi à GAUCHE, il saura que ca correspond au nombre 105, qui lui même correpond à la dernière direction de mon spriteset ;)
chrono_position = BAS;
Bon ben voila, on y est, intero surprise, après cette commande, à quoi est égal la variable chrono_position ?
Ceux qui ont répondu 67 vont au coin, et ceux qui ont répondu 0 ont le droit de continuer :P
(au fait, retenez qu'au début chrono_position = 0, ca facilitera une explication plus loin)
oslDrawGradientRect(0,0,480,272,RGB(0,128,0), RGB(0,128,0), RGB(128,255,128), RGB(128,255,128));
Dessine un beau dégradé à 4 points. Comme mes 2 premiers et mes 2 derniers points sont identiques, ici ce n'est qu'un dégradé à 2 points (vert foncé vers vert clair)
if (!osl_keys->held.value)
La valeur "value" de la structure osl_keys passe à 1 quand une touche est pressée ou maintenue (pressed ou held).
Donc il me suffit de tester si elle est à 0 pour savoir si le joueur n'appuie sur aucune touche ;)
oslSetImageTileSize(chrono,0,chrono_position,22,35 );
Bon, voici la commande d'oslib la plus importante à comprendre pour ce tuto.
En fait cette fonction permet d'afficher le morceau que l'on veut d'une image, prenons pour mieux comprendre la formulation générique :
oslSetImageTileSize(image,x,y,largeur,hauteur);
Vous vous rappelez notre spriteset ? sans cette commande, il s'afficherai entierement sur l'écran, ce serai pas cool admettez :P
Donc ici, on dit à la PSP : "affiche moi l'image certe, mais seulement le morceau qui commence au point x,y et qui mesure largeur sur hauteur"
Une fois ceci fait, à chaque appel de oslDrawImage(image), seul ce fameux morceau sera affiché.
Compris ?
Donc ici, je dit à la PSP : "affiche moi le spriteset de chrono, mais seulement le morceau qui commence à 0,chrono_position et qui fait 22 pixels de large sur 35 pixels de haut"
Un peu confus toujours ? bon, considérons que c'est la premiere execution et que chrono_position = 0 (bravo à ceux qui s'en sont rappelé, un bon point), la commande deviens :
"affiche moi le spriteset de chrono, mais seulement entre les points 0,0 et 22,35"
C'est plus clair là, non ? elles ne vous disent rien ces coordonnées ?
Hé oui, il s'agit de ma toute premiere étape, le 1er sprite du spriteset.
Ce sprite correspond donc bien à la position BAS.
Vous comprenez un peu mieux la ligne du code ?
Remplacez mentalement la valeur de chrono_position par la valeur correspondant à la position GAUCHE (105), vous pouvez savoir quelle sprite va s'afficher : le dernier de la 1ere colonne (0,105 - 22,140)
Comprennez bien, car la même ligne reviens, mais en un peu plus compliqué :P
temp_anim_marche++;
if (temp_anim_marche == 6)
{
temp_anim_marche = 0;
....
Ici je temporise un peu, sinon nous n'aurions pas le temps de voir les differentes étapes de l'animation. Il faut juste ne pas oublier de repasser la variable temp à 0 :)
oslSetImageTileSize(chrono,(anim_marche * 22),chrono_position,22,35);
Oui voila, je vous avais prévenus :lol:
Mais c'est pas si compliqué :
Observez bien le code : à chaque fois que la tempo est atteinte, on incrémente la variable anim_marche.
Considérons que nous sommes au début et qu'elle était à 0, donc elle est maintenant égale à 1.
Considérons également que chrono_position est toujours égal à 0 (BAS)
Remplacons maintenant toutes les variables par leurs valeurs, nous obtenons :
oslSetImageTileSize(chrono,22,0,22,35);
Ce qui correspond à demander à la PSP d'afficher le 2ème sprite de la 1ere ligne de mon spriteset, c'est à dire la 1ere étape de l'anim de marche vers l'avant !
On peut ainsi continuer mentalement d'incrementer anim_marche pour voir que l'on pointe à chaque fois sur l'étape suivante !
Même chose en changeant la valeur de chrono_position !
Regardez donc le code dans sa globalité : quand le joueur maintient la direction droite appuyée, on déplace le sprite vers la droite, puis on dit que chrono_position est maintenant égal à DROITE ( 3ème ligne d'anim du spriteset) et on lance l'anim.
Tant qu'il maintient la touche, la fonction AnimMarche est lancée à chaque frame, la variable anim_marche est incrémentée toutes les 6 frames, ce qui correspond à une nouvelle position du sprite à afficher. Et enfin quand la derniere étape est atteinte, on revient au début de l'anim et c'est reparti (if (anim_marche == 6) anim_marche = 0;)
Vous n'avez toujours rien compris ? c'est pas grave, remarquez les valeurs numériques de la commande, elles ne vous disent rien ?
Oui c'est ca, il s'agit des valeurs de mon cadre de référence (tout au début). Vous pouvez donc vous contentez de copier/coller le code et de remplacer les valeurs par les votres ;)
Pour les pros : On peut également utiliser oslSetImageTile(image,x0,y0,x1,y1) qui permet d'afficher un morceau de l'image de facon plus précise ;)
Il est possible aussi qu'au lieu d'avoir dans le spriteset les 2 directions droite et gauche, n'en avoir qu'une seule et effectuer un "miroir" sur le sprite au bon moment (oslMirrorImageH(image))
4) Screen et eboot
Voila, vous devez obtenir un truc comme ça :
http://hothmoon.free.fr/psp/dev/jour06_screen.png
Téléchargez l'eboot compilé pour 1.5 ici (http://hothmoon.free.fr/psp/dev/jour06_eboot.zip)