ProgrammationDC

  

 

 

 

Conseils pour bien programmer un jeu vidéo par Julien // Mai 2003

Conseils pour bien programmer un jeu vidéo par Julien // Mai 2003


Ce document a pour but de vous aider à programmer de la meilleure façon un jeu vidéo. Je parlerai ici du développement d'un jeu en langage c, mais certains conseils restent valable pour les autres langages.


Note légale


Ce document est le fruit de l'apprentissage de mes études, de mes expériences en programmation et de la lecture de sujets divers sur internet. Il n'engage que moi et vous utilisez son contenu à vos risques et périls. Aucun lien vers quelque chose d'illégal ne sera donné ici. Ce document est librement distribuable. Toutefois, si vous utilisez une partie ou l'ensemble du document, merci de me le signaler et de ne pas oublier de me citer.


Introduction


Dans ce document, je vais essayer de vous donner quelques conseils d'ingénieur pour vous aider à bien programmer votre jeu vidéo. Bien sur, c'est un sujet difficile car chacun a sa façon de voir les choses. Surtout ne me croyez pas prétentieux, je souhaite juste vous transmettre un peu de mon expérience. Ce ne sont que des astuces.

Pourquoi essayer de bien programmer son jeu ? Par exemple, imaginons que vous êtes impatient de tester une nouvelle librairie permettant d'afficher une image avec un effet. Vous écrivez le code sans trop essayer de le structurer, avec des ajouts dans la fonction principale, dans les fonctions intermédiaires... au bout d'une heure de tests et de debuggage, tout fonctionne correctement, mais il y a du code partout. Il est alors très difficile de rendre son programme structuré et propre, car vous allez devoir déclarer des variables ailleurs, etc... et ce sera source de nouvelles erreurs, vous aurez alors envie de laisser tout comme ça... Le bon reflexe à avoir est de bien tout préparer, et ainsi, on gagne du temps.

Un code bien structuré permet :
- de comprendre son application et de la faire évoluer : si une fonction regroupe la partie de l'affichage, avec des sous fonctions, il sera plus facile d'evoluer pour une nouvelle version. De plus, si quelqu'un veut reprendre votre travail, il aura + de facilité à le comprendre.
- de corriger plus facilement les bugs
- de travail en équipe


I- Une fonction principale structurée et peu chargée


Proposition :

 

int main(void)
{
initSDL();
initJeu();
while (endJeu == 0){
entree();
modif();
affiche();
}
fermeture();
fermetureSDL();
return 0;
}


Explication : c'est une fonction principale de base. Grace à elle, on comprend tout de suite ce que le programme fait :

int main(void) : déclaration de la fonction principale

initSDL(); : fonction dans laquelle on initialise tout ce dont a besoin la librairie SDL (SDL_Init, SDL_SetVideoMode,...)

initJeu(); : fonction dans laquelle on initialise tout ce dont on a besoin pour le jeu (chargement des images, variables globales...)

while (endJeu == 0){ : boucle principale, tant qu'une fonction ne met pas la variable globale endJeu à 1, le jeu continue

fermeture(); : fonction dans laquelle on finit proprement le jeu, par exemple, on libere de la mémoire. fermetureSDL(); : fonction dans laquelle on quitte la librairie. Pour SDL, on appelle la fonction SDL_quit()

return 0; : le programme se termine


II- Diviser son programme dans plusieurs fichiers


Le langage c permet de travailler avec plusieurs fichiers. Attention, ce qui suit peut paraitre assez complexe : mais le gain de temps par la suite justifie l'effort à fournir. Par exemple, vous pouvez creer un fichier graphisme.c où l'on mettra la fonction affiche(); et ses sous fonctions, puis le fichier controlleur.c où sera écrite la fonction entree();, etc...
Je peux citer le modèle MVC où l'on sépare le Modèle, la Vue et le Controlleur (repris dans le main là haut).
Le but est de faciliter l'evolutivité, la maintenance et la compréhension. De plus, imaginez que vous voulez faire un nouveau jeu. Peut etre, vous souhaiterez reprendre exactement la même gestion de la manette. Vous pourrez alors réutiliser votre fichier controlleur.c, alors que vous auriez du reprendre tout votre gros code dans un seul fichier. De plus, vous pouvez travailler la partie graphique alors que votre pôte peut travailler la partie son.
Comment la fonction principale va t elle retrouver les fonctions ? c'est le but des fichiers .h : ils déclarent les fonctions écrites dans le fichier .c
Il suffit ensuite dans la fonction principale : #include "controlleur.h" pour qu'elle utilise "controlleur.c"
Pour compiler, il faudra indiquer à votre compilateur les noms de tous les fichiers c (et non plus le seul gros fichier) :
ex : gcc main.c controlleur.c graphisme.c son.c


III- Quelques spécificités


- Les commentaires : ce sont des parties du programmes qui ne seront pas exécutées. Les commentaires permettent d'expliquer les fonctions, tel choix dans le code... C'est indispensable de bien commenter un code, mais c'est très difficile d'y penser et de s'y mettre. La plupart des gens font les commentaires à la fin, mais il vaut mieux les faire au moment où l'on écrit le code pour ne pas oublier pourquoi on a fait ça... Syntaxe : while (i> 0) { // ce commentaire ne sera pas executé


- L'utilisation de constantes : imaginons que, dans votre jeu, vous donnez la possibilité à l'utilisateur de choisir son nombre d'ennemis (des fantomes par exemple). Ce nombre maximum sera repris certainement dans plusieurs de vos fonctions (affichage, vérifier que l'utilisateur n'en demande pas plus que le maximum possible...). Il est alors intéressant de créer une constante. Ainsi, si vous décidez d'augmenter ce nombre max, vous n'aurez qu'à changer cette constante et tout le reste du programme sera à jour.


Syntaxe :

#include "SDL/SDL.h" //ici ce sont les définitions vers les librairies
#include "SDL/SDL_image.h"

#define NB_FANTOMES_MAX 5 // ici c'est ma constante : pas plus de 5 fantomes !


- L'utilisation des variables globales : à utiliser "à petites doses".
Ce sont des variables que l'on pourra utiliser dans n'importe quelle fonction du programme (à part pour les fonctions des fichiers séparés, mais dans ce cas on peut utiliser des variables globales externes, on verra ça un peu plus tard). Voila comment les déclarer :


Syntaxe :

#include "SDL/SDL.h"
#include "SDL/SDL_image.h"

#define NB_FANTOMES_MAX 5

int nombre_de_fantomes; //c'est utile de l'avoir en variable globale car l'utilisateur peut les parametrer (donc appel dans la fonction du controlleur), puis si un fantome meurt, la fonction modif va decrementer ce nombre, etc...


Pourquoi ne pas en abuser ? Car ces variables sont et resteront en mémoire de l'ordinateur tout au long de l'exécution du programme. A l'inverse, la mémoire, utilisée par une variable déclarée dans une fonction, est récupérée à la sortie de la fonction. Nous reviendrons sur la gestion de la mémoire un peu plus bas.


- L'utilisation des structures : Imaginons que votre fantome est représenté par une image, par un vecteur (pour définir son mouvement, représenté par (X,Y)), une position dans l'espace (donc par (x,y)), un état (vivant ou mort, donc 1 ou 0)... Il est intéressant de définir une structure dans laquelle on va pouvoir définir les caractéristiques. Ca se rapproche beaucoup des langages objet (c++, java,...) mais c'est moins flexible.


Syntaxe :

#include "SDL/SDL.h"
#include "SDL/SDL_image.h"

#define NB_FANTOMES_MAX 5

typedef struct {
int X;//abscisse
int Y;//ordonnée
} Position;

typedef struct {
int Nb_X;//le fantome se deplacera de Nb_X pixels sur l'axe des abscisses à chaque mouvement
int Nb_Y;//le fantome se deplacera de Nb_Y pixels sur l'axe des ordonnées à chaque mouvement
} Vecteur;

typedef struct {
SDL_Surface *image;//image du fantome
Vecteur vecteur;
Position position;
int etat;//1 s'il est vivant, 0 sinon
} Fantome;


- L'utilisation des tableaux : on a défini la structure d'un fantome, on a dit que j'en voulais 5 maximum : comment s'en sortir ? Une des solutions est d'utiliser un tableau. L'avantage est que ce soit simple à gérer (tableau[1] donne le 2ème element du tableau, car c'est tableau[0] le 1er élement), mais on ne peut pas gérer la mémoire comme on le voudrait.


Syntaxe :

// suite du code precedent
Fantome Tableau_fantomes[NB_FANTOMES_MAX];


- Les pointeurs :
là on aborde un sujet très délicat, mais indispensable si vous voulez réussir à faire un bon programme en c. Je n'en parle que maintenant, car beaucoup sont allergiques au langage c à cause de ça. Toutefois, sachez d'hors et déjà que si votre programme termine en erreur par : Segmentation fault, c'est dû à un problème de pointeur.
Qu'est ce qu'un pointeur ?
C'est un lien vers qqch en mémoire de l'ordinateur. Lorsqu'on le crée, on précise vers quel type d'objet on pointe.


Syntaxe :

int variable=12; // ici je déclare une variable, elle est donc en mémoire
int *pointeur; // ici je déclare un pointeur qui peut être lié à un entier en mémoire

Comment faire pour que mon pointeur pointe sur ma variable ? En fait, un pointeur est une adresse mémoire. Pour que le pointeur pointe sur ma variable, il suffit de dire que le pointeur est égal à l'adresse de la variable :


Syntaxe :

pointeur=&variable;


Mais quel intérêt ?? :
-
meilleure alternative aux tableaux : je vais prendre l'exemple de la chaine de caractères. En c, une chaine de caractère est un tableau de caractères (char). Si l'on veut écrire une chaine, on peut écrire :

soit : char[20] tableau; : tableau de vingt caractères

soit : char *pointeur : pointeur vers des caractères

Dans les deux cas, tableau[0] et pointeur[0] nous donneront le 1er caractère de la chaine. Par contre, pour que l'on puisse utiliser 20 caractères avec la version pointeur, il faut allouer de la mémoire :

pointeur = (char*) malloc ( 20 * strlen(' ')+ 1 );

Comprenez ce code par : réserve une taille mémoire de 20 fois la taille d'un caractère pour ce que pointe le pointeur. Par convention, on alloue toujours une case en plus (d'où le + 1). Vous l'aurez compris, l'utilisation de tableaux est une façon simplifiée de déclarer ses pointeurs (pas besoin de faire le malloc, il est fait automatiquement) et la variable tableau contient la même chose que la variable pointeur, c'est à dire l'adresse de la 1ere case du tableau en mémoire.

Mais à quoi ça sert alors de faire un malloc ? Imaginons que vous avez besoin d'écrire le 21ème élément du tableau... tableau[21]=12; ou pointeur[21]=12 engendreront la même erreur : Segmentation fault.

Comment s'en sortir ?


Enfin, imaginons que vous faites une grande fonction d'initialisation avec des calculs dans des tableaux. Une fois les calculs finis, vous n'avez plus besoins des valeurs dans les tableaux. Pourquoi ne pas libérer la mémoire tout de suite (elle sera libérée à la fin de la fonction mais autant le faire tout de suite). Attention, ceci est irreversible et vous n'aurez plus accès aux valeurs contenues dans le tableau : free (tableau) ou free(pointeur)


- Quelle convention de nommage utiliser pour ses variables ?
2 solutions sont très utilisées :

pour une variable donnant les résultats de foot : resultatFoot ou resultat_foot
pour une fonction qui donne l'heure du jour : heureDuJour ou heure_du_jour

Je ne saurais que vous conseiller de toujours utiliser la même convention tout du long de votre programme pour vous y retrouver, mais c'est vous qui voyez.


Conclusion


La programmation en langage c n'est pas toujours simple. J'espère que ce document pourra vous aider. J'espère également ne pas avoir fait trop d'erreur dedans. N'hésitez pas à me contacter si vous trouvez une erreur, ou pour toute autre suggestion. Email : patbier@hotmail.com