Jeux Libres
       
           

» Les Tutoriels » Création d'un jeu vidéo » Objet 3D statique

Objet 3D statique


On commence à se rendre compte qu'il est temps de créer une classe permettant de gérer les objets 3D. C'est ce que nous allons faire maintenant.





Chapitre précédent     Sommaire     Chapitre suivant


Définition de notre objet 3D


On aimerait que :

  • Notre classe modélise l'objet 3D à partir d'un fichier.
  • Ce fichier soit modifiable facilement avec un éditeur de texte.
  • Cet objet puisse être créé à partir de plusieurs fichiers de texture.
  • Son rendu soit optimisé.

La classe



Son nom



J'ai choisi d'appeler la classe Objet3DStatique. Elle décrit un objet, 3D, qui ne se déformera pas. On la déclarera dans un fichier objet3DStatique.h et on la définira dans un fichier objet3DStatique.cpp.

Ses méthodes



Le constructeur



Un seul constructeur sera nécessaire ; il prendra en paramètre le nom du fichier sous la forme d'un std::string.

Autres méthodes



L'objet 3D ne peut qu'être dessiné, on ajoute donc une méthode dessiner3D().

Bien entendu, il pourrait se déplacer, tourner... Mais par soucis de simplicité, nous allons ajouter à notre objet 4 attributs publiques :
1
2
3
4
5
6
7
8
    public:
       // Position de l'objet
       float16 positionX;
       float16 positionY;
       float16 positionZ;
 
       // Angle horizontal avec la verticale en Z
       float16 angleHorizontal;

Généralement, on ne rend pas publique les attributs d'une classe. Ici je me permets de le faire pour éviter de passer par des accesseurs à chaque fois que je vais les manipuler. On pourra revoir ça plus tard.


Les attributs privés



En interne, la classe "embarquera" les textures de l'objet. On peut donc refaire ce qu'on avait déjà fait pour les textures :
1
2
typedef std::map<std::string, GLuint> Textures;
Textures textures;


Le fichier de l'objet 3D


Pour pouvoir charger l'objet 3D, nous devons créer un format d'objet 3D. Afin que celui-ci soit facilement modifiable, nous allons le faire en ASCII (en texte).

Afin d'accélérer le rendu, nous créerons une liste d'affichage. Les textures doivent exister avant de créer la liste d'affichage. Dans le fichier, on va donc séparer le chargement des textures de la création de la liste d'affichage.

Notre classe comportera donc un attribut de plus :
1
GLuint listeAffichage;

La méthode dessiner3D() fera donc appel à cette liste d'affichage.

Le format O3S



Après réflexion, voici à quoi ressemble le fichier de ma skybox que j'ai appelé skybox.o3s. Je vous laisse essayer de comprendre mon format O3S (Objet 3D Statique).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
nom Skybox

image skybox\nord.bmp
image skybox\sud.bmp
image skybox\ouest.bmp
image skybox\est.bmp
image skybox\haut.bmp
image skybox\bas.bmp

rst 0.0 0.0 0.0 1.0      1.0 1.0 1.0      0.0 0.0 0.0

image skybox\nord.bmp
{
   0   0     -1 -1  1
   0   255   -1 -1 -1
   255 255   -1  1 -1
   255 0     -1  1  1
}

image skybox\sud.bmp
{
   0   0      1  1  1
   0   255    1  1 -1
   255 255    1 -1 -1
   255 0      1 -1  1
}

image skybox\ouest.bmp
{
   0   0      1 -1  1
   0   255    1 -1 -1
   255 255   -1 -1 -1
   255 0     -1 -1  1
}

image skybox\est.bmp
{
   0   0      1 -1  1
   0   255    1 -1 -1
   255 255   -1 -1 -1
   255 0     -1 -1  1
}

Explications



Nom



Le tag nom permet de donner un nom à notre objet. Pour l'instant, il n'a pas vraiment d'utilité.
1
nom Skybox

Chargement des textures



Par soucis de simplicité, j'ai choisi d'énumérer toutes les textures en tête de fichier.
1
2
3
4
5
6
image skybox\nord.bmp
image skybox\sud.bmp
image skybox\ouest.bmp
image skybox\est.bmp
image skybox\haut.bmp
image skybox\bas.bmp

A chaque fois qu'on rencontrera le tag "image", on chargera la texture en question.

Toutes les textures doivent être chargées avant le début de la description de l'objet.


Rotation, Redimensionnement, Translation



Le tag rst permet d'adapter la taille et le sens de l'objet au reste de la scène. La taille des objets étant relative aux autres objets, ce choix permet d'adapter notre objet à la scène.
1
rst 0.0 0.0 0.0 1.0      1.0 1.0 1.0      0.0 0.0 0.0

Ce tag aura pour effet d'effectuer les transformations suivantes :
1
2
3
glRotatef(0.0, 0.0, 0.0, 1.0); // R : Rotation
glScalef(1.0, 1.0, 1.0); // S : Redimensionnement
glTranslatef(0.0, 0.0, 0.0); // T : Translation

Description de l'objet



Ensuite, on décrit l'objet par polygone :
1
2
3
4
5
6
7
image skybox\nord.bmp
{
   0   0     -1 -1  1
   0   255   -1 -1 -1
   255 255   -1  1 -1
   255 0     -1  1  1
}

La première ligne signifie que l'on sélectionne la texture skybox\devant.bmp. Ensuite, on ouvre une accolade, ce qui signifie que le dessin du polygone va commencer. Chaque ligne définit un sommet du polygone.
1
0   0     -1 -1  1

Cette ligne fait correspondre le point (0;0) du BMP (en haut à gauche, les X en premier et vers la droite) au point tridimensionnel (-1;-1;1) de l'objet dans l'espace.

Conclusion



Comme vous pouvez le voir, le format O3S a le mérite d'être simple et facilement modifiable. Il conviendra pour la plupart des objets de notre scène. Il a pour inconvénient de pouvoir ne décrire que des objets 3D statique. Ce format ne convient pas pour un personnage avec des jambes qui bougent lorsqu'il se déplace.


La classe de l'objet 3D


Chargement



Dans un premier temps, je m'occupe du chargement de l'objet sans m'occuper de l'affichage. Voici ce que j'ai fait pour commencer :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Objet3DStatique::Objet3DStatique(std::string nomFichier)
{
   // Initialisation des attributs
   float16 positionX = 0.0;
   float16 positionY = 0.0;
   float16 positionZ = 0.0;
   float16 angleHorizontal = 0.0;
 
   // Ouverture du fichier
   std::fstream fichier(nomFichier.c_str(), std::fstream::in);
 
   // Ouverture ralise avec succs
   if (fichier.is_open())
   {
       std::string ligne;
 
       // Lecture de chaques lignes
       while(getline(fichier, ligne))
       {
           // Chargement du nom
           if (0 == ligne.compare(0, 4, "nom "))
           {
               this->nom.assign(ligne.begin() + 4, ligne.end());
           }
 
           // Chargement d'une texture
           else if (0 == ligne.compare(0, 6, "image "))
           {
               // Lecture du nom de fichier de la texture
               std::string nomFichierTexture;
               nomFichierTexture.assign(ligne.begin() + 6, ligne.end());
 
               // Si la texture n'est pas deja creee
               if(this->textures.find(nomFichierTexture) == this->textures.end())
               {
                   // On cr la texture
                   this->textures[nomFichierTexture] = loadTexture(nomFichierTexture.c_str());
               }
           }
 
           // Debut de coordonnees
           else if (0 == ligne.compare(0, 1, "{"))
           {
 
           }
 
           // Commentaire et ligne vide
           else if (0 == ligne.compare(0, 1, "#") || ligne.empty())
           {
           }
       }
   }
   else
   {
       printf("Erreur lors de l'ouverture du fichier %s\n", nomFichier.c_str());
   }
}

Cela fait beaucoup de ligne d'un coup, c'est vrai. Mais ce code n'est pas très compliqué à comprendre. C'est la raison pour laquelle je ne vais pas expliquer davantage. Prenez le temps de bien comprendre ce qu'il fait avant de continuer.


Afin d'éviter la fuite de mémoire liée au chargement des textures, je libère dès maintenant les textures dans le destructeur :
1
2
3
4
5
6
7
8
9
Objet3DStatique::~Objet3DStatique()
{
   // Libration des textures
   for (Textures::iterator element = this->textures.begin(); element != textures.end(); element++)
   {
       glDeleteTextures(1, &element->second);
       element->second = 0;
   }
}

Liste d'affichage



Pour ceux qui aurait oublié ce qu'est une liste d'affichage, c'est comme un affichage, sauf que ça n'affiche pas, mais ça enregistre les étapes d'affichage dans la carte graphique.

On génère une liste d'affichage avec glGenLists(), on la commence avec glNewList() puis on la termine avec glEndList().

On va maintenant distinguer deux modes : le mode création de la liste d'affichage activé de celui désactivé.

Au début du chargement, le mode création de la liste d'affichage est désactivé :
1
bool8 creationListeAffichage = FALSE;

La liste d'affichage commence lorsqu'on rencontre la première accolade :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
            // Debut de coordonnees
           else if (0 == ligne.compare(0, 1, "{"))
           {
               if (FALSE == creationListeAffichage)
               {
                   this->listeAffichage = glGenLists(1);
                   glNewList(this->listeAffichage, GL_COMPILE);
 
                   glEnable(GL_TEXTURE_2D);
 
                   creationListeAffichage = TRUE;
               }
 
               // . . .
           }

Remarquez que j'ai ajouté un attribut listeAffichage.

Pour chaque polygone, on fait une boucle pour parcourir les sommets et les ajouter à la liste d'affichage. Lorsqu'on rencontre l'accolade fermante, on termine le polygone en cours. Remarquez également le changement de la texture courante qui s'effectue lorsqu'on rencontre le tag "image".

Pour finir, je fais un appel à glEndList(), puis je libère le fichier.

Voici le code de la fonction :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
Objet3DStatique::Objet3DStatique(std::string nomFichier)
{
   // Initialisation des attributs
   float16 positionX = 0.0;
   float16 positionY = 0.0;
   float16 positionZ = 0.0;
   float16 angleHorizontal = 0.0;
 
   bool8 creationListeAffichage = FALSE;
   GLuint textureCourante = 0;
 
   // Ouverture du fichier
   std::fstream fichier(nomFichier.c_str(), std::fstream::in);
 
   // Ouverture ralise avec succs
   if (fichier.is_open())
   {
       std::string ligne;
 
       // Lecture de chaques lignes
       while(getline(fichier, ligne))
       {
           // Chargement du nom
           if (0 == ligne.compare(0, 4, "nom "))
           {
               this->nom.assign(ligne.begin() + 4, ligne.end());
           }
 
           // Chargement d'une texture
           else if (0 == ligne.compare(0, 6, "image "))
           {
               // Lecture du nom de fichier de la texture
               std::string nomFichierTexture;
               nomFichierTexture.assign(ligne.begin() + 6, ligne.end());
 
               // Si la texture n'est pas deja creee
               if(this->textures.find(nomFichierTexture) == this->textures.end())
               {
                   // On cr la texture
                   this->textures[nomFichierTexture] = loadTexture(nomFichierTexture.c_str());
               }
 
               // Cette texture devient la texture courante
               textureCourante = this->textures[nomFichierTexture];
           }
 
           // Debut de coordonnees
           else if (0 == ligne.compare(0, 1, "{"))
           {
               // Detection du debut de la cration de la liste d'affichage
               if (FALSE == creationListeAffichage)
               {
                   // Gnration et commencement de la liste d'affichage
                   this->listeAffichage = glGenLists(1);
                   glNewList(this->listeAffichage, GL_COMPILE);
 
                   glEnable(GL_TEXTURE_2D);
 
                   creationListeAffichage = TRUE;
               }
 
               // Selection de la dernire texture rencontre texture
               glBindTexture(GL_TEXTURE_2D, textureCourante);
 
               glBegin(GL_POLYGON);
 
               // Description des sommets du polygone
               while(getline(fichier, ligne))
               {
                   sint32 x2D, y2D, x3D, y3D, z3D;
 
                   // Fin de coordonnees
                   if (0 == ligne.compare(0, 1, "}") )
                   {
                       glEnd();
                       break;
                   }
 
                   // Commentaire / ligne vide
                   else if (0 == ligne.compare(0, 1, "#") || ligne.empty())
                   {
 
                   }
 
                   // Coordonnees 2D / 3D
                   else if (5 == sscanf(ligne.c_str(), "%ld %ld %ld %ld %ld", &x2D, &y2D, &x3D, &y3D, &z3D))
                   {
                       // ATTENTION : N'ayant pas enregistr la taille de la texture, j'ai entr la taille 256 en dur
                       // Cette taille n'est valable que pour les faces de la skybox
                       glTexCoord2f((float16)x2D / 256, (256 - (float16)y2D) / 256); glVertex3d(x3D, y3D, z3D);
                   }
 
                   else
                   {
                       printf("ATTENTION : Erreur lors du chargement du fichier O3S\n");
 
                       fichier.close();
                       return;
                   }
               }
           }
 
           // Commentaire et ligne vide
           else if (0 == ligne.compare(0, 1, "#") || ligne.empty())
           {
           }
       }
 
       // A la fin, la liste d'affichage est termin
       glEndList();
 
       // Le fichier peut alors etre ferm
       fichier.close();
   }
   else
   {
       printf("Erreur lors de l'ouverture du fichier %s\n", nomFichier.c_str());
   }
}

Ce code est encore provisoire, n'aillant pas connaissance de la taille des textures, je me suis retrouvé bloqué lorsqu'il a s'agit de définir les sommets de la textures. Nous verrons plus tard comment s'y prendre pour remédier à ce problème. La gestion des textures sera faite plus tard.


Au niveau des appels aux fonctions glTexCoord2f() et glVertex3d(), il y a une petite subtilité. Les textures SDL sont chargés avec pour origine l'angle situé en haut à gauche tandis que l'origine d'OpenGL est en bas à gauche. C'est la raison pour laquelle il faut faire 256 - (float16)y2D.


RST



Pour ce qui concerne le tag RST permettant d'apporter la correction de l'objet dans la scène, voici comment j'ai fait :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
            // Correction RST (Rotate, Scale, Translate)
           else if (0 == ligne.compare(0, 4, "rst "))
           {
               std::string rst;
               GLfloat rAngle, rX, rY, rZ, sX, sY, sZ, tX, tY, tZ;
 
               rst.assign(ligne.begin() + 4, ligne.end());
 
               if (10 == sscanf(rst.c_str(), "%f %f %f %f %f %f %f %f %f %f",
                               &rAngle, &rX, &rY, &rZ, // glRotatef
                               &sX, &sY, &sZ, // glScalef
                               &tX, &tY, &tZ  // glTranslatef
                               ))
               {
                   // Gnration de la liste d'affichage pour la RST
                   this->listeRST = glGenLists(1);
                   glNewList(this->listeRST, GL_COMPILE);
 
                   // Dfinition de la RST
                   glRotatef(rAngle, rX, rY, rZ);
                   glScalef(sX, sY, sZ);
                   glTranslatef(tX, tY, tZ);
 
                   glEndList();
               }
           }

Je vous l'accorde, le sscanf n'est pas très propre. Tant pis. L'attribut listeRST à dû être ajouté.

Dessin de l'objet



Pour dessiner, rien de plus simple, on mémorise le repère courant (la matrice), puis on dessine l'objet à sa place avec la correction RST, et on termine par une restauration du repère d'origine.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Objet3DStatique::dessiner3D()
{
   // On mmorise le repre courant avant d'effectuer la RST
   glPushMatrix();
 
       // Positionne l'objet en lieu de dessin
       glTranslatef(this->positionX, this->positionY, 0.0);
       glRotated(this->angleHorizontal, 0.0, 0.0, 1.0);
 
       // Correction / Adaptation de l'objet a la scene
       glCallList(this->listeRST);
 
       // Dessin de l'objet
       glCallList(this->listeAffichage);
 
   // Restauration du repre d'origine
   glPopMatrix();
}


Test d'affichage de l'objet


J'ajoute un objet à ma scène que j'instancie à partir de mon fichier de skybox et je remplace l'affichage manuel de la skybox par un appel à ma méthode dessiner3D().

Visiblement rien de ne passe. Pas de panique, les choses fonctionnent rarement du premier coup.

Débugger



Pour débugger, chacun sa méthode. Personnellement je ne suis pas fan de débugger, je préfère écrire dans un fichier les actions qui s'exécutent et les analyser après.

Je tiens à préciser qu'il s'agit d'un bug involontaire de ma part. Je pensais que ça fonctionnerait du premier coup. Vous assistez donc en live à un bug. C'est l'occasion pour moi de vous montrer comment je m'y prend pour le résoudre.


Ce dont on est sûr



On voit que rien ne s'affiche. Nous somme pourtant sur que l'objet est bien instancié. Peut-être que les textures ne sont pas chargées correctement. Pourtant, si elles n'était pas chargées correctement et qu'on essayait de les afficher, on les verrait apparaître en blanc. La caméra étant correctement placée, à priori, les textures ne sont donc pas dessinées à l'écran. On peut donc se demander si les textures ont été correctement lues dans le fichiers O3S.

On va donc vérifier que les tags image aient bien été détectés et créés.
1
2
3
                    // On cr la texture
                   this->textures[nomFichierTexture] = loadTexture(nomFichierTexture.c_str());
                   printf("Texture %s(%lu) cre.\n", nomFichierTexture.c_str(), this->textures[nomFichierTexture]);

Après exécution, dans le fichier stdout.txt (sortie standard de la SDL), j'obtient ceci :
1
2
3
4
5
6
Texture skybox\nord.bmp(1) créée.
Texture skybox\sud.bmp(2) créée.
Texture skybox\ouest.bmp(3) créée.
Texture skybox\est.bmp(4) créée.
Texture skybox\haut.bmp(5) créée.
Texture skybox\bas.bmp(6) créée.

A priori, les textures sont bien détectées et chargées.

Nous allons vérifier la liste d'affichage en affichant chaque commande OpenGL à l'aide de printf.

Par exemple :
1
2
                glBegin(GL_POLYGON);
               printf("glBegin(GL_POLYGON);\n");

Après exécution, je vois ceci :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
glGenLists();
glNewList();
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureCourante);
glBegin(GL_POLYGON);
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glEnd();
glBindTexture(GL_TEXTURE_2D, textureCourante);
glBegin(GL_POLYGON);
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glEnd();
glBindTexture(GL_TEXTURE_2D, textureCourante);
glBegin(GL_POLYGON);
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glEnd();
glBindTexture(GL_TEXTURE_2D, textureCourante);
glBegin(GL_POLYGON);
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glTexCoord2f(); glVertex3d();
glEnd();
glEndList();

La liste d'affichage me paraît correcte. Il n'y a donc aucune raison que la skybox ne soit pas dessinée. Si elle est bien dessiné, où est-elle dessiné ? Visiblement, on la dessine en (this->positionX;this->positionY;this->positionZ). Si elles sont bien initialisé à zéro, la skybox devrait apparaitre au centre de l'écran. En regardant le constructeur, on se rend compte que les variables initialisées ne sont pas les attributs. Après rectification, ça marche !


Modélisation de la skybox



Nous ne voyons que 2 faces. Ceci est parfaitement normal, les faces "vers nous" sont regardées de derrière, elle ne sont donc pas dessinées. Pour vérifier les faces, nous devons soit déplacer la caméra, soit faire tourner la skybox. Le plus simple est de la faire tourner :
1
2
3
4
void Scene::animer(void)
{
   this->skybox->angleHorizontal += 0.3;
}

On remarque alors un "bug" au niveau de la face est. Je vous laisse faire la correction dans le fichier O3S. Vous devriez obtenir ceci :


Je vous laisse également faire le bas, puis le haut. Pour le haut, vous devrez placer la caméra dans les Z négatifs.
1
gluLookAt(3,4,-2,0,0,0,0,0,1);

Temps de construction de l'image



Chez moi, le cube fait une tour complet en 20 secondes. Je m'interroge donc sur le temps de la boucle de construction de l'image. Un simple produit en croix me dit qu'en mettant 20 secondes à faire 360°, avec une rotation de 0.3° par image, j'obtiens un temps de construction d'image de 16ms, soit 60 images par seconde.

Suis-je déjà arrivé à la limite ?



Cela supposerait donc que le processeur consomme 100% du CPU, or ce n'est pas cas. Une pause serait donc faite quelque part ?

En réalité, dans ma configuration graphique, j'ai validé l'option : "Synchronisation verticale". Cela a pour effet de bloquer l'affichage glFlush() jusqu'à l'heure de la prochaine image. Mon écran étant synchronisé à 60 images par seconde, ma boucle de création de l'image sera limité à 60 images par seconde. (60 FPS)

Ne soyez donc pas surpris si votre programme se limite à 60 FPS, c'est tout à fait normal. Si la synchronisation verticale n'est pas activé, vous devriez mémoriser l'heure de la dernière boucle et faire tourner le cube de façon proportionnel au temps écoulé afin que la vitesse de rotation ne dépende pas de la machine qui exécute le programme.

Agrandir la skybox, placer la camera en centre



Pour agrandir la skybox, rien de plus simple, il suffit de redimensionner l'objet directement dans l'O3S :
1
rst 0.0 0.0 0.0 1.0      500.0 500.0 500.0      0.0 0.0 0.0

Maintenant, le cube est si grand que la camera est presque au centre du cube. Pour le moment, on se satisfera de ce résultat.


Si vous agrandissez trop la skybox, vous risquez de voir du noir. La raison est très simple, votre skybox est en collision avec le plan de clipping du fond (réglé à 1000 pour le moment), ce qui a pour effet de ne pas la dessiner, on voit donc du noir.

Entre les textures, des lignes apparaissent



En regardant bien, on peut voir un trait entre les textures. Pour remédier à ce problème, on peut croiser très légèrement les textures ou utiliser un mode de rendu différent. Dans le cadre de ce chapitre, je n'en parlerais pas. Vous trouverez des informations en tapant sur google les mots suivant : skybox arêtes opengl. Nous verrons plus tard comment remédier à ce problème.




Nous savons maintenant comment modéliser un objet 3D et le manipuler. Souvenez-vous, lors de la création de la liste d'affichage, il nous manquait une information : la taille de la texture. Dans le prochain chapitre, nous allons organiser nos textures de façon à mémoriser ses informations.

D'autre part, il y a un problème que j'ai passé sous silence, il s'agit de la charge des textures dans la carte graphique lorsqu'un objet et instancié plusieurs fois. Pourquoi recharger une texture qu'on a déjà chargée ? (Perte de temps, perte de place...) Nous mettrons donc au point un mécanisme permettant de ne charger que les textures utiles.

Jusqu'à présent, nous ne faisons que des choses qui ne se voient pas, ce qui peut être assez décourageant. Ce qu'il faut bien garder à l'esprit, c'est que nous faisons tout cela pour faciliter le développement de la suite de notre jeu. Nous prendrons de la hauteur et il sera très facile ensuite d'ajouter des objets, sans se préoccuper des "couches basses".

Pour repartir du bon pied, voici le projet actuel. J'ai fait un peu de mise au propre.

Attention : Un bug s'est glissé. Pour les développeurs sous linux, veuillez vous informer de ce sujet.




Chapitre précédent     Sommaire     Chapitre suivant



Rédigé par David
Consulté 20875 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 126 jours 4 heures 18 minutes
Espace libre : 2117 Mo
Dernière sauvegarde : inconnue
Taille de la sauvegarde : 1101 Mo


4149572 pages ont été consultées sur le site !
Dont 649 pages pendant les 24 dernières heures.

Page générée en 0.42 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++


  © 2005-2017 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.