Youtube fourmille de démonstrations de ce qu'on l'on peut faire avec ce genre de scrollings, et je vous en ai sélectionné deux exemples:

Le second exemple est une variante, et ne permet pas un scrolling infini, comme le premier, toutefois le principe de base reste le même. Nous allons donc aujourd'hui apprendre à réaliser ce genre de défilement avec XNA, et en créer un composant réutilisable dans nos jeux.

Les principes du parallax side scrolling

Animation d'exemple de parallax side scrolling

Une illustration de l'effet que l'on souhaite obtenir.

Les pointillés représentent la limite de la texture.

Nous allons réutiliser la classe SpriteBatch dont on avait expliqué le fonctionnement de base lors de notre tutoriel sur le SplashScreen. Sans rentrer dans les détails ici, SpriteBatch permet l'affichage d'une texture à l'écran.

Problème: l'effet que nous allons mettre en œuvre nécessite d'afficher plusieurs textures côte à côte, en répétition, chose que l'on nomme le tiling.

Toutefois, la classe SpriteBatch ne permet pas d'effectuer, par défaut, cette répétition de texture. L'on pourrait effectuer plusieurs fois le rendu de la texture l'une a coté de l'autre, mais ce n'est pas la méthode la plus efficace.

Il existe en effet un moyen de contourner cette limitation: la technique nous est donnée dans la documentation MSDN de Microsoft.

L'illustration ci dessous montre ce que l'utilisateur ne voit pas dans le système de défilement: en effet, une grosse partie est cachée, ce qui nous permet de simuler un défilement infini en faisant sauter les textures aux endroits nécessaires:

En regardant l'image telle quelle, et en voyant le fonctionnement interne, on a la désagréable sensation que l'image "saute"; toutefois, cette sensation n'arrive que lorsque l'utilisateur voit les calques sauter; en effet, la petite illustration à gauche est exactement la même que celle ci-dessus!

Plus de détails sur le tiling de textures

Nous allons donc devoir répéter un certain nombre de fois les textures pour pouvoir les déplacer sans que l'utilisateur final ne remarque rien. La seule question est: combien de fois doit on répéter une texture?

Pour répondre à cette question, nous devons nous dire qu'à un certain point, on doit pouvoir faire déplacer la texture sans que l'utilisateur ne remarque rien. Il faut donc déjà que la texture soit périodique (qu'elle se répète) et que donc la couleur des pixels de son bord gauche doit correspondre à la couleur des pixels de son bord droit.

Si, en plus, on veut pouvoir défiler dans l'autre direction, le même raisonnement doit être appliqué au bord haut et bas de la texture (on peut d'ailleurs étendre le raisonnement et seulement effectuer un défilement vertical plutôt que horizontal, le principe reste le même).

Supposons maintenant que nous souhaitions faire un tiling horizontal, avec des textures de 512 pixels de large et de 1024 pixels de haut, sur un écran full HD 1920x1020 pixels: dans 1920 pixels, on peut mettre 3.75 fois la texture en largeur. Nous sommes obligés de prendre la valeur directement supérieure (nous sommes obligé de prendre un multiple de 512 pour que les bords de la texture correspondent) donc 4 fois.

N'oublions pas non plus qu'il faut absolument rajouter une largeur supplémentaire (marge de manœuvre), sinon nous allons à un moment ou a un autre faire un trou lorsque nous ferons déplacer la texture. La formule pour connaitre la largeur de répétition est donc:

width = (Math.Ceil( game.Window.ClientBounds.Width / texture.Width ) + 1) * texture.Width;

On peut optimiser cette formule, car les largeurs et les longueurs sont exprimés dans le type entier (les divisions sont donc toujours tronquées à l'entier inférieur. Nous avons donc:

width = ((game.Window.ClientBounds.Width / texture.Width) + 2) * texture.Width;

Dans notre cas, cela fera: 512 x 5 = 2560 pixels. Le même principe peut être appliqué pour les défilements verticaux.

Le composant ParallaxSideScroller

Maintenant que nous avons vu une partie de la théorie sur le dimensionnement des textures, il nous faut commencer à coder la classe qui va gérer l'affichage et le positionnement de ces dernières.

Nous allons implémenter le composant ParallaxSideScroller en deux parties distinctes. D'abord le composant en lui même, qui sert de couche de haut niveau et d'interfacage avec le jeu ou les autres composants, et la classe utilitaire ScrollingLayer, qui s'occupe de gérer un calque spécifique.

  • Mode texte
  • Copier
  1. namespace XnaConnection.Demos.ParallaxSideScrolling.Components.Generic.Graphics
  2. {
  3. #region Using statements
  4.  
  5. using System;
  6. using System.Collections.Generic;
  7. using Microsoft.Xna.Framework;
  8. using Microsoft.Xna.Framework.Graphics;
  9. using XnaConnection.Components.Graphics;
  10.  
  11. #endregion
  12.  
  13. /// <summary>
  14. /// This is a GameComponent that implements a parallax side scrolling system
  15. /// </summary>
  16. public class ParallaxSideScroller : Base2DGameComponent
  17. {
  18. #region Fields
  19.  
  20. /// <summary>
  21. /// Does the scrolling component allow to loop texture horizontally?
  22. /// </summary>
  23. private bool constraintOnX;
  24.  
  25. /// <summary>
  26. /// Does the scrolling component allow to loop texture vertically?
  27. /// </summary>
  28. private bool constraintOnY;
  29.  
  30. /// <summary>
  31. /// Stores the list of layers
  32. /// </summary>
  33. private List<ScrollingLayer> layers;
  34.  
  35. #endregion

Nous créons donc trois champs dans notre classe. Le champ le plus évident est List<ScrollingLayer> qui va stocker les références à tous les calques que nous allons utiliser. Les deux booléens ont une fonction très simple: ils permettent de limiter le défilement dans une direction, ou l'autre (ou même les deux à la fois), en fonction de la taille de l'image de fond.

Cela ne limite donc pas le déplacement sur l'axe en question, mais cela permet d'éviter de reboucler la texture sur l'axe en question (la vidéo de démonstration en bas de ce tutoriel montre les effets de la limitation, et une vidéo vaut mieux qu'un grand discours).

On voit aussi que nous réutilisons la classe Base2DGameComponent que nous avions vu lors de notre premier tutoriel sur un composant graphique. Je vous laisse y référer pour plus de détails sur le fonctionnement de cette classe.

  • Mode texte
  • Copier
  1. #region Constructors
  2.  
  3. /// <summary>
  4. /// Initializes a new instance of the ParallaxSideScroller class.
  5. /// </summary>
  6. /// <param name="game">The game class that will use the ParallaxSideScroller</param>
  7. /// <param name="constraintOnX">Should the X movement be constrained?</param>
  8. /// <param name="constraintOnY">Should the Y movement be constrained?</param>
  9. public ParallaxSideScroller(Game game, bool constraintOnX, bool constraintOnY) : base(game)
  10. {
  11. this.layers = new List<ScrollingLayer>();
  12.  
  13. this.constraintOnX = constraintOnX;
  14. this.constraintOnY = constraintOnY;
  15. }
  16.  
  17. #endregion

Pas grand chose à dire sur le constructeur de notre classe. On initialise la liste des calques et on assigne les deux booléens de contrainte sur les axes.

  • Mode texte
  • Copier
  1. #region Properties
  2.  
  3. /// <summary>
  4. /// Gets a value indicating whenever the component allow a texture loop on X-Axis
  5. /// </summary>
  6. public bool ConstraintOnX
  7. {
  8. get { return this.constraintOnX; }
  9. }
  10.  
  11. /// <summary>
  12. /// Gets a value indicating whenever the component allow a texture loop on Y-Axis
  13. /// </summary>
  14. public bool ConstraintOnY
  15. {
  16. get { return this.constraintOnY; }
  17. }
  18.  
  19. #endregion

Encore une fois ici, pas grand chose à dire: nous laissons les booléens de contrainte en lecture seule pour les autres classes. Ces propriétés seront notamment utilisées par les classes ScrollingLayer.

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Adds a new layer to the parallax side scroller
  3. /// </summary>
  4. /// <param name="asset">The texture asset name</param>
  5. public void Add(string asset)
  6. {
  7. // Add the layer
  8. this.layers.Add(new ScrollingLayer(this, this.Content.Load<Texture2D>(asset), 1f / (float)Math.Pow(2, this.layers.Count)));
  9.  
  10. // Sort the layers by Depth property
  11. this.layers.Sort(delegate(ScrollingLayer a, ScrollingLayer b)
  12. {
  13. return a.Depth > b.Depth ? 1 : a.Depth < b.Depth ? -1: 0;
  14. });
  15. }

La méthode Add() de la classe ParallaxSideScroller permet d'ajouter un nouveau calque de défilement à notre système. La classe ScrollingLayer nécessite plusieurs paramètres en entrée pour fonctionner:

  • La classe ParallaxSideScroller courante (pour l'accès aux propriétés de contraintes, mais aussi pour l'accès à la classe Game).
  • L'objet Texture2D chargée par le ContentManager.
  • Et une propriété flottante depth qui indique la "profondeur" visuelle du calque. Cette propriété sert notamment à indiquer le coefficient multiplicateur à utiliser lorsque qu'on applique un mouvement à nos calques (c'est la différence de vitesse entre les calques qui donne l'impression de profondeur). Dans notre cas, le coefficient multiplicateur démarre à 1 pour le premier calque et est ensuite à chaque fois divisé par deux pour les calques suivants.

Il nous reste encore à trier les calques ajoutés dans l'ordre décroissant de leur profondeur (on affiche les calques les moins profonds en premier), chose faite par le delegate appliqué à la méthode Sort() de la liste.

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Draws all layers
  3. /// </summary>
  4. /// <param name="gameTime">Provides a snapshot of game times values</param>
  5. public override void Draw(GameTime gameTime)
  6. {
  7. // See the <cref="http://msdn.microsoft.com/en-us/library/bb975153.aspx">MSDN</cref> for this trick
  8. this.Renderer.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
  9.  
  10. // Force texture wrapping
  11. this.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
  12. this.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
  13.  
  14. // Loop each layer and renders them
  15. foreach (ScrollingLayer layer in this.layers)
  16. {
  17. this.Renderer.Draw(layer.Texture, layer.Position, new Rectangle(0,0,layer.Width, layer.Height), Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 1.0f);
  18. }
  19.  
  20. // Stop the rendering sequence
  21. this.Renderer.End();
  22. }

La méthode Draw() de notre composant va être chargé de faire le rendu de chaque calque en bouclant la liste des ScrollingLayer. Comme je l'avais évoqué plus haut, par défaut, la classe SpriteBatch n'est pas capable de faire des répétitions de textures en un seul appel.

Nous utilisons donc l'astuce fournie dans la MSDN pour effectuer du tiling. Nous passons en effet le paramètre SpriteSortMode.Immediate à l'initialisation de la passe de rendu, ce qui a pour effet d'appliquer les options personnelles du GraphicsDevice (par défaut, le SpriteBatch effectue des opérations complexes afin d'optimiser le rendu entre les méthodes Begin() et End(), en appliquant une configuration par défaut du GraphicsDevice).

Bien que théoriquement plus lente, l'utilisation de SpriteSortMode.Immediate dans notre cas accélère le rendu, car au lieu d'effectuer plusieurs appel pour faire le rendu de la texture côte à côte, un seul appel est effectué.

Je reviens toutefois sur ces deux lignes, qui nécessitent quelques explications:

  • Mode texte
  • Copier
  1. // Force texture wrapping
  2. this.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
  3. this.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

Le GraphicsDevice représente la carte graphique, qui elle même est une machine à états. Quand on effectue un rendu de quelque chose à l'écran, on règle les options (états) que l'on souhaite et une fois réglée, on invoque la méthode de rendu de la carte graphique (c'est version simplifiée de la chose, mais vous n'avez pas besoin d'en savoir plus pour le moment).

Il existe des myriades d'options pour les rendus, qui ont été élaborés au fil des conceptions de cartes graphiques (et les lister ici n'est clairement pas le but du tutoriel). Toutefois, je vais quand même expliquer les deux options utilisées ici.

On voit que dans notre cas, nous réglons la propriété AddressU et AddressV du premier SamplerState en mode TextureAddressMode.Wrap. Un sampler est en fait une petite partie de la puce de la carte graphique qui s'occupe de gérer les textures. Le SamplerState est donc la classe qui gère les états de ce sampler et contient tous les paramètres de rendu de la texture.

Une carte graphique dispose de plusieurs samplers (les premières n'en avaient que un) mais la plupart du temps, nous utiliserons toujours le premier sampler (les autres sont utilisés dans le cas de multi-texturing).

Au sein de la carte graphique, les coordonnées de la texture sont adressées via des vecteurs (représentés par la structure Vector2D en XNA) comme pour des coordonnées écrans. Les propriétés AddressU et AddressV indiquent comment ce vecteur doit se comporter quand il sort des coordonnées de la texture.

TextureAddressMode.Wrap va lui dire d'effectuer un modulo sur les coordonnées. Cela veut dire qu'un pixel à la coordonnée (520;10) sur une texture de 512x512 pixels aura la même couleur que le pixel à la coordonnée (8;10).

C'est cette propriété qui nous permet de reboucler la texture indéfiniment et ainsi, faire ce tiling tant souhaité. D'autres méthodes d'adressages existent et je vous conseille d'aller jeter un coup d'œil sur l'énumération TextureAddressMode pour en savoir plus.

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Moves each layer in a particuliar direction
  3. /// </summary>
  4. /// <param name="direction">The direction where to move the layer</param>
  5. public void MoveBy(Vector2 direction)
  6. {
  7. foreach (ScrollingLayer layer in this.layers)
  8. {
  9. layer.MoveBy(direction);
  10. }
  11. }
  12.  
  13. #endregion
  14. }
  15. }

Nous finissons notre classe par la méthode MoveBy() qui va décaler tous les calques dans une certaine direction. Elle ne fait que transmettre le paramètre à chaque calque. Ce sera au calque, en fonction de sa profondeur et de sa position courante, de gérer son changement de position.

La classe utilitaire ScrollingLayer

La classe ScrollingLayer représente un calque de notre système de scrolling. Une grosse partie mathématique se retrouve dans cette classe qui est notamment chargée de positionner les calques correctement.
  • Mode texte
  • Copier
  1. namespace XnaConnection.Demos.ParallaxSideScrolling.Components.Generic.Graphics
  2. {
  3. #region Using statements
  4.  
  5. using Microsoft.Xna.Framework;
  6. using Microsoft.Xna.Framework.Graphics;
  7.  
  8. #endregion
  9.  
  10. public class ScrollingLayer
  11. {
  12. #region Fields
  13.  
  14. /// <summary>
  15. /// Stores the depth of the layer
  16. /// </summary>
  17. private float depth;
  18.  
  19. /// <summary>
  20. /// Stores the top left position of the texture
  21. /// </summary>
  22. private Vector2 position;
  23.  
  24. /// <summary>
  25. /// Stores the scroller object needed to compute the size of the scrolling layer
  26. /// </summary>
  27. private ParallaxSideScroller scroller;
  28.  
  29. /// <summary>
  30. /// Stores the texture for the layer
  31. /// </summary>
  32. private Texture2D texture;
  33.  
  34. #endregion
  35.  
  36. #region Constructors
  37.  
  38. /// <summary>
  39. /// Initializes a new instance of the ScrollingLayer class.
  40. /// </summary>
  41. /// <param name="scroller">The ParallaxSideScroller object that contains this ScrollingLayer</param>
  42. /// <param name="texture">The texture for the layer</param>
  43. /// <param name="depth">The depth of the layer. Should not equals to 0</param>
  44. public ScrollingLayer(ParallaxSideScroller scroller, Texture2D texture, float depth)
  45. {
  46. this.depth = depth;
  47. this.scroller = scroller;
  48. this.texture = texture;
  49. this.position = this.Middle;
  50. }
  51.  
  52. #endregion

Aucune surprise ici, nous retrouvons nos champs et nos paramètres que nous avions vu lors de la méthode Add() du composant ParallaxScrollingLayer. Le seul point important à noter est que nous positionnons notre texture sur l'écran grâce à la propriété Middle de notre classe (voir ci-dessous).

  • Mode texte
  • Copier
  1. #region Properties
  2.  
  3. /// <summary>
  4. /// Gets the depth of the layer
  5. /// </summary>
  6. public float Depth
  7. {
  8. get { return this.depth; }
  9. }
  10.  
  11. /// <summary>
  12. /// Gets the height of the tiled texture
  13. /// </summary>
  14. public int Height
  15. {
  16. get { return ((this.scroller.Game.Window.ClientBounds.Height / this.texture.Height) + 2) * this.texture.Height; }
  17. }
  18.  
  19. /// <summary>
  20. /// Gets the position of the texture to set it in middle of the screen, in pixels
  21. /// </summary>
  22. private Vector2 Middle
  23. {
  24. get
  25. {
  26. float x = Modulate((this.scroller.Game.Window.ClientBounds.Width - this.texture.Width) / 2f, this.Texture.Width);
  27. float y = Modulate((this.scroller.Game.Window.ClientBounds.Height - this.texture.Height) / 2f, this.Texture.Height);
  28. return new Vector2(x, y);
  29. }
  30. }
  31.  
  32. /// <summary>
  33. /// Gets the position of the tiled texture
  34. /// </summary>
  35. {
  36. get { return this.position; }
  37. }
  38.  
  39. /// <summary>
  40. /// Gets the texture of the layer
  41. /// </summary>
  42. {
  43. get { return this.texture; }
  44. }
  45.  
  46. /// <summary>
  47. /// Gets the width of the tiled texture
  48. /// </summary>
  49. public int Width
  50. {
  51. get { return ((this.scroller.Game.Window.ClientBounds.Width / this.texture.Width) + 2) * this.texture.Width; }
  52. }
  53.  
  54. #endregion

La plupart des propriétés de notre classe ne sont que des accesseurs en lecture seule vers les champs de la classe. Toutefois, trois propriétés méritent qu'on s'attarde un peu sur elles: Middle, Width et Height.

Width et Height représentent la largeur et la hauteur totale de la texture, en pixels, et avec les répétitions. Nous avions vu au début du tutoriel comment calculer cette largeur et cette hauteur afin que toute la surface de l'écran soit recouverte par la texture.

La propriété Middle indique les coordonnées du coin supérieur gauche de la texture de telle sorte à ce que la texture soit positionnée au milieu de l'écran.

Supposons que nous ayons une texture de 512x1024: la coordonnée Y de cette texture, de telle sorte que le point central de la texture (aux coordonnées 256;512) soit au centre de l'écran (aux coordonnées 400x300 sur un écran par défaut de 800x600) est correspond donc à:

window.Height / 2 - texture.Height / 2

Ce qui peut être refactorisé en:

(window.Height - texture.Height) / 2

Le même raisonnement peut être appliqué pour la position gauche de notre texture. Les coordonnées finales sont donc:

((800-512)/2;(600-1024)/2) = (144;-212)

Normalement, les coordonnées doivent toujours être comprises entre -texture.Width et 0 pour les abscisses et -texture.Height et 0 pour les ordonnées pour éviter d'avoir un trou dans le rendu (voir la seconde figure illustrative). Il nous faut donc encore moduler notre résultat par la largeur et la hauteur de la texture, ce qui nous donne au final les coordonnées (-368;-212).

Dans notre code, nous utilisons la méthode Modulate, privée et statique, dont la signature est la suivante:

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Modulates coordinates so the coordinates are in the [-modulo;0] interval
  3. /// </summary>
  4. /// <param name="value">The value to modulate</param>
  5. /// <param name="modulo">The interval</param>
  6. /// <returns></returns>
  7. private static float Modulate(float value, float modulo)
  8. {
  9. return ((value - modulo) % modulo);
  10. }

Nous devons maintenant nous attaquer au gros morceau mathématique de la classe, à savoir la fonction MoveBy():

  • Mode texte
  • Copier
  1. #region Methods
  2.  
  3. /// <summary>
  4. /// Move the layer in a direction, wrapping where necessary
  5. /// </summary>
  6. /// <param name="direction">The direction of the movement</param>
  7. public void MoveBy(Vector2 direction)
  8. {
  9. this.position += direction * this.depth;
  10.  
  11. if (this.scroller.ConstraintOnX)
  12. {
  13. this.position.X = MathHelper.Clamp(this.Position.X, this.Middle.X + (this.Middle.X * this.depth), this.Middle.X - (this.Middle.X * this.depth));
  14. }
  15.  
  16. if (this.scroller.ConstraintOnY)
  17. {
  18. this.position.Y = MathHelper.Clamp(this.Position.Y, this.Middle.Y + (this.Middle.Y * this.depth), this.Middle.Y - (this.Middle.Y * this.depth));
  19. }
  20.  
  21. // Re-position the texture with a modulo
  22. this.position.X = (this.Position.X - this.Texture.Width) % this.Texture.Width;
  23. this.position.Y = (this.Position.Y - this.Texture.Height) % this.Texture.Height;
  24. }
  25.  
  26. #endregion

La méthode MoveBy() de notre classe s'occupe non seulement de positionner correctement la texture, mais aussi de contraindre ses mouvements.

La première étape consiste à rajouter la direction passée en paramètre à notre position. La direction est multipliée par le scalaire depth, afin que les couches les plus éloignées (et qui ont donc une valeur depth de plus en plus petite) se déplacent plus lentement.

Viennent ensuite les contraintes de mouvement: nous limitons, suivant l'état des booléens de contrainte de notre classe ParallaxSideScroller, la coordonnée X ou Y de la position grâce à la méthode d'aide MathHelper.Clamp().

Les valeurs minimum et maximum du Clamp doivent être expliquées, car la formule n'est pas forcément très triviale: pour chaque calque, suivant sa profondeur nous voulons limiter son déplacement par rapport au centre de la texture. La première couche (ayant une valeur depth de 1) doit donc pouvoir bouger à partir du centre, de toute sa hauteur.

Or, on se rappelle que les coordonnées de Middle représente la position de la texture telle qu'elle est positionnée au centre (donc à mi-hauteur et a mi-largeur), mais en tenant compte du décalage nécessaire à son positionnement hors écran!

Le maximum étant obtenu lorsque la texture est a sa coordonnée 0 (quand le haut de la texture touche le haut de l'écran), nous avons donc la formule, pour le maximum:

Middle - Middle * depth

La multiplication par le coefficient depth nous assure que les calques étant à une profondeur plus important que notre premier calque s'arrêteront de bouger quand le premier calque aura atteint ses limites. N'oublions pas non plus que Middle est un nombre négatif, c'est pourquoi nous faisons une soustraction pour avoir le maximum à 0. Dans notre cas précédent, si l'on remplace par des chiffres, cela donnerait:

-212 - (-212) * 1 = 0

Vu que Middle représente les coordonnées de la texture centrée, la borne minimum est donc:

Middle + Middle * depth

Ce qui, si l'on remplace par des chiffres:

-212 +(-212) * 1 = -424

Ce chiffre peut sembler peu intuitif de prime abord, mais correspond tout simplement à l'amplitude de mouvement de la texture sur l'axe des ordonnées. En effet, su un écran de 600 pixels de haut, si notre texture fait 1024, nous pouvons bouger de 424 pixels! On retombe donc sur nos pieds, comme par magie.

Nous avons maintenant fini notre classe qui s'occupe de gérer un des calques de notre composant. Il ne nous reste plus qu'à nous occuper de l'implémentation de celui ci au sein d'un jeu.

Initialisation de notre composant

Il ne nous reste plus qu'à initialiser le composant. Généralement cette initialisation se fera dans un autre composant qui utilise notre ParallaxSideScroller (en effet, il faut piloter le composant grâce à notre méthode MoveBy()), mais ici, pour la démonstration, nous allons directement l'initialiser dans la classe Game:

  • Mode texte
  • Copier
  1. protected override void Initialize()
  2. {
  3. // Initializes a new parallax side scroller, and allow only a scroll on X axis
  4. this.scroller = new ParallaxSideScroller(this, false, true);
  5.  
  6. // Add three textures as layers
  7. this.scroller.Add("Layer1");
  8. this.scroller.Add("Layer2");
  9. this.scroller.Add("Layer3");
  10.  
  11. // Add the component to the game's component list
  12. this.Components.Add(this.scroller);
  13.  
  14. base.Initialize();
  15. }

Ici, nous initialisons le composant en limitant le défilement sur l'axe des Y (le paramètre constraintOnY est à true). Le composant permet par contre un bouclage infini sur l'axe X. C'est la configuration typique utilisée dans un jeu de plateforme à la Super Mario Bros ou un jeu comme R-Type.

Nous ajoutons ensuite, via la méthode Add(), les noms des médias textures qui serviront à faire le rendu. On se rappellera que l'ordre par défaut est défini du plus proche au plus lointain, donc dans notre cas, la texture nommée "Layer1" se trouve être la plus proche visuellement (avant plan), et la texture "Layer3" est en arrière plan.

Il ne reste plus qu'à ajouter le composant à la liste des composants du jeu, et le tour est joué!

Notre version finie du défilement parallaxe

Naturellement, cette version finie ne paie pas de mine grâce à mon talent d'artiste très "particulier". Un artiste professionnel sera capable de tirer le meilleur de ce genre d'effets.