Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.
La shoutbox n'est pas chargée par défaut pour des raisons de performances. Cliquez pour charger.

Forum Casio - Actualités


Index du Forum » Actualités » TDM #21 : Les animations et les structures de données
Potter360 Hors ligne Rédacteur Points: 1255 Défis: 2 Message

TDM #21 : Les animations et les structures de données

Posté le 17/02/2021 17:58

Le Tutoriel du Mercredi (TDM) est une idée proposée par Ne0tux, qui recouvre tous les usages de la calculatrice - des applications de Casio à la conception de jeux en passant par la production artistique.

Aujourd'hui, nous allons voir les animations en C/gint et leur rapport avec les structures de données

Niveau : ★★★★☆
Tags : Animations, gint, POO

Bonjour et bienvenue dans ce TDM #21 !

Aujourd'hui nous allons parler des animations en C pour calculatrices et de comment les créer avec les structures !
Je précise que ce tutoriel contient des codes en C pour gint de Lephenixnoir, mais le code doit être facilement convertible pour le SDK "officiel" avec un poil de jugeote !

Voici le résultat final :



Durant ce tutoriel, j'utiliserai les frames suivants :


Frame idle



Frame de marche


Sommaire :

1) Qu'est ce qu'une animation ?
1.1) La boucle principale d'un jeu vidéo

2) Animation et boucle principale
2.1) Premières idées
2.2) L'utilité du sinon
2.3) L'apparition de la variable "frame suivant"

3) La POO (Programmation Orientée Objet)
3.1) Definition
3.2) L'exemple du catalogue d'animaux

4) S'inspirer de la POO pour les animations
4.1) Premières idées
4.2) Détection du clavier

5) Les structures de données en C
5.1) Equivalent des classes
5.2) Equivalent des objets
5.3) Affichage
5.4) Lire les entrées du joueur
5.5) Continuer l'animation précédente
5.6) Le code entier

6) Conclusion

7) Liens utiles


Commençons sans plus tarder par des révisions :

Qu'est ce qu'une animation ?

La question paraît bête : oui, je sais ce qu'est une animation : c'est une suite d'images passées très vite qui donne une impression de mouvement !

Oui, effectivement, bravo !
Mais dans un jeu vidéo ?

La boucle principale d'un jeu vidéo

Vous savez peut être qu'un jeu vidéo est, en général, constitué d'une boucle principale. Par exemple, pour détecter si l'utilisateur appuie sur la touche [EXE], il va y avoir une boucle principale, dans laquelle le jeu détecte si l'utilisateur appuie sur la touche. En algorithmique ça donne ca :

Répéter ∞ fois {
     Si touche [EXE] est pressée : {
          //faire l'action
     }
}

et, en C/gint on a approximativement ca :

int main void(){
    while(True){
        if(getkey().key == KEY_EXE){
             //faire l'action    
        }
    }
}


Mais...attendez...une boucle qui change selon l'environnement et qui va très vite...ça ne vous rappelle pas quelque chose ?

Oui, les animations !

Animations et boucle principale

Premières idées

Prenons par exemple une "boucle" d'animation (une animation qui se répète toutes les 3 images par exemple).
On pourrait organiser un truc comme ça : on crée une boucle, et les images s'affichent chacune une répétition de la boucle sur 3.
On pourrait alors imaginer un truc du genre :

A la première répétition : afficher image1
A la deuxième répétition : afficher image2
A la troisième répétition : afficher image3

Puis on recommence au début !

C'est un bon moyen de faire une animation, mais comment la réaliser ?
Avec une variable, du genre :

Nouvelle variable nommée "a" = 1

Répéter ∞ fois : {
     si a = 1 alors afficher la première image
     si a = 2 alors afficher la deuxième image
     si a = 3 alors afficher la troisième image

     si a = 1 alors mettre la variable "a" à 2
    sinon si a = 2 alors mettre la variable "a" à 3
    sinon si a = 3 alors mettre la variable "a" à 1


}

?

En soi cela marche mais ce code est long et n'est pas très lisible, il faut de la concentration pour le comprendre.


Et puis, imaginez que vous vous retrouvez avec une animation contenant 3 images (que je vais appeler dorénavant des "frames"), le code serait complexe et illisible : on appelle cela un code spaghetti

Voici la représentation mathématique d'un code spaghetti :


C'est pas beau à voir, hein ?.
Le second problème, c'est que tous les "si ... alors ... sinon ..." sont placés dans le sinon du premier "si ... alors ... sinon ...", ce qui pose des problèmes : si le premier "si ... alors ... sinon ..." a un problème faisant que le "sinon" ne sera jamais exécuté, tout le reste est à l'arrêt.

On appelle cela les dépendances : certaines lignes de code sont dépendantes d'autres lignes, et cela peut poser problème, et peut créer l'effet domino : en effet, imaginons que dans un code toutes les lignes sont dépendantes (ce qui est très peu probable), un problème qui ne concerne que la ligne 1 va arrêter la ligne 2 car elle est dépendante de la ligne 1, la ligne 2 va arrêter la ligne 3, la 3 va arrêter la 4 etc...

Mais alors, pourquoi mettre tout le code dans des "sinon" ?

Très bonne question.

L'utilité du "sinon"

Pour y répondre je vais prendre un exemple de code en algorithmique que j'appelle " l'interrupteur " : un texte est affiché à l'écran, "1" ou "0", et quand l'utilisateur appuie sur une touche, disons [EXE], l'état du texte change : si "0" était affiché on affiche maintenant "1" et vice-versa.
Cela donne :

Répéter ∞ fois : {
     Afficher la variable "a"
     Si la touche EXE est pressée alors :
          Si la variable "a" = 0 alors mettre la variable "a" à 1
          Sinon mettre la variable "a" à 0
}

Le sinon est indispensable : réfléchissons. Imaginons que l'on ne le mette pas, on a ça :

Répéter ∞ fois : {
     Afficher la variable "a"
     Si la touche EXE est pressée alors :
          Si la variable "a" = 0 alors mettre la variable "a" à 1
          Si la variable "a" = 1 alors mettre la variable "a" à 0
}

La suite imagine que a = 0 au départ : lorsque EXE est pressé on a cette ligne : Si la variable "a" = 0 alors mettre la variable "a" à 1
Or a = 0 donc ce if s'exécute et a = 1.
Ensuite : Si la variable "a" = 1 alors mettre la variable "a" à 0
Depuis la ligne précédente a = 1 donc ce if s'exécute donc a = 0.
Au final, a n'a pas changé, le code ne marche pas.
Le sinon ne vérifie pas la condition avant de s'exécuter mais avant d'exécuter le if, donc ici cela marche.

Attends... j'ai une idée... pourquoi ne pas créer une variable framesuivant pour chaque image ? Et dans le code on dit : affiche le frame actuel et met le frame actuel au frame suivant !

Effectivement, c'est la solution, mais... comment faire ?

L'apparition de la variable "frame suivant"

Les variables sont multipliées par 2 ; pour 3 frames on a ces variables : frame1 ; frame1next ; frame2 ; frame2next ; frame3 ; frame3next.
Et cela en fait 6 !
Encore là, c'est acceptable, mais imaginez une animation de 60 frames : on a 120 variables !
Et si en plus chaque frame a une position x et une position y, ce qui est plus propre, chaque frame a 4 variables ; pour le frame 1 ces variable sont : frame1 (l'image) , frame1next, frame1posx , frame1posy.
Pour une animation de 60 frames, cela représente tout de même 240 variables !
Cela impacte en premier lieu le développeur, qui ne s'y retrouvera pas dans ses variables, mais aussi la machine, car les variables prennent de la RAM, et l'utilisateur, qui va voir le programme ralenti.

Ha, j'ai une idée ! Et si on... heu... ha non...heu...


Bon, j'ai la solution, ne cherchez pas.
Et si on s'inspirait de...

La POO (Programmation Orientée Objet)

On m'informe dans l'oreillette que j'ai dit un mot compliqué...
Ne vous inquiétez pas, je vais expliquer.
Je demande juste aux programmateurs pointilleux de ne pas me cracher dessus : je vais résumer un max.

Résumons... nous aurions besoin de plusieurs variables par frames...

Définition

La programmation orientée objet est un style d'écriture du code, et Wikipédia dit ici que :
Dans ce style, le code source est une suite de descriptions de classes ou de prototypes, avec la description de leurs caractéristiques ( propriétés ) et de leurs comportements ( méthodes ).


Ne paniquez pas.
En POO, on crée des objets, et les variables ont des "états" en fonction de ces objets.

L'exemple du catalogue d'animaux

Prenons un exemple : je veux faire un programme simple dans un langage quelconque qui demande à l'utilisateur le nom de l'animal et la caractéristique, et le programme donne le résultat (Exemple : le programme demande le nom de l'animal, l'utilisateur répond "Chien", il demande la caractéristique, l'utilisateur rentre "Cri" et le programme renvoie "Wouf !").
Pour cela on pourrait faire un truc du genre : (en algorithmique)

Afficher : "Quel animal ?"
Attendre la saisie de l'utilisateur et la stocker dans la variable "animal"
Afficher : "Quelle caractéristique ?"
Attendre la saisie de l'utilisateur et la stocker dans la variable "caractéristique"
Si animal = "chien" : {
     Si caractéristique = "Cri" : {
          Afficher "Wouf !"
     }
     Si caractéristique = "Poids" : {
          Afficher "Le poids moyen se situe entre 15 et 25 kg."
     }
Si animal = chat : {
     Si caractéristique = "Cri" : {
          Afficher "Miaou !"
     }
     Si caractéristique = "Poids" : {
          Afficher "Le poids moyen se situe entre 4 et 6 kg."
     }
}

Vous le voyez, ce code est long et il faut du temps pour le comprendre.
L'inventeur de la POO a trouvé ce problème embarrassant, et voici sa solution : on crée deux variables : Cri et Poids.
On crée deux "objets" : chat et chien, et pour chaque objet une "version" différente de la variable est crée : par exemple il existe deux versions de la variable Cri : celle du chat et celle du chien.

Voici une partie du code amélioré avec la POO :

Nouvelle variable "Cri"
Nouvelle variable "Poids"
Nouvel objet "Chien" : {
    Sa version de "Cri" = "Wouf !"
    Sa version de "Poids" = 20
}
Nouvel objet "Chat" : {
    Sa version de "Cri" = "Miaou !"
    Sa version de "Poids" = 4
}

Oui, c'est perturbant mais vous allez vous y faire.
Tous mes objets sont des animaux, mais imaginons que je veuille créer un objet "fourmi", pour laquelle je veux enlever la variable "cri".

Facile : on ajoute au code précédent :
Nouvel objet "Fourmi" : {
    Sa version de "Poids" = "150 milligrammes"
}
!


En soi oui. Mais la programmation est pleine de règles, et on ne peut pas choisir de mettre ou non une variable. C'est un peu comme pour un livre : si tu écris un roman mais que tu ne trouves pas de nom, tu ne peux pas décider de ne pas en mettre.

Il va falloir créer des "types" d'objets. On va par exemple créer le type "Animal", dans lequel on va créer les variables "Poids" et "Cri", et créer le type "Insecte" dans lequel on va créer la variable "poids" ; en programmation on appelle ces types des "classes".
On peut alors obtenir un truc du genre :

Nouvelle classe "Animal" : {
    Elle contient la nouvelle variable "Cri"
    Elle contient la nouvelle variable "Poids"
}
Nouvelle classe "Insecte" : {
    Elle contient la nouvelle variable "Poids"
}
Nouvel objet "Chien" de type "Animal" : {
    Sa version de "Cri" = "Wouf !"
    Sa version de "Poids" = 20
}
Nouvel objet "Chat" de type "Animal" : {
    Sa version de "Cri" = "Miaou !"
    Sa version de "Poids" = 4
}
Nouvel objet "Fourmi" de type Insecte : {
    Sa version de "Poids" = "150 milligrammes"
}

On touche au but ! Mais, pour les entrées de l'utilisateur ?
En POO, on peut récupérer la "version" d'une variable via cette syntaxe : (la plus courante)
[Nom_de_l'objet].[nom_de_la_variable]
Par exemple, dans notre cas, écrire dans mon programme Afficher Fourmi.Poids affichera "150 milligrammes", ou écrire Afficher Chien.Cri affichera "Wouf !".
On pourrait donc faire ca :

Afficher : "Quel animal ?"
Attendre la saisie de l'utilisateur et la stocker dans la variable "animal"
Afficher : "Quelle caractéristique ?"
Attendre la saisie de l'utilisateur et la stocker dans la variable "caractéristique"
Nouvelle classe "Animal" : {
    Elle contient la nouvelle variable "Cri"
    Elle contient la nouvelle variable "Poids"
}
Nouvelle classe "Insecte" : {
    Elle contient la nouvelle variable "Poids"
}
Nouvel objet "Chien" de type "Animal" : {
    Sa version de "Cri" = "Wouf !"
    Sa version de "Poids" = 20
}
Nouvel objet "Chat" de type "Animal" : {
    Sa version de "Cri" = "Miaou !"
    Sa version de "Poids" = 4
}
Nouvel objet "Fourmi" de type Insecte : {
    Sa version de "Poids" = "150 milligrammes"
}
Afficher Animal.Caractéristique

Et on arrive au résultat souhaité !

Mais les animations dans tout ca ?
Ca arrive...

S'inspirer de la POO pour les animations

Premières idées

On pourrait par exemple faire un truc comme cela pour relier les 2 sujets...

Nouvelle classe "frames" : {
    Nouvelle variable "Image"
    Nouvelle variable "Nextframe"
    Nouvelle variable "Posx"
    Nouvelle variable "Posy"
}
Nouvel objet "Frame1" de type "frames" : {
    image = image_frame1
    nextframe = "Frame2"
    Posx = 1
    Posy = 10
}
Nouvel objet "Frame2" de type "frames" : {
    image = image_frame2
    nextframe = "Frame3"
    Posx = 1
    Posy = 10
}
Nouvel objet "Frame3" de type "frames" : {
    image = image_frame3
    nextframe = "Frame1"
    Posx = 1
    Posy = 10
}
Nouvelle variable "FrameActuel" = Frame1
Répéter ∞ fois : {
   Afficher FrameActuel.image
   Attendre 25 ms
   FrameActuel = FrameActuel.nextframe
}

On commence à y arriver !
Ici, on a une animation qui fonctionne, mais qui ne s'arrête pas.

Détection du clavier

Il faudrait créer une variable de type boolean isAnimated, la mettre à true si on appuie sur le bouton permettant d'avancer, et l'animation se fait seulement si isAnimated est à true.
Et pour savoir quand l'animation est finie, on crée une variable AnimateTime, sorte de décompte avant la fin du frame.
On a ce code :

Nouvelle classe "frames" : {
    Nouvelle variable "Image"
    Nouvelle variable "Nextframe"
    Nouvelle variable "Posx"
    Nouvelle variable "Posy"
}
Nouvel objet "Frame1" de type "frames" : {
    image = image_frame1
    nextframe = "Frame2"
    Posx = 1
    Posy = 10
}
Nouvel objet "Frame2" de type "frames" : {
    image = image_frame2
    nextframe = "Frame3"
    Posx = 1
    Posy = 10
}
Nouvel objet "Frame3" de type "frames" : {
    image = image_frame3
    nextframe = "Frame1"
    Posx = 1
    Posy = 10
}
Nouvelle variable "FrameActuel" = Frame1
Répéter ∞ fois : {
   Si touche avancer est pressée : {
        Mettre isAnimated à true
        Mettre AnimateTime à 3
        //3 car il y a 3 frames avant la fin de l'animation
   }
   Si isAnimated = true : {
        Afficher FrameActuel.image
        Attendre 25 ms
        FrameActuel = FrameActuel.nextframe
        Enlever 1 à AnimateTime
        Si AnimateTime = 0 : {
             //l'animation est terminée
             Mettre isAnimated à false
        }
   }
   Sinon : {
        //Idle est l'image de la position inactive
        Afficher Idle
   }
}

Bon, on a la base, maintenant...

Les structures de données en C

Bon, super, nous avons le code en algorithmique !
Mais je vous rappelle que gint, lui, est en C !
Alors comment passer de l'un à l'autre ?

Equivalent des classes

En C, la POO n'existe pas , mais il existe une solution similaire : les "structures".
Pour définir l'équivalent d'une classe, rien de plus simple :

struct name_of_class {
    int variable1;
    long variable2;

}


Equivalent des objets

Pour définir l'équivalent d'un objet, c'est plus compliqué. Je vais d'abord donner la syntaxe d'un objet faisant partie de la classe "name_of_class" crée précédemment :

struct  name_of_class name_of_object[2] {
     {12,14}
    //Dans l'exemple précédent, la valeur de la variable variable1 pour l'objet name_of_object[1] est 12 et la valeur de la variable variable2 pour l'objet name_of_object[1] est 14  
    {20,41}
}

Alors, je sais que c'est difficile à comprendre, mais chaque "objet" créé est une liste d'"objets".
Mais en soi cela peut être une bonne idée : on crée une "liste d'objets" pour chaque animations, et chaque objet est un frame.
Concrètement, voyons le code de la structure anim, je vous le donne et vous explique après :
struct anim {
     bopti_image_t *img;
     int duration;
     struct anim *next;
};

La 1ère variable, bopti_image_t *img; est la variable contenant l'image.
La 2ème, duration, contient le nombre de frames sur lesquels l'image va rester avant de passer à une autre animation
La 3ème, struct anim *next;, est un pointeur.
C'est une variable qui envoie vers une autre variable. Ici, elle contient le frame suivant, qui est aussi de type struct.
Voici donc ce qu'on obtient pour l'animation de marche :

struct anim anim_walk[2] = {
    { &img_personnagemarche, 3 , &anim_walk[1] },
    { &img_personnage, 3, &anim_walk[0] },
};

Pour la facilité du code on va ajouter une animation idle :

struct anim anim_idle[1] = {
    { &img_personnage, 1, &anim_idle[0] },
};

Et voici finalement tout le code des structures, à mettre avant int main(void) :

struct anim {
     bopti_image_t *img;
     int duration;
     struct anim *next;
};

struct anim anim_idle[2] = {
    { &img_personnage, 40, &anim_idle[1] },
    { &img_personnage2, 40, &anim_idle[0] },
};
struct anim anim_walk[2] = {
    { &img_personnagemarche, 3 , &anim_walk[1] },
    { &img_personnage, 3, &anim_walk[0] },
};


Pour créer la variable current_anim, sachant que celle ci est un pointeur, il faut faire :

struct anim *current_anim = &anim_idle[0];

&anim_idle[0] est l'animation par défaut, sachant qu'en arrivant sur l'add-in le personnage est normalement en position idle.

Et voici toutes les variables à définir avant la boucle :

struct anim *current_anim = &anim_idle[0];
int current_anim_time_left = 0;
extern bopti_image_t img_personnage;
extern bopti_image_t img_personnagemarche;
//État du personnage : 0=arrêté, 1=marche
int state = 0;
//État du personnage au frame précédent
int previous_state = 0;
    


Affichage
Pour l'affichage de notre animation, rien de compliqué : on affiche aux positions x et y de current_anim l'image de current_anim comme ceci :

dclear(C_WHITE);
dimage(current_player->x,current_player->y, current_anim->img);
dupdate();


Lire les entrées du joueur
Ensuite on va lire les entrées du joueur ; et pour cela nous allons utiliser keydown qui nécessite un clearevents(); qui va effacer les évènements précédents.
Lors de l'appui sur une touche il va falloir changer d'animation : pour faire cela proprement, on va créer 2 variables : state, qui va contenir l'état de notre personnage (si la touche permettant d'avancer est pressée, state = 1) et previous_state, qui contient le state du frame précédent.
Si state = 1 mais que previous_state = 0, on vient de commencer à marcher, et on peut donc passer à l'animation de marche, et vice-versa.
Ca donne : (entrées + test des variables state et previous_state)

clearevents();
state = 0;
if(keydown(KEY_RIGHT))
      state = 1;
if(previous_state == 0 && state == 1)
{
      //On vient de commencer à marcher
      current_anim = &anim_walk[0];
      current_anim_time_left = current_anim->duration;
}
else if(previous_state == 1 && state == 0)
{
      // On vient de s'arrêter
      current_anim = &anim_idle[0];
      current_anim_time_left = current_anim->duration;
}


Continuer l'animation précédente
Bon, ca c'est si on vient de commencer à marcher, ou de s'arrêter, mais si il n'y a aucun changement ?
Et bien, on enlève 1 à current_anim_time_left et, si il est égal à 0, on met current_frame au frame suivant
et current_current_anim_time_left à current_anim_time_left = current_anim->duration !
Voici le code : (à placer après le bout de code précédent, le else correspond à " if(previous_state == 0 && state == 1) ")

else
{
    //On continue l'anim précédente
    current_anim_time_left--;
    if(current_anim_time_left <= 0)
    {
        current_anim = current_anim->next;
        current_anim_time_left = current_anim->duration;
    }
}

Puis, si state = 1, on ajoute 1 à x pour que le personnage avance comme ceci :

if(state == 1)
     {
                x = x+1;
     }  

Enfin, on met le délai d'attente, puis on met previous_state à state :

//Délai
sleep_us(25000);
// Préparation des invariants du frame suivant
previous_state = state;

Et voilà !

Le code entier

Voici le code entier :

#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/clock.h>
extern bopti_image_t img_personnage;
extern bopti_image_t img_personnagemarche;

struct anim {
     bopti_image_t *img;
     int duration;
     struct anim *next;
};

struct anim anim_idle[2] = {
    { &img_personnage, 40, &anim_idle[1] },
    { &img_personnage2, 40, &anim_idle[0] },
};
struct anim anim_walk[2] = {
    { &img_personnagemarche, 3 , &anim_walk[1] },
    { &img_personnage, 3, &anim_walk[0] },
};
int main(void)
{
    struct anim *current_anim = &anim_idle[0];
    int current_anim_time_left = 0;
    extern bopti_image_t img_personnage;
    extern bopti_image_t img_personnagemarche;


    //État du personnage : 0=arrêté, 1=marche
    int state = 0;
    // État du personnage au frame précédent
    int previous_state = 0;

    while(a != 1)
    {
        //Affichage
        dclear(C_WHITE);
        dimage(current_player->x,current_player->y, current_anim->img);

        dupdate();
        //Lecture des entrées ; si on n'appuie sur rien, state=0
        clearevents();
        state = 0;


        if(keydown(KEY_RIGHT))
            state = 1;

        //Exécution des animations

        if((previous_state == 0 && state == 1))
        {
            //On vient de commencer à marcher
            current_anim = &anim_walk[0];
            current_anim_time_left = current_anim->duration;
        }
        else if(previous_state == 1 && state == 0)
        {
            //On vient de s'arrêter
            current_anim = &anim_idle[0];
            current_anim_time_left = current_anim->duration;
        }
        else
        {
            //On continue l'anim précédente
            current_anim_time_left--;
            if(current_anim_time_left <= 0)
            {
                current_anim = current_anim->next;
                current_anim_time_left = current_anim->duration;
            }
        }

        //Simulation du monde
              
        if(state == 1)
        {

          xref = xref-1;
        
        
        }    

        //Délai
        sleep_us(25000);

        //Préparation des invariants du frame suivant
        previous_state = state;
    }

    getkey();
    return 1;
}

On a à présent une belle animation, celle ci normalement :



Conclusion

Vous avez appris dans ce tutoriel les bases des animations, la POO et son équivalent en C, les structures de donnés et comment utiliser ces dernières en C/gint !

J'espère que ce TDM vous aura aidé dans la réalisation de vos animations, j'ai été très content de le faire !

Merci à Lephenixnoir et à Dark storm pour leur aide qui m'a été précieuse !

Si vous avez des questions, lâchez vous en commentaires !
Liens utiles

Voir le TDM précédent : Comprendre et utiliser le path sous Linux

Consulter l'ensemble des TDM


Potter360 Hors ligne Rédacteur Points: 1255 Défis: 2 Message

Citer : Posté le 19/02/2021 15:34 | #


Quelqu'un pourrait il rajouter ce TDM à la liste des TDM ?
Globalement, coder. Mal, mais coder.
Lephenixnoir En ligne Administrateur Points: 24699 Défis: 170 Message

Citer : Posté le 19/02/2021 15:36 | #


Ah, oui, j'oublie à chaque fois ! C'est fait

D'ailleurs on n'en a pas reparlé parce qu'on a surtout commenté sur ton post original, mais merci pour ce tuto. C'est bien détaillé et illustré
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Potter360 Hors ligne Rédacteur Points: 1255 Défis: 2 Message

Citer : Posté le 19/02/2021 15:43 | #


Merci beaucoup !
Globalement, coder. Mal, mais coder.
Dark storm En ligne Labélisateur Points: 11641 Défis: 176 Message

Citer : Posté le 19/02/2021 15:46 | #


D'ailleurs, maintenant que tu es rédacteur, tu peux réupload les images sur le site, plutôt que de les laisser sur imgur où il y a une chance non-négligeable qu'elles soient supprimées avant que ce topic ne disparaisse
Finir est souvent bien plus difficile que commencer. — Jack Beauregard

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v4.3 © créé par Neuronix et Muelsaco 2004 - 2024 | Il y a 69 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd