XNA Tutorial 7 : Cas concret, l’affichage d’une ville et du système solaire

Retourner au sommaire des cours  


Nous avons passé, avec le dernier tutorial, une première étape dans le développement de jeux. Ce tutorial marque la fin de cette étape et, en conclusion, va nous apprendre à utiliser nos connaissances acquises pour créer une ville (rudimentaire, il faut rester humble), et pour créer une simulation de notre bon vieux système solaire. Ces deux projets ne sont pas seulement ludiques, il vont nous être très utiles. La ville tout d’abord va nous permettre de bien comprendre comment s’orienter dans l’espace et de mesurer l’utilité d’un bon système objet. La simulation quand à elle, poussera nos connaissances et notre maitrise des Matrices dans leur derniers retranchements. Ces deux projets seront aussi la base de nombreux projets qui vont suivre pour expliciter les notions que nous apprendrons aux fils des tutoriaux qui vont suivre.

L’objet de toutes les convoitises

La ville que nous allons créer sera simple. Chaque paté de maison sera en fait constitué d’un seul batiment dont la hauteur variera de manière aléatoire. Nous utiliserons pour la créer trois types de cubes : un cube pour créer le sol, un cube par paté pour créer les trottoires, un cube pour créer chaque gratte ciel. Evidemment nous n’allons pas comme dans le précédent tutoriel, nous amuser à instancier un vertex buffer et un index buffer pour chaque cube créé à la main. Nous allons plutot utiliser une classe “Cube” qui va faire tout le travail à notre place en nous offrant deux méthodes : Load et Render. Nous reprendrons le code du tutorial précédent en le factorisant pour permettre un développement plus modularisé.

La nouvelle classe Cube se présente ainsi :

/// <summary>/// <para>3D representation of a cube.</para>/// </summary>public class Cube : IDisposable{     #region Private members     private GraphicsDevice _device;    private VertexBuffer _vertexBuffer = null;    private IndexBuffer _indexBuffer = null;    private float _width;    private float _height;    private float _depth;    private Color _color = Color.TransparentWhite;    private float _x, _y, _z;    private float _rotationX, _rotationY, _rotationZ;    private static short[] indices = new short[36]{0,1,2,                                          0,2,3,                                          3,2,4,                                          3,4,5,                                          5,4,7,                                          5,7,6,                                          6,7,1,                                          6,1,0,                                          6,0,3,                                          6,3,5,                                          1,7,4,                                          1,4,2};    private Matrix _transformationMatrix;    private Matrix _translationMatrix;    private Matrix _scaleMatrix;    private Matrix _rotationMatrix;     #endregion      #region Properties      /// <summary>    /// <para>Gets or sets the Cube’s color.</para>    /// </summary>    public Color Color    {        get        {            return this._color;        }        set        {            this._color = value;        }    }     /// <summary>    /// <para>Gets or sets the Cube’s current transformation.</para>    /// </summary>    public Matrix Transformation    {        get        {            return this._transformationMatrix;        }        set        {            this._transformationMatrix = value;        }    }     /// <summary>    /// <para>Gets the cube’s Width.</para>    /// </summary>    public float Width    {        get        {            return this._width;        }    }     /// <summary>    /// <para>Gets the cube’s height.</para>    /// </summary>    public float Height    {        get        {            return this._height;        }    }     /// <summary>    /// <para>Gets the cube’s depth.</para>    /// </summary>    public float Depth    {        get        {            return this._depth;        }    }     /// <summary>    /// <para>Gets the cube’s position on x-axis.</para>    /// </summary>    public float X    {        get        {            return this._x;        }    }     /// <summary>    /// <para>Gets the cube’s position on y-axis.</para>    /// </summary>    public float Y    {        get        {            return this._y;        }    }     /// <summary>    /// <para>Gets the cube’s position on z-axis.</para>    /// </summary>    public float Z    {        get        {            return this._z;        }    }     #endregion      #region Constructors     /// <summary>    /// <para>Instanciate a new Cube objet.</para>    /// </summary>    public Cube()    {        this._transformationMatrix = Matrix.Identity;        this._vertexBuffer = null;         this._width = 3.0f;        this._height = 3.0f;        this._depth = 3.0f;         this._x = 50.0f;        this._y = 10.0f;        this._z = 10.0f;         this._rotationMatrix = Matrix.Identity;        this._translationMatrix = Matrix.Identity;        this._scaleMatrix = Matrix.CreateScale(1f, 1f, 1f);    }     public void Dispose()    {        if (this._vertexBuffer != null)            this._vertexBuffer.Dispose();         if (this._indexBuffer != null)            this._indexBuffer.Dispose();         this._indexBuffer = null;        this._vertexBuffer = null;    }      #endregion      #region Initialization     public void Load(GraphicsDevice device)    {        this._device = device;        this.InitializeIndices();        this.InitializeVertices();    }     private void InitializeIndices()    {        this._indexBuffer = new IndexBuffer(            this._device,            typeof(short),            36,            ResourceUsage.WriteOnly,            ResourceManagementMode.Automatic);         this._indexBuffer.SetData(Cube.indices);    }     private void InitializeVertices()    {        VertexPositionColor[] vertices = new VertexPositionColor[8];         vertices[0].Position = new Vector3(0f, 0f, 0f);        vertices[0].Color = this.Color;        vertices[1].Position = new Vector3(0f, 0f, 1f);        vertices[1].Color = this.Color;        vertices[2].Position = new Vector3(1f, 0f, 1f);        vertices[2].Color = this.Color;        vertices[3].Position = new Vector3(1f, 0f, 0f);        vertices[3].Color = this.Color;        vertices[4].Position = new Vector3(1f, 1f, 1f);        vertices[4].Color = this.Color;        vertices[5].Position = new Vector3(1f, 1f, 0f);        vertices[5].Color = this.Color;        vertices[6].Position = new Vector3(0f, 1f, 0f);//        vertices[6].Color = this.Color;        vertices[7].Position = new Vector3(0f, 1f, 1f);        vertices[7].Color = this.Color;         this._vertexBuffer = new VertexBuffer(        this._device,        typeof(VertexPositionColor),        8,        ResourceUsage.WriteOnly,        ResourceManagementMode.Automatic);         this._vertexBuffer.SetData(vertices);    }      #endregion      #region Rendering     /// <summary>    /// <para>Render the cube on the device.</para>    /// </summary>    public void Render()    {        this._device.Vertices[0].SetSource(this._vertexBuffer, 0, VertexPositionColor.SizeInBytes);        this._device.Indices = this._indexBuffer;        this._device.VertexDeclaration = new VertexDeclaration(this._device, VertexPositionColor.VertexElements);        this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);     }       #endregion      #region Public methods     /// <summary>    /// Sets a size for the cube.    /// </summary>    public void SetRotation(float rotationX, float rotationY, float rotationZ)    {        this._rotationX = rotationX;        this._rotationY = rotationY;        this._rotationZ = rotationZ;         this._rotationMatrix = Matrix.CreateRotationX(rotationX) * Matrix.CreateRotationY(rotationY) * Matrix.CreateRotationZ(rotationZ);        this.UpdateTransformation();    }     /// <summary>    /// Sets a size for the cube.    /// </summary>    public void SetSize(Vector3 size)    {        this._width = size.X;        this._height = size.Z;        this._depth = size.Y;         this._scaleMatrix = Matrix.CreateScale(size.X, size.Y, size.Z);        this.UpdateTransformation();    }     /// <summary>    /// Sets a position for the cube.    /// </summary>    public void SetPosition(Vector3 location)    {        this._x = location.X;        this._y = location.Y;        this._z = location.Z;         this._translationMatrix = Matrix.CreateTranslation(location.X, location.Y, location.Z);        this.UpdateTransformation();    }     private void UpdateTransformation()    {        this._transformationMatrix = this._scaleMatrix * this._rotationMatrix * this._translationMatrix;    }     #endregion }
 

rien de bien compliqué ; il s’agit d’une simple factorisation du code du tutoriel 6. J’ai pris le code de la classe Game1 d’alors pour le copier coller à l’intérieur de cette classe. Si vous regardez le code de la nouvelle classe Game1 vous  verrez une nette amélioration de la lisibilité du code. Notons toutefois que nous utilisons ici une couleur qui peut être spécifiée lors de la création du cube en lieu et place des huit couleurs que nous utilisions jusqu’ici pour chaque sommet du cube.

Le constructeur de Game1 contient désormais une nouvelle instruction :

this._cube = new Cube();

La méthode Initialize ne fait plus appel à this.InitializeIndices();this.InitializeVertices();

mais à

this.InitializeCubes();

qui correspond à la méthode

private void InitializeCubes()
{
    this._cube.Load(this.graphics.GraphicsDevice);

}

La méthode d’affichage dessine le cube en une instruction :

this._cube.Render();

Enfin, les actions utilisateur sur le clavier modifient la taille et la position du code par l’intermédiaire d’appels à des méthodes explicites :

this._cube.SetSize(size);

this._cube.SetPosition(position);

La transformation étant obtenue par l’intermédiaire de la propriété Transformation. Celle-ci se calcule automatiquement à partir des méthodes SetRotation, SetSize et SetPosition.

//modulolong iTime = (long)(gameTime.TotalGameTime.TotalMilliseconds % 2000f);//passage en radianfloat fAngle = iTime * (2.0f * MathHelper.Pi) / 2000.0f; this._cube.SetSize(size);this._cube.SetPosition(position);             //la transformatio en elle mêmeMatrix world = Matrix.CreateRotationY(fAngle)    * Matrix.CreateRotationX(fAngle)    * this._cube.Transformation; 

effect.Parameters["xWorld"].SetValue(world);

A l’exécution vous obtenez pourtant une application dont le rendu est complètement différent de notre précédent tutoriel :

En fait on peut avoir l’impression au premier abord d’avoir regressé. Au contraire nous avons fait un grand pas en avant ! Notre cube est plus petit tout simplement parcequ’il possède désormais une taille unitaire (1 pour chacun de ses arrètes contre 2 précédemment), afin de facilement spécifier des tailles précises. Il possède une couleur pâle. Ceci est du au fait que nous affectons à chaque vertex la couleur spécifiée par l’utilisateur avant l’appel à la méthode Load du cube. Reportez vous au code de la méthode InitializeVertices pour mieux comprendre :

private void InitializeVertices(){    VertexPositionColor[] vertices = new VertexPositionColor[8];     vertices[0].Position = new Vector3(0f, 0f, 0f);    vertices[0].Color = this.Color;    vertices[1].Position = new Vector3(0f, 0f, 1f);    vertices[1].Color = this.Color;    vertices[2].Position = new Vector3(1f, 0f, 1f);    vertices[2].Color = this.Color;    vertices[3].Position = new Vector3(1f, 0f, 0f);    vertices[3].Color = this.Color;    vertices[4].Position = new Vector3(1f, 1f, 1f);    vertices[4].Color = this.Color;    vertices[5].Position = new Vector3(1f, 1f, 0f);    vertices[5].Color = this.Color;    vertices[6].Position = new Vector3(0f, 1f, 0f);//    vertices[6].Color = this.Color;    vertices[7].Position = new Vector3(0f, 1f, 1f);    vertices[7].Color = this.Color;     this._vertexBuffer = new VertexBuffer(    this._device,    typeof(VertexPositionColor),    8,    ResourceUsage.WriteOnly,    ResourceManagementMode.Automatic);     this._vertexBuffer.SetData(vertices);

}

Enfin pour terminer, le cube ne semble pas tourner sur lui-même mais par rapport à un point correspondant au vertice 0. Là encore tout est normal, le vertice 0 se trouve à l’origine dans le repère 3D à l’intérieur duquel nous créer notre cube (0f, 0f, 0f). C’est par rapport à l’origine d’un objet 3D que se calculent toutes les transformations qui lui sont appliquées. Notre cube se voit rotaté sur lui-même, cette rotation s’effectuera donc par rapport à ce vertice.

Pour terminer  ce point il est plus que necessaire de lire l’article qui se trouve ici, pour bien comprendre l’importance de l’ordre de la multiplication des matrices de transformation (rotation, redimentionnement, translation) pour modifier l’affichage d’un objet à l’écran afin de vous éviter toute surprise et bug de rendu incompréhensible.

La théorie étant acquise, un exercice peaufinera notre pratique : Maintenant que nous avons une classe clé en main pour créer un cube et l’afficher simplement, essayez d’en afficher plusieurs à l’écran.

 

telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   

 

New York !

Si vous avez reussi l’exercice précédent, notre premier monde de jeu sera une formalité pour vous. La ville que nous allons créer ici sera plus que rudimentaire. Le sol sera un cube avec une hauteur de 1, les trottoires des pavés applatis avec une hauteur de 5, enfin les batiments seront eux aussi des cubes dont la hauteur sera variable. A ce stade de nos connaissances nous ne pouvons pas ajouter de lumières d’ambiance, pas de texture aux immeubles et pas d’optimisation d’affichage. C’est pourtant un avantage certain puisque dans les articles qui suivront nous améliorerons le code dans ce sens et pourrons mesurer facilement l’importances des notions que nous allons acquerir. Cette ville nous offre aussi la possibilité de bien comprendre comment placer ses objets à l’écran.

Les seules modifications que nous apporterons au programme que nous venons de faire vont porter sur la classe Game1. Nous venons d’énumérer trois types de cube. Commencez donc à déclarer ces cubes au tout début de cette classe :

private Cube _ground;
private Cube[] _trottoires;
private Cube[] _batiments;

 Ajoutez de même un ensemble de constantes qui nous éviterons de remplir notre code de valeur numériques incompréhensibles :

private static int NumberOfBatimentsOnASide = 5;
private static int BatimentSize = 50;
private static int TrottoireSize = 70;
private static int TrottoireHeight = 5;
private static int RoadWidth = 40;
private static int BatimentMinimalHeight = 50;

private static int BatimentMaximalHeight = 450;

Celles ci sont assez explicites pour ne pas avoir à être présentées. Le constructeur instanciera tous les cubes.

_ground = new Cube();
_trottoires = new Cube[NumberOfBatimentsOnASide * NumberOfBatimentsOnASide];
_batiments = new Cube[NumberOfBatimentsOnASide * NumberOfBatimentsOnASide];


Et la méthode InitializeCubes les chargera en mémoire


 

private void InitializeCubes(){    TerrainSize = (RoadWidth + TrottoireSize) * NumberOfBatimentsOnASide + RoadWidth;    Random random = new Random();     //sol    this._ground.Color = Color.Gray;    this._ground.Load(this.graphics.GraphicsDevice);    this._ground.SetSize(new Vector3(        TerrainSize,        TerrainSize,        1));    this._ground.SetPosition(new Vector3(-TerrainSize / 2, -TerrainSize / 2, 0));     //trottoires    for (int i = 0; i < NumberOfBatimentsOnASide; i++)    {        for (int j = 0; j < NumberOfBatimentsOnASide; j++)        {            this._trottoires[i * NumberOfBatimentsOnASide + j] = new Cube();             this._trottoires[i * NumberOfBatimentsOnASide + j].Color = Color.LightGray;            this._trottoires[i * NumberOfBatimentsOnASide + j].Load(this.graphics.GraphicsDevice);            this._trottoires[i * NumberOfBatimentsOnASide + j].SetSize(new Vector3(TrottoireSize, TrottoireSize, TrottoireHeight));             _trottoires[i * NumberOfBatimentsOnASide + j].SetPosition(new Vector3(RoadWidth + j * (TrottoireSize + RoadWidth) – TerrainSize/2 , RoadWidth+(TrottoireSize-BatimentSize)/2 + BatimentSize + i * (TrottoireSize + RoadWidth) – TerrainSize/2 , 0));        }    }     //grattes ciels     for (int i = 0; i < NumberOfBatimentsOnASide; i++)    {        for (int j = 0; j < NumberOfBatimentsOnASide; j++)        {            this._batiments[i * NumberOfBatimentsOnASide + j] = new Cube();             byte grayColorComposante = (byte)random.Next(0, 256);            this._batiments[i * NumberOfBatimentsOnASide + j].Color = new Color(grayColorComposante, grayColorComposante, grayColorComposante);            this._batiments[i * NumberOfBatimentsOnASide + j].Load(this.graphics.GraphicsDevice);            int size = random.Next(BatimentMinimalHeight, BatimentMaximalHeight);            this._batiments[i * NumberOfBatimentsOnASide + j].SetSize(new Vector3(BatimentSize, BatimentSize, size));            this._batiments[i * NumberOfBatimentsOnASide + j].SetPosition(new Vector3(BatimentSize + j * (BatimentSize + RoadWidth + (TrottoireSize – BatimentSize)) – TerrainSize / 2, RoadWidth + (TrottoireSize – BatimentSize) + BatimentSize + i * (TrottoireSize + RoadWidth) – TerrainSize / 2, 0));        }    }}   
Pour chaque cube nous :
  • Spécifions une couleur
  • Chargeons les données 3D en mémoire
  • Spécifions une taille
  • Specifions une position.

La taille et la position sont calculées en fonction des constantes déclarées au début de la classe. Ne reste plus que l’affichage dans la méthode Draw :

for (int i = 0; i < NumberOfBatimentsOnASide; i++){    for (int j = 0; j < NumberOfBatimentsOnASide; j++)    {         effect.Parameters[2].SetValue(this._trottoires[i * NumberOfBatimentsOnASide + j].Transformation);         foreach (EffectPass pass in effect.CurrentTechnique.Passes)        {            pass.Begin();            _trottoires[i * NumberOfBatimentsOnASide + j].Render();            pass.End();        }    }

}

Notons ici que nous spécifions la transformation world avant chaque affichage de cube.

       effect.Parameters[2].SetValue(this._batiments[i * NumberOfBatimentsOnASide + j].Transformation);

La valeur 2 correspond à l’index “xWorld” que nous utilisions précédemment.

La méthode Update met à jour la matrice View pour faire tourner la camera autour de la ville.

    // TODO: Add your update logic here

    int iTime = Environment.TickCount % 10000;    //passage en radian    float fAngle = iTime * (2.0f * (float)Math.PI) / 10000.0f;     // Mettre en place notre matrice de vue.     Matrix view = Matrix.CreateLookAt(new Vector3(500.0f * (float)Math.Cos(fAngle), 500.0f * (float)Math.Sin(fAngle), 510f + 500.0f * (float)Math.Sin(fAngle)), new Vector3(0.0f, -20.0f, 0f), new Vector3(0.0f, 0.0f, 1.0f));     this.effect.Parameters[0].SetValue(view);//0 equivalent de “xView”

Au final à l’affichage nous obtenons :

Comme exercice pour terminer sur ce premier monde de jeu, essayez de modifier la taille des trottoires, l’espacement entre les batiments et, plus difficile deplacer la caméra à l’aide du clavier.

telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   

Système solaire 

 Passons maintenant au second moteur de jeu : le système solaire. Nous allons aller ici beaucoup plus loin dans l’utilisation des matrices et d’un modèle objet. Notre but est de créer une vue de l’espace où apparaitra Mercure, Venus, La Terre, Mars et Jupiter. Nous ajouterons de même la Lune. Plusieurs points critiques seront abordés : le développement d’une classe Sphere, sur le modèle de la classe Cube pour afficher … une sphère. Un système d’héritage dans le mesure ou nous allons implémenter des dérivés de cette classe sphere et enfin des calculs Matriciels très poussés.

 A ce propos -NDA : je me repete mais c’est important- il est plus que necessaire de lire l’article qui se trouve ici, pour bien comprendre l’importance de l’ordre de la multiplication des matrices de transformations  (rotation, redimentionnement, translation) pour modifier l’affichage d’un objet à l’écran afin de vous éviter toute surprise et bug de rendu incompréhensible.

 La classe Sphere

La classe sphere est relativement similaire à la classe Cube. Le développeur doit toutefois spécifier deux informations à la création d’une sphere : le nombre d’anneaux (résolution verticale), et le nombre de segments (résolution horizontale). Ces deux données permettent de faire varier la qualité et la douceur des courbes du modèle 3D. Inutile d’afficher ici le code de la classe : une lecture suffit. Les points compliqués se situent dans les deux méthodes de création du VertexBuffer et de l’IndexBuffer. Nous utilisons là un code à base de formules trigonométrique pour générer notre spère. Ce que fait réellement le code là, n’est pas très important…

Maintenant nous sommes pret à créer un modèle objet. Nous suivrons le plan suivant :

Une classe Planet sera créée et héritera de Sphere. Elle implémentera en spécifique 4 Matrices :

  • Une matrice pour la distance entre la planete courante en tant que sattelite et la planete principale (terre<->soleil)
  • Une matrice pour la rotation de la planète sur elle même.
  • Une matrice pour la révolution de la planète courant en tant que sattelite autour de la planète principale.
  • Une matrice résultat qui contiendra le résultat de toutes ces transformation et qui sera appliquée à la matrice World.

 Elle implémentera 2 constantes sur lesquels se baseront les calculs pour les mouvement de chaque planète. Ces constantes correspondrons au temps de rotation de la terre (jour) et au temps de révolution de la terre (année). Elle implémentera une propertie Transformation qui sera une surcharge obscurante de la propriété du même nom héritée. Cette propriété renverra le contenu de la matrice résultat des transformations de la planète courante. Enfin cette classe oblige toutes les classes filles à implémenter une méthode Update. Cette méthode contiendra le code qui mettra à jour cette matrice résultat.

La classe Planet se présente ainsi :

public abstract class Planete : Sphere{    internal Matrix planetMatrix, moveMatrix, rotationMatrix, revolutionMatrix;      public const float DayTime = 10;    public const float YearTime = DayTime * 365.0f;     public new Matrix Transformation    {        get        {            return planetMatrix;        }    }     public Planete()        : base(30, 30)    {     }     public abstract void Update(GameTime gameTime);} 

Septs planètes hériteront de Planete : Sun, Mercure, Venus, Earth, Moon, Mars, Jupiter. Nous étudierons deux d’entre elles : Earth et Mercure. Earth se présente ainsi :

public class Earth : Planete{     public Earth()    {         this.Color = Microsoft.Xna.Framework.Graphics.Color.Blue;    }     public override void Update(GameTime gameTime)    {        rotationMatrix = Matrix.CreateRotationY(Environment.TickCount / DayTime);//une rotation par jour        revolutionMatrix = Matrix.CreateRotationY(Environment.TickCount / YearTime);//365 rotation par an        moveMatrix = Matrix.CreateTranslation(20.0f, 0.0f, 0.0f);//58 milllions de km de distance du soleil        Matrix pivotMatrix = Matrix.CreateRotationZ(23.44f * (float)Math.PI / 180);//obliquité de l’axe de rotation         planetMatrix = Matrix.CreateScale(1f, 1f, 1f) * rotationMatrix * pivotMatrix;//création d’une seule matrice pour tous ces calculs        planetMatrix *= moveMatrix;        planetMatrix *= revolutionMatrix;    }

}

La classe Earth offre une spécificité par rapport à la classe Planete, en donnant une couleur bleue au modèle 3D (dans le constructeur) et en affectant une valeur à chacune de nos matrices (dans la méthode Update).

rotationMatrix = Matrix.CreateRotationY(Environment.TickCount / DayTime);

revolutionMatrix = Matrix.CreateRotationY(Environment.TickCount / YearTime);

moveMatrix = Matrix.CreateTranslation(20.0f, 0.0f, 0.0f);

Matrix pivotMatrix = Matrix.CreateRotationZ(23.44f * (float)Math.PI / 180);

planetMatrix = Matrix.CreateScale(1f, 1f, 1f) * rotationMatrix * pivotMatrix;

planetMatrix *= moveMatrix;planetMatrix *= revolutionMatrix;

La première matrice correspond à la rotation de la terre autour d’elle-même. Nous divisons le temps passé par la constant correspondant à un jour terrestre. La seconde matrice correspond au temps de révolution de la terre autour du soleil. Même calcul mais par rapport à une année terrestre. La troisième matrice spécifie la distance terre / soleil. La matrice suivant spécifie l’obiquité de la terre (la terre est légerement penchée de 23° sur l’axe des pôles). Nous créons ensuite une matrice pour dimensionner la terre (la terre est la planète sur laquelle nous basons nos calculs, donc ici aucun redimentionnement). Les trois dernières instructions multiplient ces matrices entre elles pour affecter la matrice résultat. Si vous avez lu l’article sur l’importance de l’ordre des multiplications de matrices vous devez porter une attention particulière à ces opérations. Après l’instruction :

planetMatrix = Matrix.CreateScale(1f, 1f, 1f) * rotationMatrix * pivotMatrix;

La matrice résultat permet de faire tourner la terre, en lui donnant une obiquité et une taille. La terre ne tourne pas autour du soleil et se trouve au centre de l’univers (haaa Aristote…). Après

planetMatrix *= moveMatrix;

La terre se trouve à une distance de 20 du soleil, tourne sur elle même et possède une taille et une obiquité. Mais elle ne tourne pas autour du soleil (nous nous rapprochons de Galilé toutefois). C’est ce que nous obtenons avec

planetMatrix *= revolutionMatrix;

Nous affectons donc ici une rotation autour d’un cercle imaginaire de 20 de rayon (multiplication précédente d’une matrice de translation de 20 par une matrice de rotation).

Regardons maintenant la classe Mercure.

public class Mercure : Planete{     public Mercure()    {         this.Color = Microsoft.Xna.Framework.Graphics.Color.LightGray;    }      public override void Update(GameTime gameTime)    {        rotationMatrix = Matrix.CreateRotationY(Environment.TickCount / DayTime * 58.6f);//une rotation par jour        revolutionMatrix = Matrix.CreateRotationY(Environment.TickCount / YearTime * 1.6f);//365 rotation par an        Matrix moveMatrix = Matrix.CreateTranslation(7.0f, 0.0f, 0.0f);//58 milllions de km de distance du soleil        Matrix pivotMatrix = Matrix.CreateRotationZ(0.0f * (float)Math.PI / 180);//obliquité de l’axe de rotation        planetMatrix = Matrix.CreateScale(0.4f, 0.4f, 0.4f) * rotationMatrix * pivotMatrix;//création d’une seule matrice pour tous ces calculs        planetMatrix *= moveMatrix;        planetMatrix *= revolutionMatrix;    }

}

Mercure possèdera une couleur gris clair. Nous savons qu’elle tourne atour de son axe en 58,6 jours. Qu’elle effectue 1,6 révolution pendant un an. Nous la plaçons a une distance de 7 du soleil. Elle n’a aucun obiquité. Elle fait 0,4 fois la taille de la terre. Le contenu de la méthode Update coule de source si la méthode Update de Earth a été comprise. Jettez encore un oeil au contenu de la classe Moon. Sa méthode Update suit le même principe que Earth et Mercure et ajoute deux calculs en plus :

 

            planetMatrix *= this._earth.moveMatrix;            planetMatrix *= this._earth.revolutionMatrix;

A ce stade la lune ne se trouve pas autour de la terre mais autour du soleil bien que les calculs précédents soient bons. Nous devons faire une translation correspondant à la distance terre / soleil. C’est ce que fait la première instruction. De même, tout en tournant autour de la terre, la lune tourne forcement autour du soleil puisque la terre le fait. C’est la raison de la seconde instruction affichée ici.

Etudions maintenant le contenu de la classe Game1. Nous commonçons déjà par déclarer les 7 planètes que nous manipulons ici.

private Sun _sun;
private Earth _earth;
private Mercure _mercure;
private Mars _mars;
private Venus _venus;
private Jupiter _jupiter;

private Moon _moon;

Après création, elles sont ajoutées dans une liste générique afin de simplifier le code par la suite.

_sun = new Sun();_earth = new Earth();_mercure = new Mercure();_mars = new Mars();_venus = new Venus();_jupiter = new Jupiter();_moon = new Moon(_earth);_planetes.AddRange(new Planete[] { _sun, _earth, _mercure, _mars, _venus, _jupiter, _moon }); Vient ensuite les étapes traditionnelles de chargement à l’aide de la méthode Load et d’affichage avec Render. Notons que la méthode Update boucle sur toutes les planètes et appelle leur méthode Update pour permettre de mettre à jour leur mouvement.

 A l’exécution vous obtenez :

 Pas très beau, mais très instructif. Là encore, comme pour le moteur de ville, nous l’améliorerons au fil des tutoriaux qui vont suivre. Nous l’utiliserons dans notre apprentissage de la lumière et des effets spéciaux. Voici au final ce que donne notre moteur spacial :

 

 Notre moteur spacial est fini ! Comme exercice, je ne saurais trop vous conseiller d’ajouter Jupiter, et un satellite de Jupiter. De même pourquoi ne pas exporter notre variable effet, au niveau de la classe Sphere ?

 

 telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   

Conclusion

La première étape de notre apprentissage est désormais terminée. Si cette étape portait sur les bases du développement 3D, l’étape qui vient portera sur le réalisme. Si vous jetez un oeil aux deux images précédentes (moteur spacial) vous comprendrez que ce mot prend une signification importante en 3D. Ce que nous avons vu jusqu’ici, porte sur des notions qui sont à la base de n’importe quel développement d’application 3D. Bien les comprendre, bien les maitriser, c’est assurer une base stable, saine et reutilisable pour ses futures productions. Nous aborderons maintenant le texturing, les Lumières, les effets de brouillard, le texte en 3D, les particules, les Meshs …

 

telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   

 

[Soon]

Valentin

[Help]

Retourner au sommaire des cours 

18 thoughts on “XNA Tutorial 7 : Cas concret, l’affichage d’une ville et du système solaire”

  1. Magnifique tutos,

    clair, relativement simple quand on a les bases mathématiques, je n’ai qu’une chose à dire

    vivement la suite…

  2. Bonjour,

    c’est un diagramme de classe réalisé à partir de Visual Studio tout simplement. Malheureusement la version Express de Visual Studio ne le permet pas.

    Merci pour tes remarques.

  3. il me semble qu’il y a deux problèmes :

    – les sources exemples en téléchargement ne sont pas les bonnes

    – Je ne comprend pas exactement comment fonctionne la transformations du cube. Chez moi,les objet cube fabriqué par cette classe on bien les bonnes propriétés en ce qui concerne la position et les dimensions mais le cube qui s’affiche lors de l’execution n’en tien pas compte, quelle que soit les dimensions de mon cube j’ai toujours le meme cube.

    en plus explicite ca donne :

    dans la methode Render(), nous avons : ” this._device.Vertices[0].SetSource(this._vertexBuffer, 0, VertexPositionColor.SizeInBytes); ” qui reprend les vertices initialisé par la methode InitializeVertices.
    Mais meme si nous avons changé la valeur des attributs _width,_height_depth ,_x, _y, et _z notre cube est dessiné avec les vertices qui eu n’ont pas changé …

    ? je me trompe peut être mais chez moi ca ne marche pas :(

    une petite aide ? :)
    merci par avance

  4. ah ok j’allais te répondre

    c’est sûr que ca vient pas de moi ? parceque si ca t’a posé probleme je peux prendre ca plus clair peut être …

  5. XNA c’est bien mais ca fait mal a la tete quand ca marche pas!

    j’ai beau tout essayer mais ca ne marche toujours pas !!!
    meme les sources telechargées sur ce site ne tournent pas chez moi !!
    lorsque de la méthode render de l’objet cube , j’ai cela lors de la dernière instruction :
    L’exception System.InvalidOperationException n’a pas été gérée
    Message=”The method call is invalid.”
    Source=”Microsoft.Xna.Framework”

    je pense que cela vient du fichier effet car quand j’utilise ” this.effect.CurrentTechnique = effect.Techniques["Colored"]; “, j’ai cette erreur. Par contre si j’utilise ” this.effect.CurrentTechnique = effect.Techniques["Pretransformed"]; “, je n’ai plus d’erreur mais l’affichage n’est pas celui attendu (le quart haut-droit de ma fenêtre est gris et le reste bleu.

    je craque… je pense qu’il faut que je dirige vers les tuto HLSL !!!

  6. On ne change pas le type d’affichage comme ça. Passer de Pretransformed à Colored necessite de changer le type de vertices.

    Tu as ca sur quel projet ? la ville ou le système solaire ?

    pour le carré haut gris c’est normal. Le pretransformed place tes vertices par rapport à la fenêtre d’affichage. Ce que tu vois c’est une partie du sol qui commence en 0,0,0 soit le centre de la fenêtre. Pretransformed c’était dans le premier exemple, après ça, tu ne le verras plus jamais :)

  7. Salut !

    Je confirme un problème avec les sources en téléchargement de l’affichage des cubes : j’ai voulu réutiliser ta méthode InitialzeVertices() dans un programme perso, et j’ai la même erreur RunTime que quand on essaie de compiler le projet mis à disposition : à l’exécution de

    vertexBuffer = new VertexBuffer(
    _device,
    typeof(VertexPositionColor),
    8,
    ResourceUsage.WriteOnly,
    ResourceManagementMode.Automatic);

    Le debugger signale une exception :

    “Object reference not set to an instance of an object.”

    Manifestement ça vient de _device.

    J’ai voulu tenter de régler le problème en lui entrant de force (à la main) un device quelconque, sous la forme

    “_device = MonDevice”

    , mais je ne comprends pas ce que contient la classe GraphicDevice, donc je stagne.

  8. Salut Valentin,

    voilà des nouvelles du front : il semblerait que tout bêtement on n’ait pas défini _device dans la méthode Load(). _device est de fait effectivement vide, et le VertexBuffer ne peut pas être créé.

    Il ne reste plus qu’à trouver ce par quoi tu voulais initialiser _device.

    (Au passage, merci à toi de t’être repenché sur la question si longtemps après l’élaboration de ce tuto).

  9. Ca y est, j’ai trouvé !

    Malheureusement, la solution à mon problème (j’utilisais _device avant la méthode Game1.Initialize(), ce qui fait qu’il n’était pas instancié) ne résout pas celui du projet de Valentin.

  10. Encore une fois, je ne comprend pas vraiment ton soucis, il faut que tu sois plus explicite, ou que tu m’envoie ton projet

  11. Non, non, mon problème à moi est parfaitement réglé ! Par contre je suis toujours dans l’incapacité totale de débugger ton projet d’affichage d’une ville.

  12. aaaah on avance :) bon alors voyons le soucis :
    qu’entends tu par “l’incapacité totale de débugger” ?

    quand tu lance le projet avec F5 ca ne se lance pas ?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>