Programme 2D très lent (gros débutant) [Résolu]

meuh_ou_jeje
Messages postés
3
Date d'inscription
vendredi 31 octobre 2003
Dernière intervention
8 juillet 2007
- 7 juil. 2007 à 08:36 - Dernière réponse : meuh_ou_jeje
Messages postés
3
Date d'inscription
vendredi 31 octobre 2003
Dernière intervention
8 juillet 2007
- 8 juil. 2007 à 02:42
Bonjour,

Tout d'abord, je tiens à dire que je suis très fortement débutant en programmation, surtout en c#.
Ayant depuis peu plusieurs heures à perdre, j'ai voulu faire un petit jeu en 2D (du style rpg).
Je n'utilise ni directx ni quoi que ce soit d'autre (je n'y connais absolument rien) et je me suis donc lancé en touriste avec pour seuls outils ceux de l'API de Visual Studio.

Mon problème se situe dans la redéfinition de la méthode onPaint du formulaire affichant le jeu.

protected override void OnPaint(PaintEventArgs e)
{
    carte.dessiner(e);
}

Allons voir la méthode dessiner :

public void dessiner(PaintEventArgs e)
{
    dessinerDecor(e);     // On dessine le décor
    dessinerUnités(e);    // puis les unités
}

Rien de bien palpitant, mais le problème est que à chaque déplacement d'une unité sur la carte, la méthode onPaint est appelée via la fonction invalidate(). Donc ça redessine tout à chaque fois que mon unité bouge, décor y compris (de plus, je redessine toutes les 100ms).
Le problème vient du décor qui prend un temps fou à se redessiner (1 seconde !!), car si je désactive le décor, tout se fait très fluidement.

C'est franchement pas génial de redessiner tout à chaque mouvement d'unité, mais je n'arrive pas à trouver une autre solution... J'ai essayé de faire des invalidate avec un rectangle comme argument pour ne redessiner que la zone où mon unité est en train de bouger, mais celà ne marche pas, ou du moins, je n'y arrive pas. :-(

Que faire? Je bloque sur ce point depuis des heures, et ce n'est franchement pas celui qui m'intéresse le plus. Le problème est que je ne peux pas avancer plus loin dans la programmation car la lenteur du programme empêche tout autre test.

PS: je stocke le décor dans une grilleDécor qui contient des objets Décor. Lors du dessin du décor, pour chaque élément Décor de grilleDécor, je dessine une image correspondant au type de décor (herbe, eau...). Ces images élémentaires sont en .gif de dimension 20x20 et pèsent moins de 100 octets.

Merci d'avance pour d'éventuelles réponses.
Afficher la suite 

Votre réponse

7 réponses

Meilleure réponse
Julien237
Messages postés
884
Date d'inscription
vendredi 3 novembre 2000
Dernière intervention
3 mars 2009
- 7 juil. 2007 à 09:54
3
Merci
Salut,
Il y a plusieurs précautions à prendre, d'abord ne dessine pas ta map directement sur le graphics donné par PaintEventArgs. Au chargement de ton jeu dessine la map statique sur un bitmap, puis utilise graphics.DrawImageUnscaledClipped lors du dessin en clippant le bon morceau. Tu devras utiliser graphics.TranslateTransform pour la placer au bon endroit. Rien qu'en faisant ça tu y gagneras déjà beaucoup.

Ensuite pour les autres éléments du décors, qui pourraient ne pas être statiques, appliquent la même technique à une différence près. Tu ajoute un flag booléen disant si le bitmap a besoin d'être redessiné ou pas. Lorsqu'une propriété affectant l'apparence de ton élément de décors change, tu lève le flag. Lors du dessin, si le flag est levé, tu l'abaisse et tu redessine ton bitmap, puis tu fais le rendu de la même manière qu'au point précédant.

Tu gagneras beaucoup à utiliser DrawImageUnscaled plutot que DrawImage. A toi de leur donner directment la bonne taille.

Essaye de voir ce qui prend vraiment du temps dans ton dessin, c'est pas toujours évident, utilise System.Diagnostic.Stopwatch pour mesurer les temps dans ton code.

Sinon la vraie solution ce serait quand même du directx, mais je pense que y'a quand même moyen de faire de chouettes choses avec GDI+...

Julien.

Merci Julien237 3

Avec quelques mots c'est encore mieux Ajouter un commentaire

Codes Sources a aidé 94 internautes ce mois-ci

Commenter la réponse de Julien237
cs_Bidou
Messages postés
5507
Date d'inscription
dimanche 4 août 2002
Dernière intervention
20 juin 2013
- 7 juil. 2007 à 10:10
0
Merci
Moi je connais un gros wrapper directX qui s'appelle WPF

<hr />
-Blog-
Commenter la réponse de cs_Bidou
MorpionMx
Messages postés
3489
Date d'inscription
lundi 16 octobre 2000
Dernière intervention
30 octobre 2008
- 7 juil. 2007 à 10:36
0
Merci
Et XNA alors ?

Mx
MVP C# 
Commenter la réponse de MorpionMx
Lutinore
Messages postés
3248
Date d'inscription
lundi 25 avril 2005
Dernière intervention
27 octobre 2012
- 7 juil. 2007 à 12:54
0
Merci
Salut, 1 seconde pour dessiner le background, c'est pas normal.. GDI+ n'est pas super performant mais avec un double buffer et en limitant les zones à dessiner on peut faire des choses sympas. 

public partial class Form1 : Form
{
    private const int WIDTH = 800; // client size width.
    private const int HEIGHT = 600; // client size height.
    private const int W = 20; // tile width.
    private const int H = 20; // tile height;
    private const int X = WIDTH / W; // tile x count.
    private const int Y = HEIGHT / H; // tile y count;



    private Bitmap[ ] tiles = null;
    private byte[ , ] map = null;




    public Form1( )
    {
        InitializeComponent( );




        this.ClientSize = new Size( WIDTH, HEIGHT );
        this.BackColor = Color.Black;
        this.FormBorderStyle = FormBorderStyle.FixedSingle;
        this.StartPosition = FormStartPosition.CenterScreen;
        this.SetStyle // Double buffer.
        (
            ControlStyles.AllPaintingInWmPaint |
            ControlStyles.OptimizedDoubleBuffer,
            true
        );




        // Les tiles font 20x20 pour eviter d'être redimensionnés.
        tiles = new Bitmap[ 4 ]
        {
            new Bitmap( "tile1.bmp" ),
            new Bitmap( "tile2.bmp" ),
            new Bitmap( "tile3.bmp" ),
            new Bitmap( "tile4.bmp" )
        };




        map = new byte[ X, Y ];




        Random r = new Random( ( int )DateTime.Now.Ticks );




        for( int x = 0; x < X; x++ )
        {
            for ( int y = 0; y < Y; y++ )
            {
                map[ x, y ] = ( byte )r.Next( 0, 4 ); // Aléatoire pour l'exemple.
            }
        }




        // Une boucle avec DoEvent peut être mieux qu'un timer,
        // ou invalider seulement dans un OnMouseDown ou KeyDown ..
        Timer t = new Timer( );
        t.Interval = 100;
        t.Tick += delegate
        {
            Invalidate( this.ClientRectangle );
        };
        t.Start( );
    }




    protected override void OnPaint( PaintEventArgs args )
    {
        //base.OnPaint( args );




        Graphics g = args.Graphics;




        for( int x = 0; x < X; x++ )
        {
            for ( int y = 0; y < Y; y++ )
            {
                g.DrawImage( tiles[ map[ x, y ] ], x * W, y * H );
            }
        }
    }
}
Commenter la réponse de Lutinore
yann_lo_san
Messages postés
1137
Date d'inscription
lundi 17 novembre 2003
Dernière intervention
23 janvier 2016
- 7 juil. 2007 à 21:09
0
Merci
Il y a un moyen simple de ne redessiner que la partie qui est derriere le sprite qui vient de bouger :

1 - on dessine le fond (qui est presque toujours statique)
Image imgFond = new Bitmap("leFond.bmp");
g.Draw ect...

2 - dans la méthode qui deplace les sprites on récupère les coordonnées des sprites avant qu'ils ne bougent
int xOld = X_EN_COURS;
int yOld = Y_EN_COURS;
int w = sprite.Width;
int h = sprite.Height;

3 - On bouge le sprite

4 - On ne redessine du fond, que l'ancienne position du sprite de dimention de celui-ci


BitmapData bmpData = 
         imgFond.LockBits(new Rectangle(xOld, yOld, w, h), 
         ImageLockMode.ReadWrite,
         imgFond.PixelFormat);
  
int stride = bmpData.Stride;
System.IntPtr scan0 = bmpData.Scan0;

// morceau de l'image de fond a dessiner

Bitmap morceauBmp = new Bitmap(
   w ,
   h,
   stride,
   imgFond.PixelFormat,
   scan0)

// dessine le morceau
g.Draw ect...

// on peut travailler au niveau pixel directement avec les pointeurs C#
// pour faire des effet de couleurs

unsafe
{
    byte* p = (byte*)(void*)scan0;
    int offset = stride - w * 3;
    p[offset] = 0x00ffff;
}

// UnlockBits(...)

Mais le mieux n'étant pas toujours l'ennemis du bien, DIRECTX en dotnet est très agréable à utiliser....
Commenter la réponse de yann_lo_san
Julien237
Messages postés
884
Date d'inscription
vendredi 3 novembre 2000
Dernière intervention
3 mars 2009
- 7 juil. 2007 à 21:41
0
Merci
Bon je recommence avec firefox vu qu'apparemment IE est allergique à ce forum (ou le contraire ?)

Je disais,
Si tu clippe la partie où ça a bougé, puis que tu demande de redessiner tout, normalement GDI ne redessine que la partie clippée non ?
Ce serait quelque peu plus simple non ?

Julien.
Commenter la réponse de Julien237
meuh_ou_jeje
Messages postés
3
Date d'inscription
vendredi 31 octobre 2003
Dernière intervention
8 juillet 2007
- 8 juil. 2007 à 02:42
0
Merci
Problème résolu facilement !
Merci beaucoup pour toutes vos réponses, c'est très sympa.

J'ai suivi vos conseils et crée un bitmap qui sert pour le décor statique, et j'ai mis des flags pour redessiner éventuellement le décor modifiable. À vrai dire cette idée me trottait en tête avant de vous poser ma question mais je ne savais pas à l'époque comment déclarer une image.

class Carte
{
/* Attributs */
private static const int LARGEUR_CARTE = 460, LONGUEUR_CARTE=300;
private Bitmap decor = new Bitmap(LARGEUR_CARTE, LONGUEUR_CARTE);

...
/* Fonction permettant de dessiner tous les décors de la grille */
 private void dessinerDecor(PaintEventArgs e)
{
    Graphics g = Graphics.FromImage(decor);
    Pour chaque élément de la grille de décor, si son flag est à true
        {       
                elem.dessiner(g);
                mettre le flag à faux;
          }
                e.Graphics.DrawImageUnscaled(decor, 0, 0, LARGEUR_CARTE, LONGUEUR_CARTE);   // Puis finalement on dessine le décor
}

Voilà, c'est tout bête, tout simple et ça marche, il fallait donc créer une image fixe ! A chaque fois que le formulaire se rafraîchit, aucun problème, ça s'affiche direct.
Je m'occuperai du clipping plus tard, quand j'aurai des maps plus grandes :-)

Merci à vous tous et bonne journée.
Commenter la réponse de meuh_ou_jeje

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.