Programme 2D très lent (gros débutant)

Résolu
meuh_ou_jeje Messages postés 3 Date d'inscription vendredi 31 octobre 2003 Statut Membre Dernière intervention 8 juillet 2007 - 7 juil. 2007 à 08:36
meuh_ou_jeje Messages postés 3 Date d'inscription vendredi 31 octobre 2003 Statut Membre 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.

7 réponses

Julien237 Messages postés 883 Date d'inscription vendredi 3 novembre 2000 Statut Membre Dernière intervention 3 mars 2009 7
7 juil. 2007 à 09:54
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.
3
cs_Bidou Messages postés 5487 Date d'inscription dimanche 4 août 2002 Statut Membre Dernière intervention 20 juin 2013 61
7 juil. 2007 à 10:10
Moi je connais un gros wrapper directX qui s'appelle WPF

<hr />
-Blog-
0
MorpionMx Messages postés 3466 Date d'inscription lundi 16 octobre 2000 Statut Membre Dernière intervention 30 octobre 2008 57
7 juil. 2007 à 10:36
Et XNA alors ?

Mx
MVP C# 
0
Lutinore Messages postés 3246 Date d'inscription lundi 25 avril 2005 Statut Membre Dernière intervention 27 octobre 2012 41
7 juil. 2007 à 12:54
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 );
            }
        }
    }
}
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
yann_lo_san Messages postés 1137 Date d'inscription lundi 17 novembre 2003 Statut Membre Dernière intervention 23 janvier 2016 26
7 juil. 2007 à 21:09
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....
0
Julien237 Messages postés 883 Date d'inscription vendredi 3 novembre 2000 Statut Membre Dernière intervention 3 mars 2009 7
7 juil. 2007 à 21:41
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.
0
meuh_ou_jeje Messages postés 3 Date d'inscription vendredi 31 octobre 2003 Statut Membre Dernière intervention 8 juillet 2007
8 juil. 2007 à 02:42
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.
0
Rejoignez-nous