Vous êtes sur la page 1sur 17

CSharp/Monogame - Atelier Images - Support de cours © Gamecodeur

Qu'est-ce qu'une image ?


Au départ, vos images sont sous la forme de fichiers. On peut créer des fichiers images à partir de logiciels
tels que : Photoshop (payant), GIMP (Gratuit), Paint.net (Gratuit) ou encore des logiciels de Pixel Art tels
que ​PyxelEdit​ (Payant, offert avec un abonnement Premium Plus ou Pro) ou en ligne avec P ​ iskel​.

On peut aussi télécharger des images sur internet et/ou les extraires de bibliothèques d'images gratuites
ou payantes.

Bibliothèques gratuites :
http://www.gamecodeur.fr/gamecodeur-pack/
http://opengameart.org/

Bibliothèques payantes :
https://www.gamedevmarket.net/​ (recommandé !)

Quel format de fichier image pour vos jeux ?


Il existe de nombreux formats, mais vous ne pouvez, au départ n'en retenir que deux.

Format JPG
C'est un format d'image léger, qui a 2 inconvénients :
- Il est compressé, et donc la qualité de l'image peut être altérée
- Il ne permet pas de gérer une transparence, vos images couvriront donc toujours une zone
rectangulaire

Format PNG
C'est LE format pour afficher des sprites dans vos jeux. Il gère en effet une transparence. Seul
inconvénient : le poids des fichiers qui peut devenir gênant si l'image est grande.

Ici, la zone quadrillée représente la transparence du PNG. La transparence peut aussi être partielle, pour
donner par exemple un effet translucide.
Voici comment choisir entre JPG et PNG :

Pour un sprite (personnage, élément de décor, bouton, etc.) : PNG


Pour un fond d'écran, une image rectangulaire dont le motif occupe toute sa surface : JPG

Pour un jeu mobile, il est indispensable de se poser la question car une application trop lourde peut être
problématique : frein au téléchargement de la part des joueurs, place occupée sur le device, temps de
chargement…

Comment fonctionne une carte graphique ?


Note : voir cette excellente page (en anglais) pour une explication animée =>
https://simonschreibt.de/gat/renderhell-book1/

Nous n'irons pas dans le détail, mais nous allons juste voir la "surface" du fonctionnement d'une carte.

Il est au départ juste important de savoir que pour arriver sur votre écran, votre fichier image doit être
chargé dans la mémoire de l'ordinateur (RAM), puis, transféré dans la mémoire vidéo (VRAM) afin que le
GPU (le processeur graphique), puisse l'afficher.

FICHIER => RAM => VRAM => GPU

Voir l'animation :
https://data.simonschreibt.de/gat049/copy_data_from_hdd_to_ram_vram_01.webm
https://data.simonschreibt.de/gat049/copy_data_from_vram_to_l2_01.webm

En réalité, pour une carte graphique, tout est "texture". Votre beau sprite donc, est considéré par la carte
comme une texture qu'elle va devoir plaquer sur quelque chose. En 2D, ce sera un plan rectangulaire, sans
perspective.

La carte, de plus, ne sais afficher que des lignes, des points et des triangles. Donc pour afficher une image
rectangulaire, elle devra afficher 2 triangles sur lesquels elle va plaquer une texture (une image donc) !

Autres notions importantes à comprendre : Render State et Draw Call

Une fois la texture prête dans le GPU, il va falloir afficher. On va aller paramétrer le GPU pour lui dire
comment afficher cette texture. C'est le R
​ ender State​. Il décrit diverses choses comme l'image à afficher
(la texture), son blend mode (transparence, façon dont les couleurs sont traitées), son/ses shaders, etc.

Une carte graphique ne peut, grossomodo, gérer qu'un seul Render State à la fois (en réalité, la science
des cartes graphiques et complexes et il existe des moyens internes pour la carte de réaliser des travaux
en parallèle, mais ce n'est pas important à ce stade). Une fois le Render State préparé, l'affichage se fait
en une passe. Votre programme demande alors au GPU d'afficher via une commande : on appelle ça un
Draw Call​. Plus il y de Draw Call, plus il y a de risques de ralentissement de l'affichage.

Donc, à chaque fois que vous affichez une image, si elle diffère de l'image affichée précédemment (pas le
même fichier, pas le même blend mode, pas le même shader, etc.), la carte va devoir effacer son Render
State et en créer un autre.
Pour vos premiers jeux, on s'en fout un peu, car les performances sont tellement énormes que vous n'y
verrez que du feu. Mais si vous manipulez beaucoup d'images en même temps, il faudra peut être faire
usage de "batches" pour accélérer les choses.

Un batch consiste à préparer un Render State capable de faire afficher à la carte plusieurs sprites en une
seule fois. On utilise pour ça des "atlas". Il s'agit d'une image regroupant plusieurs sprites, et la liste des
coordonnées des images qu'elle contient.

On appelle ça un "Texture Atlas" ou encore une "Sprite Sheet" même si beaucoup considère que ce n'est
pas la même chose.

L'idée générale est que la carte, même si elle affiche des "morceaux" différents de l'image, n'aura pas
besoin de faire des transferts entre la RAM et la VRAM. On regroupe donc par exemple, toutes les images
d'une animation dans une seule comme ceci :

On appelle "Frame" chacune des images qui composent une animation ou une sprite sheet.
Ou encore (et c'est là qu'on parle plus d'Atlas) on regroupe plusieurs sprites et images dans une grande
sheet :

Que faire de tout ça quand on débute ?


Réponse : ne pas s'en occuper !

Inutile de faire des sprite sheets quand on débute. On peut (et on doit) coder de la manière la plus simple
possible. On peut très bien avoir un fichier image par frame !

Note : Nous apprendrons, dans un cours spécifique, à gérer des sprites sheets et/ou utiliser des logiciels
de génération d'atlas.
Afficher sa première image
En love2D c'est le plus simple car Löve masque la notion de render state, et de batch. Et permet
également de générer et afficher des "primitives" (lignes, rectangles, cercles), ce que XNA/Monogame ne
permet pas en standard.

Il suffit donc de "charger" l'image dans une variable. Puis d'afficher cette image avec l'API graphics de
Löve, via la fonction "draw".

Ici, on va afficher un personnage à la position 100,100 de l'écran du jeu.

Voici votre 1ère image affichée avec Löve :

local img

function love.load()
img = love.graphics.newImage("images/personnage.png")
end

function love.draw()
love.graphics.draw(img, 100, 100)
end

Les sources de ce projet sont accessibles ici :


https://www.dropbox.com/sh/k7bxnuylb94wqb8/AADZmt5aOwWNVOOPuEpLF_N8a?dl=0

Résultat :
Voici votre 1ère image affichée avec Monogame :

Le principe, malgré les apparences, est le même que pour Löve : on charge l'image et on la "stocke" dans
une variable. Puis on l'affiche en utilisant le moteur.

Les différences avec Löve :


- L'image doit être au préalable ajoutée au projet via un outil intégré, appelé le "Content Pipeline"
- On doit créer un contexte de rendu (Render State) via un spriteBatch, pour afficher l'image.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class Game1 : Game


{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D img;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize()


{
base.Initialize();
}

protected override void LoadContent()


{
spriteBatch = new SpriteBatch(GraphicsDevice);
img = this.Content.Load<Texture2D>("personnage");
}

protected override void Draw(GameTime gameTime)


{
GraphicsDevice.Clear(Color.Black);

spriteBatch.Begin();
spriteBatch.Draw(img, new Vector2(100, 100));
spriteBatch.End();

base.Draw(gameTime);
}
}
Afficher une images avec Monogame
Il faut tout d'abord mettre le fichier image dans le répertoire Content du projet.

Ensuite, l'ajouter au Content Pipeline :


- Double cliquer sur Content.mgcb

Cela ouvre automatiquement l'outil "Monogame Pipeline".

Et faire glisser l'image dans la partie Content. On peut faire un glisser/déposer depuis l'explorateur de
fichier vers la fenêtre du Monogame Pipeline. Résultat : le fichier s'ajoute dans la liste (noter son nom).

Puis compiler les ressources en cliquant sur l'engrenage (ou menu Build/Build ou F6).

Le résultat de la compilation doit afficher "Build 1 succeeded".


Retour dans le code pour déclarer un membre de type Texture2D au niveau de la classe Game :

Texture2D imgSlime;

Ensuite instancier l'image dans LoadContent() :

imgSlime = this.Content.Load<Texture2D>("personnage");

Le nom passé en paramètre doit être le même que celui du fichier, ​sans extension​.

Et enfin afficher l'image dans Draw() :

spriteBatch.Begin();
spriteBatch.Draw(img, new Vector2(100, 100));
spriteBatch.End();

Effectuer un effet miroir horizontal


Il faut utiliser une surcharge de Draw qui propose un plus grand nombre de paramètres, dont celui qui nous
intéresse : SpriteEffects.

SpriteBatch.Draw (Texture2D, Vector2, Nullable<Rectangle>, Color, Single, Vector2, Vector2,


SpriteEffects, Single)

Position du Type Rôle


paramètre

1er paramètre Texture2D La texture qu'on veut afficher (notre image) chargée
dans LoadContent. Dans notre cas i​ mgSlime.​

2ème paramètre Vector2 La position du sprite à l'écran, on peut lui passer


directement un n​ ew Vector2(....).​

3ème paramètre Nullable<Rectangle> La zone de la texture à afficher : passer n


​ ull

4ème paramètre Color La teinte du sprite. Utiliser ​Color.White​ pour avoir


l'image normale.

5ème paramètre Single L'angle de rotation en radians. 0


​ ​ pour aucune.

6ème paramètre Vector2 L'origine (offset) d'affichage. Passer V


​ ector2.Zero
pour la valeur par défaut, c'est à dire 0,0.

7ème paramètre Vector2 ou Single La déformation (scale) totale ou horizontale et verticale


(passer ​1.0f​ pour taille normale).

8ème paramètre SpriteEffects (enum) L'effet d'affichage ! V


​ oir plus bas.

9ème paramètre float La profondeur du calque sur lequel afficher l'image.


Passer ​0​.
Le paramètre qui nous intéresse donc est le 8ème sur les 9 et il faut passer obligatoirement des valeurs par
défaut aux 8 autres.

Ce paramètre est de type SpriteEffects. Après un coup d'oeil à la doc de XNA on constate que c'est une
simple enum. Voir
https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.spriteeffects.aspx.​

Elle peut prendre comme valeur ​FlipHorizontally, FlipVertically​ ou N


​ one.

Pour pouvoir n'avoir qu'un seul appel à Draw qui pourra faire, ou pas le flip horizontal, on va déclarer une
variable de type SpriteEffects et lui donner par défaut, aucun effet. Ensuite, si le déplacement du sprite est
vers la gauche (vitesse inférieure à 0) alors on lui donne la valeur FlipHorizontally. Ca donne ça :

SpriteEffects effect = SpriteEffects.None;


if (slimSpeed > 0)
effect = SpriteEffects.FlipHorizontally;

Et pour l'appel à Draw, ça donne :

spriteBatch.Draw(imgSlime, new Vector2(100, 100), null, Color.White, 0,


Vector2.Zero, 1.0f, effect, 0);

Tada !
Deltatime
Vous avez peut être déjà entendu parler du "deltatime" et on l'avait vu ensemble dans les atelier
Lua/Love2D.

L'utilisation du deltatime consiste à effectuer un calcul, lors du changement de coordonnée d'un sprite, pour
qu'il parcoure une distance non pas calculée en pixels purs, mais en "pixels par secondes". Ainsi, si
l'ordinateur (ou le device, type téléphone) rame un peu, et qu'il ne peut pas tourner à 60 FPS, les objets se
déplaceront tout de même à la bonne vitesse, quitte à avoir un mouvement saccadé.

Par défaut, Monogame gère un affichage à vitesse constante, en visant le 60 FPS. Utiliser le deltatime
n'aura donc pas d'influence sur votre jeu, à moins de demander à Monogame de travailler en affichage à
vitesse variable.

Insérez cette ligne dans le constructeur de votre classe Game pour passer en vitesse variable :

IsFixedTimeStep = false

Ensuite, lors de vos updates, il faut travailler en nombre de pixels par secondes. Pour cela Monogame vous
fournit un paramètre précieux à travers la méthode Update, il s'agit de gameTime :

protected override void Update(GameTime gameTime)


Ce paramètre vous fournit des information sur le temps "consommé" par votre jeu et notamment le temps
écoulé depuis le dernier update :

gameTime.ElapsedGameTime.TotalSeconds

En utilisant cette valeur, vous obtenez une fraction correspondant au temps qu'il a fallu à la dernière update
pour s'exécuter. Il vous suffit d'un calcul pour savoir de combien de pixels faire avancer votre personnage.
Le voici (recopiez-le ou apprenez-le par coeur) :

(vitesse_en_pixels x 60) x durée_ecoulée_depuis_dernière_update

soit, en code :

(slimSpeed * 60.0f) *(float)gameTime.ElapsedGameTime.TotalSeconds;

Exemple :
Avant, on avançait de 1 pixel à chaque frame, donc 60 pixels par secondes (car le jeu tourne à 60 FPS si il
le peut).

Pour convertir ça avec le deltatime, on multiplie la vitesse par 60, puis on multiplie ce résultat par environ
1/60 de secondes. Au final, quand l'update aura été exécuté 60 fois, on aura bien parcouru 1 pixel. Et si le
jeu rame, par exemple si il tourne à 30 FPS, on aura fait la même distance en 30 updates (car le temps
écoulé depuis la dernière update sera 2 fois plus important) !

Pour une démonstration complète, voir le projet dans le répertoire DeltaTimeDemo.


Gérer des listes de sprites
Pour gérer des listes de sprites, vous pouvez utiliser des List en C#.

Il faut d'abord que vous sprites (images) soient représentées en mémoire comme des objets. Nous allons
donc mettre à profit nos connaissances en POO de manière très simple.

Pour déclarer une classe qui gèrera nos "Jelly" :

public class Jelly


{
public Vector2 position;
public int vitesse;
public float scale;
public float scaleVitesse;
}

Ainsi, toutes nos variables (position, vitesse, taille…) seront stockée pour chaque sprite.

Pour ajouter 20 sprites à notre liste, on peut ajouter ce code dans LoadContent (car nous avons besoin
d'avoir la taille de l'image et il faut donc l'avoir chargée avant) :

for (int i=1; i<=20; i++)


{
Jelly myJelly = new Jelly();
int y = rnd.Next(img.Height, GraphicsDevice.Viewport.Height);
int x = rnd.Next(0, GraphicsDevice.Viewport.Width);
myJelly.position = new Vector2(x, y);
myJelly.vitesse = rnd.Next(1, 5);
lstJelly.Add(myJelly);
}

Dand ce code, nous avons créé 20 "Jelly" et nous les avons positionnées aléatoirement sur tout l'écran
grâce à notre générateur de nombre aléatoire "rnd" (qui est un objet de type Random déclaré au niveau de
la classe Game) :

Random rnd;

Et instancié dans le constructeur de Game1 :

rnd = new Random();

(Voir la vidéo de l'atelier pour les détails de calcul de position)

Pour afficher nos images, dans le Draw, il suffit de les parcourir avec un Foreach :

foreach (Jelly item in lstJelly)


{
effect = SpriteEffects.None;
if (item.vitesse > 0)
effect = SpriteEffects.FlipHorizontally;
spriteBatch.Draw(img, item.position, null, Color.White, 0,
new Vector2(img.Width / 2, img.Height),
new Vector2(scale, scale), effect, 0);
}

Voir le projet ListeImages ou ListeImagesAdvanced pour plus de détails, notamment sur l'update.

Changer la "teinte" d'une image


Vous avez déjà utilisé cette possibilité en passant "Color.White" en paramètre à la méthode Draw du
SpriteBatch.

L'utilisation du blanc (White en anglais) permet d'avoir la couleur d'origine de l'image.

Pour teindre l'image en rouge, il suffit alors de passer Color.Red :

spriteBatch.Draw(img, position, Color.Red);

Vous pouvez utiliser toutes les couleurs prédéfinies dans Color (l'auto-completion de Visual Studio vous
aidera à en avoir la liste) ou bien créer votre propre teinte :

Color color = new Color(0, 255, 0);

Cette version du constructeur de Color attend 3 valeur : Red (rouge), Green (vert), Blue (bleu) qui sont les
trois canaux RVB. Ici j'ai créé du vert (255 représentant 100% du canal vert). Mais le mieux est de choisir
une couleur dans votre logiciel de dessin favori et récupérer les valeurs RVB qu'il vous fournit.

Ajouter de la transparence (Alpha) à une image


Rien de plus simple.

1ère solution : construire une teinte avec le canal alpha (qui va lui aussi de 0 à 255).

IMPORTANT : 0 c'est la transparence totale (invisible…) et 255 aucune transparence.

Exemple pour rendre transparent sans changer la teinte d'origine :

Color color = new Color(Color.White, 100);

J'aime bien aussi cette astuce pour exprimer la transparence en fraction (de 0 à 1, dont 0.5 pour 50%) :

Color color = new Color(255, 0, 0, (int)(255.0f * 0.5f));

ou pour du rouge transparent :

Color color = new Color(255, 0, 255, 100);

2ème solution, plus simple : utiliser une couleur existante et la rendre transparente

Il suffit de multiplier la couleur par une fraction (float) de 0 à 1.


Color color = Color.White * 0.5f;

ou plus direct :

spriteBatch.Draw(img, position, Color.White * 0.5f);

Gérer la souris
Déjà, comment afficher la souris ? Par défaut elle n'est pas affichée.

Dans le constructeur de la classe dérivée de Game (Game1 si vous ne lui avez pas donné un nom) ou
dans Initialize, ajoutez juste :

IsMouseVisible = true;

Pour connaître l'état de boutons de la souris, il faut regarder du côté de la classe Mouse qui est capable de
nous renvoyer tout ça :

MouseState etatSouris = Mouse.GetState();

Par exemple, pour savoir si le bouton gauche est enfoncé :

if (etatSouris.LeftButton == ButtonState.Pressed)
{
// Le bouton gauche est enfoncé…
}

On a aussi accès à ​MiddleButton, ​RightButton ou encore ​ScrollWheelValue pour connaitre la valeur de la


roulette de la souris.

Voir :
https://msdn.microsoft.com/fr-fr/library/microsoft.xna.framework.input.mousestate_members(v=xnagamestu
dio.40).aspx

ASTUCES DE PRO

Il est important de stocker l'ancien état de la souris afin de pouvoir gérer les événements proprement. En
effet comment savoir que c'est le 1er clic sur la souris et non pas juste le fait que le joueur garde le doigt
appuyé ? Si on ne teste que l'état "Pressed" dans l'update, la condition sera vraie jusqu'à 60 fois par
secondes, tant que le joueur ne lache pas le bouton...

La technique est la suivante :

1) Stockez, de manière extérieure à l'update, une variable de type MouseState. Dans notre exemple, on la
stocke au niveau de Game1 :

MouseState ancienEtatSouris;
2) Quand vous testez le clic dans l'update, vérifiez si le bouton n'était pas déjà enfoncé à la frame
précédente :

MouseState nouvelEtatSouris = Mouse.GetState();

if (nouvelEtatSouris.LeftButton == ButtonState.Pressed &&


ancienEtatSouris.LeftButton == ButtonState.Released)
{
// Evenement clic !
}

ancienEtatSouris = nouvelEtatSouris;

Dans un autre registre, si vous gérez le clic sur des sprites qui peuvent être superposés, vous devrez faire
face à plusieurs problématiques :

- 1) Les sprites sont affichés dans l'ordre de leur ajout au SpriteBatch (Draw). Or, quand vous allez
parcourir la liste pour savoir lequel est cliqué, le 1er de la liste est le dernier affiché ! Du coup, vous
cliquerez sur celui qui est le plus au fond…
SOLUTION : Tester les clic sans utiliser un Foreach mais via un parcours inversé

for (int i=lstSprites.Count - 1; i>=0; i--)


{
sprite = lstSprites[i]

}

- 2) Si vous ne faites rien, vous allez cliquer sur tous les sprites qui sont sous votre souris, et pas
uniquement sur le 1er !
SOLUTION : Il faut donc gérer un flag pour "noter" qu'un sprite a été cliqué et que le clic devra être
ignoré pour les autres qui restent dans la liste.

bool SelectionOK = false;



if (... && SelectionOK == false)
{
SelectionOK = true;
}

Voir le projet ListeImagesClick pour un exemple.


Détecter un clic sur une image
Pour détecter si la souris est sur une image au moment du clic, il faut vérifier si sa position est dans les
limites du rectangle contenant l'image.

ATTENTION : Ce code ne fonctionne que si l'image est affichée à une origine 0,0 ! Sinon il faut intégrer
l'origine dans tous les calculs…

// Teste si on clique sur ce sprite


bool SelectionOK = false;
if (nouvelEtatSouris.LeftButton == ButtonState.Pressed &&
ancienEtatSouris.LeftButton == ButtonState.Released
&& SelectionOK == false)
{
if (nouvelEtatSouris.X >= item.position.X
&& nouvelEtatSouris.Y >= item.position.Y
&& nouvelEtatSouris.X <= item.position.X + img.Width
&& nouvelEtatSouris.Y <= item.position.Y + img.Height)
{
SelectionOK = true;
// Le sprite est cliqué !
}
}

Pour un exemple complet de sélection de sprites en mouvement, voir le projet : ​ListeImagesClick.

Afficher un fond d'écran


Le plus simple est d'avoir une image de format jpeg (car elle occupe toute la surface et n'a pas de zones
transparentes) et de même taille que votre jeu.

En monogame, la fenêtre par défaut fait 800x480 pixels.

Si vous souhaitez changer la taille de la fenêtre, voici comment faire (dans le constructeur de l'instance de
Game) :

graphics.PreferredBackBufferWidth = 800;
graphics.PreferredBackBufferHeight = 600;
graphics.ApplyChanges();

Et vous voilà en 800x600 !

Pour afficher le fond d'écran, il suffit de le charger et de l'afficher en 0,0… revoir donc le début de cet atelier
pour ça (Projet : FirstImage).

Pour afficher le fond, il faut charger et afficher une image (comme dans la 1ère partie avec le projet
Firstimage). Mais il faut bien penser à afficher l'image de fond en 1er car les images affichées en 1er sont
celles sont en arrière plan.

Pour afficher l'image en position 0,0, utilisez Vector2.Zero :


spriteBatch.Begin();
spriteBatch.Draw(imgBackground, Vector2.Zero);
spriteBatch.End();

Faire scroller le fond d'écran


Il y a une méthode très simple pour faire scroller un fond. Avec une seule image !
Il faut que l'image puisse visuellement se répéter bien sûr. On trouve ce genre d'images (pour test) sur
Google Image avec des mots clés tels que "scrolling background".

Comment faire :
1) Gérer une position pour le fond (un simple Vector2)
2) Afficher l'image à la position 0,0 au départ
3) A chaque update, modifiez la valeur X du Vector2 pour faire scroller l'image (par exemple -1 pour
faire scroller vers la gauche)
bgPosition.X -= 10;
4) Si la position s'est déplacée de la largeur de l'image, remettre à zéro X
if (bgPosition.X <= 0 - imgBackground.Width)
bgPosition.X = 0;
5) Afficher une deuxième fois l'image à droite de la 1ère dans le draw :
// Affiche le fond
spriteBatch.Draw(imgBackground, bgPosition);
if (bgPosition.X < 0)
spriteBatch.Draw(imgBackground, new Vector2(bgPosition.X + 800, 0));

Voir le projet "Background".

Faire un scrolling Parallaxe


Il suffit d'afficher plusieurs images transparentes, les unes sur les autres, et les faire scroller à des vitesses
différentes.

Plus l'image est dans le fond, moins vite elle scrolle. Expérimentez pour trouver des vitesses qui donnent
un effet réaliste.

Pour éviter de "dupliquer" 4 fois le code si vous avez 4 couches de décors, créez une classe pour gérer un
background :

class Background
{
private Vector2 position;
private Texture2D image;
private float speed;
public Vector2 Position
{
get
{
return position;
}
}
public Texture2D Image
{
get
{
return image;
}
}

public Background(Texture2D pTexture, float pSpeed)


{
image = pTexture;
speed = pSpeed;
position = new Vector2(0, 0);
}

public void Update()


{
position.X += speed;
if (position.X <= 0 - image.Width)
position.X = 0;
}

Il suffira ensuite de créer 4 instances de la classe, avec des images différentes et leur vitesse respectives :

background0 = new Background(imgBackground0, -2);


background1 = new Background(imgBackground1, -5);
background2 = new Background(imgBackground2, -8);
background3 = new Background(imgBackground3, -10);

Puis de les afficher :


AfficheBackground(background0);
AfficheBackground(background1);
AfficheBackground(background2);
AfficheBackground(background3);

Voir le projet "ScrollBackgroundParallax".

Vous aimerez peut-être aussi