©C5Documentation de WinGraph (test de très long fichier)©C1

#A1#©G1©C+81WinGraph©G0©C1
©C3©I1Les graphismes qui déchirent enfin devenus accessibles à tous!©I0©C1
#A0#

©S1Introduction©S0

WinGraph est une librairie C qui a pour but de d'alléger la création d'applications graphiques sous Windows. WinGraph se présente comme la console que vous connaissez peut-être, mais permet de faire beaucoup plus de choses que cette dernière.
WinGraph peut compiler et exécuter la plupart des programmes standard ANSI pour autant que ceux-ci n'utilisent pas de librairie spéciale (ce qui inclut DosGraph).


©C4©S1Je veux commencer moi!©S0©C1

Voici les étapes à suivre pour créer votre premier programme en utilisant WinGraph:
-Installez-la (exécutez Install.bat)
-Démarrez Microsoft Visual Studio 6.0
-Dans le menu File, choisissez New.
-Choisissez "Win32 Application" (attention pas "Win32 Console Application"!)
-Choisissez ensuite "An empty project"
-Faites à nouveau File/New
-Choisissez C/C++ Header File
-Tapez le code suivant dans le nouveau fichier texte créé:
include <wingraph.h>

void main()		{
    printf("Hello world");
}
Exécutez-le maintenant (Ctrl+F5). Vous vous familiariserez ainsi avec l'environnement mis à disposition par WinGraph. Notez cependant que main est une fonction ne retournant rien et ne recevant aucun argument. Il n'est donc pas nécessaire de faire un return 0; à la fin.


Mode texte / Mode graphique

Le mode texte permet comme son nom l'indique d'écrire du texte, et d'en entrer au clavier; l'unité de base pour le positionnement est le caractère dans ce mode (un caractère=8x12 pixels). C'est le mode avec lequel vous travaillez avec la console.
Le mode graphique, lui, permet d'afficher des images et également du texte; cependant, ici l'unité de base est le pixel (donc plus petit et plus précis). C'est justement l'intérêt réel de WinGraph.
Rien ne vous empêche d'écrire entièrement un programme pour le mode texte et d'afficher une image graphique entre deux. A l'inverse également;
Cependant, la différence entre les deux modes est qu’en mode texte, l’écran est mis à jour en permanence. Cela signifie que si vous écrivez un texte avec printf par exemple, vous pouvez à partir de ce moment vous assurer que l’utilisateur le verra. Mais voilà le problème; imaginons que vous voulez redessiner votre écran. Le mieux est de tout effacer puis de recommencer à dessiner avec les nouvelles informations. Et justement, l’écran sera mis à jour pendant ce temps, et l’utilisateur verra, l’espace d’un court instant, un écran d’abord vide, puis les objets apparaître un à un sur celui-ci jusqu’à former l’image finale. Bien que ce temps soit minuscule, il donnera l’impression d’un clignotement très désagréable. C’est pourquoi il y a le mode graphique. Dans ce mode, l’écran n’est pas mis à jour en boucle, mais seulement lorsque vous lui en donnez l’ordre. En appliquant ceci à l’exemple précédent, vous effaceriez vos objets puis les redessineriez (l’utilisateur ne verrait rien durant ce temps-là) puis seulement lorsque vous auriez terminé intégralement l’affichage de l’image, vous diriez à l’écran «c’est bon, tu peux y aller maintenant!».
Cette méthode est aussi appelée Double Buffering. Le concept est le suivant: on a deux écrans; l’un des deux est l’écran réel (c’est celui que l’utilisateur voit) et l’autre est virtuel (il ne se voit pas); c’est sur l’écran virtuel qu’on effectue toutes les opérations, qui sont cachées à l’utilisateur tant qu’elles ne sont pas terminées, puis une fois qu’on a fini, on bascule entre l’écran affiché et l’écran virtuel, et on recommence sur l’écran qui est maintenant devenu virtuel pour afficher la prochaine image.


Fonctions du mode console

Toutes les fonctions standard n'ont pas été réimplémentées; mais les principales sont là:

Prototype de la fonction	Description
void printf("format", …)	Affiche une chaîne de caractère formatée à la position du curseur.
char *gets(char *chaine)	Lit un texte au clavier et le stocke dans la chaîne de caractère fournie. Retourne le pointeur sur la chaîne.
void MoveTo(int x,int y)	Place le curseur aux positions x,y. Ces positions sont indiquées au caractère près; par exemple 3,1 signifie 2ème ligne, 4ème caractère.
int getch()	Retourne la valeur du caractère appuyé.
int kbhit()	Retourne 0 s'il n'y a aucune touche appuyée, sinon renvoie la valeur de celle-ci. Attention: utilisez getch pour lire la touche.
void scanf("format",…)	Lit un entier %i, un réel %f ou %d, une chaîne %s ou un caractère %c. Cette fonction est limitée par rapport à l'originale, car elle ne supporte que ces formats, et qu'un seul à la fois; par exemple "%i %i" n'est pas autorisé.
void clrscr()	Efface l’écran et place le curseur en haut de celui-ci.
		
La fonction system n’est pas supportée, ainsi que toutes les fonctions liées à la console. Donc n’utilisez pas des fonctions du style SetConsole[…] ou GetConsole[…] car ça ne fonctionnera pas, mais cherchez plutôt les équivalents WinGraph (et vous verrez que c’est certainement encore mieux! :p)
Ces fonctions n’ont d’intérêt à être utilisées qu’en mode texte; même si les fonctions listées ci-dessus fonctionneront tout de même en mode graphique, vous devriez utiliser les fonctions adaptées.


Mais comment on fait un jeu alors?

C’est une bonne question, à laquelle je vous laisserai répondre par vous-même! Si je mets une librairie graphique à disposition dans le cadre d’un cours de conception de logiciel, le seul objectif est de ne pas vous freiner dans vos idées. Maintenant apprendre comment faire des jeux, c’est propre à chacun. Certes, la base pour faire tourner un jeu (quel qu’il soit) est universelle puisque les machines mettent à disposition des fonctions de base qu’il faudra utiliser. Passons en revue le squelette d’un jeu qui ne fait absolument rien pour le moment :
 
include <wingraph>

void main()	{
//Un jeu est toujours constitué d’une boucle, dans laquelle se
//déroulent tous les événements du jeu.
InitTimer();                  //Obligatoire pour pouvoir utiliser
                              //AttendSync et skip.
   while (!testTouche(T_ESCAPE))		{
      //Premièrement:  "Entrée"
      //Lire l'état du clavier et/ou du joystick. Cette opération est
      //très rapide et devrait prendre une place minuscule dans votre
      //code.
      if (testTouche(T_GAUCHE))
         […]

      //Deuxièmement:  "Gestion"
      //Mettre à jour les personnages, effectuer les actions qui y
      //sont liées, etc. (en rapport avec les touches dans un premier
      //temps, puis les opérations périodiques ensuite).
      […]

      //Troisièmement: "Sortie"
      //Effacer l'écran puis redessiner les décors puis les objets
      //selon leurs positions. Comme ces positions évoluent au fil du
      //temps et à chaque mise à jour de l'écran, cela crée ce que
      //l'on nomme une animation.
      //Pour le if (!skip), voyez la section "frameskip", plus bas.
      if (!skip)    {
          EffaceEcran();
          //Placez les opérations de dessin ici
          AfficheEcran();
      }
      AttendSync(10);             //On attend 10 millisecondes, ce qui 
                                  //donne 100 images/seconde.
   }
}

Comme pour tout programme informatique (pas seulement un jeu), on peut considérer qu'un programme est divisé en trois parties:
-L'entrée
-La gestion
-La sortie
L'entrée peut être un clavier, un joystick. Elle est indispensable, puisqu'elle permet de savoir ce que l'utilisateur nous veut. Sans entrée, un jeu est complètement inutile et n'en est pas un. (à la limite, on pourrait appeler ça du "demo making")
La gestion est en fait l'action de prendre en considération l'entrée, par exemple de modifier les coordonnées du personnage principal si le joueur appuie sur la touche directionnelle "droite". Cette partie est indispensable, sans quoi rien ne se ferait. Le seul nom qu'on pourrait donner à un jeu sans la partie gestion, c'est "un bô plantage", car c'est ce qui se passe si cette partie du jeu ne répond plus.
La sortie est ce que vous connaissez bien, à savoir l'affichage.
Note: L'entrée et la gestion sont très proches, d'ailleurs ces deux étapes sont souvent associées dans un jeu (exemple: si l'utilisateur appuie sur Start [partie entrée], alors je mets le jeu en pause [partie gestion]…). Cependant il y a vraiment lieu de faire une distinction de la taille d'un fossé entre l'affichage et la gestion et l'entrée.
Exemple: Afficher à l'écran le caractère que l'utilisateur vient d'entrer correspond à ces trois étapes:
-L'entrée (la lecture du caractère au clavier)
-La gestion (mais que fais-je de ce caractère? Bah je le stocke dans la variable c)
-La sortie (afficher le caractère lu). C'est la seule étape qui n'est pas obligatoire; il est inutile du point de vue de l'ordinateur d'afficher le caractère sur l'écran, puisque celui-ci est déjà stocké dans la variable c. Si le jeu a besoin du caractère, il va se référer à la variable c et non à ce qui se trouve sur l'écran. Cependant, l'utilisateur trouvera peut-être ça moins inutile de ne rien voir à l'écran. Consultez la section "Le frameskip et les timers" pour plus d'informations sur ce sujet.

 
Les couleurs

WinGraph vous permet d'utiliser 16'777'216 couleurs. C'est autre chose que les 16 couleurs primaires de la console, n'est-ce pas? Cependant, pour les utiliser correctement, vous pouvez soit utiliser les constantes définies dans le fichier "Couleurs.h" de WinGraph, soit les créer vous-même en utilisant le format RVB (rouge, vert, bleu). Allez, une petite explication s'impose, non?

<explication contenue dans le fichier "Coleurs.h">

 
Le frameskip et les timers

L'affichage est aujourd'hui la partie où un jeu passe à peu près 99% de son temps (en comparaison, la gestion est très rapide). C'est pourquoi on peut "sauter" (euh… dans le sens passer, ignorer, hein! :D) la partie affichage de temps en temps si le jeu commence à ramer. Cela donnera l'impression de saccader un peu mais (si tout va bien) ce ne sera pas nocif pour le jeu, puisque tout ce dont il a besoin pour avancer est censé se trouver dans la partie gestion. On pourrait aussi laisser aller, et là le jeu ralentirait, puisque la fonction de gestion ne serait pas exécutée assez souvent. C'est ce qui arrivera si vous mettez dans la partie affichage du code qui devrait se trouver dans la partie gestion.
Pour faire simple: ne modifiez jamais le contenu d'une variable du jeu (toute information liée aux persos, aux décors, aux objets) dans la partie affichage. Contentez-vous d'en lire le contenu et de l'afficher à l'écran. Toute modification aux variables doit être effectué dans la partie gestion, sinon vous risquez d'avoir des problèmes…
Pour tester si tout a bien fonctionné, essayez de faire deux ou trois fois la partie affichage à la suite. Et là vous verrez: s'il y a des objets qui bougent plus rapidement que d'autres, c'est que vous vous êtes trompé.
Grâce au système de timers de WinGraph, vous pouvez être informé si le jeu est en train de ramer. En faisant:
AttendSync(durée);
Cette fonction mettra la variable globale skip à FAUX (0, FALSE) ou à VRAI (1, TRUE). Il s'agit d'une variable globale de WinGraph, donc vous n'avez pas besoin de (et ne devez pas) la déclarer vous-même. Le principe est simple: si cette variable est à la valeur FAUX, alors le jeu se déroulera normalement, mais si elle vaut VRAI, alors vous devrez ignorer toute la partie affichage. Et c'est là que c'est important qu'il n'y ait aucun code de gestion dans la partie affichage, car cette partie sera ignorée, et s'il y a des informations vitales pour le jeu, celui-ci ne fonctionnera pas comme il faut si la gestion est exécutée plusieurs fois sans qu'il y ait d'affichage.
Prenons un exemple: vous faites bouger Link (votre personnage principal) sur votre écran, ce déplacement est effectué dans la partie gestion, comme il faut. Maintenant prenons que vous bougez ses ennemis dans la partie affichage. Tant que le jeu ne ramera pas, ça fonctionnera sans problème, et vous ne vous en rendrez même pas compte. Mais lorsque ça commencera à ramer, vous allez sauter la partie affichage, et donc vos ennemis ne seront pas déplacés durant ce temps-là (par contre Link oui puisqu'il est dans la partie gestion, qui elle, n'est sautée en aucun cas). En fait, Link ira à la bonne vitesse grâce au frameskip, alors que les ennemis seront terriblement ralentis. Vous comprenez maintenant?
Si vous ne planifiez pas d'utiliser le frameskip, indiquez-le à WinGraph en appelant la fonction AttenteMinimale() juste après InitTimer(). WinGraph ne mettra jamais skip à VRAI, mais vos jeux ralentiront s'ils en demandent trop à la machine.
Une dernière chose, même si vous ne pensez jamais utiliser de framskip, distinguez toujours la partie gestion et affichage, c'est une bonne habitude à prendre et c'est très important pour ne pas avoir de surprise par la suite. Par exemple quand vous êtes sous Windows, que l'utilisateur déplace une fenêtre et que celle-ci passe par-dessus la votre, il faudra la redessiner, et, suivant comment, cela peut se dérouler plus de 100 fois par seconde. Si vous mettez des parties gestion dans la partie affichage, ces parties du jeu iront beaucoup plus vite dans ces cas-là.
Dans WinGraph: Tout ce que vous avez à faire, c'est englober la partie affichage dans un if (!skip) comme montré plus haut. N'oubliez pas de faire InitTimer() avant de rentrer dans la boucle, sinon AttendSync n'attendra pas.
Note: Faites également un ReinitTimer() avant de rentrer dans chaque boucle (sauf la première fois où vous utiliserez InitTimer), car imaginons qu'entre la première boucle (celle de l'écran titre) et la deuxième (celle du jeu) il y ait un effet de transition qui dure 2 secondes. La dernière fois que vous avez appelé AttendSync était donc 2 secondes avant. Donc quand vous rentrerez dans la prochaine boucle, AttendSync croira qu'il a pris 2 secondes de retard et vous demandera de les rattraper en mettant skip à VRAI. Résultat: les deux premières secondes de votre jeu ne seront jamais vues, et vous ne comprendrez pas pourquoi. Mais si vous faites ReinitTimer avant de rentrer dans la boucle du jeu, AttendSync sera informé qu'il faut oublier ce qui s'est passé avant et ne mesurer que le temps écoulé depuis là.
Référez-vous aux exemples fournis avec WinGraph pour voir une application concrète de cette fonctionnalité. Maintenant voici un descriptif des fonctions liées aux timers:

Prototype de la fonction	Description
void InitTimer()	Initialise le système de timers. C'est indispensable car cette fonction demande à votre processeur la fréquence de son timer interne. Si vous ignorez cette partie, il sera impossible de mesurer le temps. (fréquence de 0 Hz)
void ReinitTimer()	Remet le compteur à zéro. Le retard accumulé est également annulé.
void AttenteASynchrone()	Se met en mode d'attente asynchrone; le frameskip est signalé uniquement dès que le retard est trop important. Le nombre d'images/seconde est toujours maximal, mais un effet de saccade accru peut apparaître si votre fréquence est basse puisque ce sera irrégulier (par exemple 72 images/seconde implique une acrobatie de la part de votre écran qui fait par exemple du 100 Hz).
void AttenteSynchrone()	Se met en mode d'attente synchrone; le frameskip est signalé dès qu'il y a le moindre retard. Le nombre d'images/seconde est ainsi assez bas, mais il est toujours un diviseur entier de celui prévu. Exemple: vous attendez 10 ms, vous voudriez donc 100 images/seconde. Si ça commence à ramer, vous passerez directement à 50 images/seconde.Dans ce mode, le jeu reste fluide puisque le nombre d'images/seconde est régulier et diviseur entier de la fréquence voulue.
void AttenteMinimale()	Désactive le frameskip. Le jeu ne dépassera pas la fréquence voulue (attente dans ce cas), par contre s'il rame, alors WinGraph n'attendra pas, mais ça ralentira.
void AttendSync(int temps)	Attend temps millisecondes en utilisant le mode d'attente défini plus haut. Cette fonction ne fera absolument rien si InitTimer n'a pas été préalablement appelé.

Note: Le mode AttenteMinimale est conseillé si vous ne voulez pas vous embêter (pour votre premier programme par exemple). Le mode AttenteASynchrone est conseillé dans des applications du style jeux en 3d et jeux de bagnoles, car la fluidité n'est pas très importante mais un bon "framerate" oui (taux d'images/seconde), ou bien un flipper, alors que le mode AttenteSynchrone est conseillé dans des jeu en "side-scrolling" du style Sonic, Mario, etc. car si le défilement n'est pas fluide, la qualité du jeu en prendra un coup. Mais bon, la meilleure solution reste de tester tout ça soi-même hein! ;)
