Laden...

schnell Viele Bilder und Pixel mit GDI+ zeichnen

Erstellt von Hunv vor 16 Jahren Letzter Beitrag vor 16 Jahren 15.459 Views
Information von herbivore vor 15 Jahren

Dies ist ein Thread, auf den aus der FAQ verwiesen wird. Bitte keine weitere Diskussion, sondern nur wichtige Ergänzungen und diese bitte knapp und präzise. Vielen Dank!

Hunv Themenstarter:in
193 Beiträge seit 2005
vor 16 Jahren
schnell Viele Bilder und Pixel mit GDI+ zeichnen

Hallo
Ich habe mal wieder ein Problem(chen).

Ich habe eine Form, in die ich ein Reihe von Bilder zeichen möchte (ein 50x50 feld - sprich 2500 Bilder und anschließend einen Kasten (250x250 Pixel), in dem jedes Pixel einzelnt gezeichnet wird.

Das ganze ist für ein Spiel. Die Bilder stellen die Spielfelder da und der kasten ist eine Art übersicht.

Mein Problem ist jetzt, dass das ganze ca 15 Sekunden dauert um das zu zeichnen und das ist eindeutig zu lange dafür, dass andere das auch spielen sollen.

Gibt es eine möglichkeit das ganze (schnell) vorher zu generieren und dann auf einen Schlag anzeigen zu lassen? Das ganze (anzeigen, ohne den Algorithmus) sollte unter 1/2 Sekunde dauern

Visit me @ www.beremote.net

S
1.047 Beiträge seit 2005
vor 16 Jahren

naja, kannst du denn mal deine zeichenroutine posten? vlei kann man da ja was optimieren.

hast du dein zeichenalgorithmus selbst ausgedacht oder benutzt du algorithmen die du im netz gefunden hast?
du könntest ja den hintergrund auf ein bitmap zeichnen was d udann immer anzeigen läßt dann soltle das z.b. schon mal fixer geht

doublebuffering wär z.b. auch noch ein stichwort

Hunv Themenstarter:in
193 Beiträge seit 2005
vor 16 Jahren

Also ich hab ein 2D-Array fields[,]
Darin stehen die Feldinformationen (z.B. Wasser = 0, Land = 1 usw)

Um die Große Karte (siehe Anhang) zu erstellen benutze ich das hier:


//Felder füllen
            for (int i = 0; i < 50; i++)
                for (int j = 0; j < 50; j++)
                {
                    if (fields[i + shiftleft, j + shiftup] == 0) gO.DrawImage(imageList1.Images[0], i * 16 + gridshiftx, j * 16 + gridshifty, 16, 16);//wasser
                    else if (fields[i + shiftleft, j + shiftup] == 1) gO.DrawImage(imageList1.Images[1], i * 16 + gridshiftx, j * 16 + gridshifty, 16, 16);//leeres, nicht bebaubares Land
                    else if (fields[i + shiftleft, j + shiftup] == 2) gO.DrawImage(imageList1.Images[2], i * 16 + gridshiftx, j * 16 + gridshifty, 16, 16);//wiese
                    else if (fields[i + shiftleft, j + shiftup] == 3) gO.DrawImage(imageList1.Images[3], i * 16 + gridshiftx, j * 16 + gridshifty, 16, 16);//moor
                    else if (fields[i + shiftleft, j + shiftup] == 4) gO.DrawImage(imageList1.Images[4], i * 16 + gridshiftx, j * 16 + gridshifty, 16, 16);//ackerland
                    else if (fields[i + shiftleft, j + shiftup] == 5) gO.DrawImage(imageList1.Images[5], i * 16 + gridshiftx, j * 16 + gridshifty, 16, 16);//wald
                    else if (fields[i + shiftleft, j + shiftup] == 6) gO.DrawImage(imageList1.Images[6], i * 16 + gridshiftx, j * 16 + gridshifty, 16, 16);//Gebirge  

                }

Um die Minimap anzuzeigen benutze ich das hier:


Pen myPen = new Pen(Color.Black);
            //880,85
            for (int i = 0; i < 250; i++)
                for (int j = 0; j < 250; j++)
                {                    
                    if (fields[i, j] == 0)
                    { myPen.Color = Color.Blue;  }
                    else if (fields[i, j] == 1)
                    { myPen.Color = Color.Yellow; }
                    else if (fields[i, j] == 2)
                    { myPen.Color = Color.Green; }
                    else if (fields[i, j] == 3)
                    { myPen.Color = Color.Green; }
                    else if (fields[i, j] == 4)
                    { myPen.Color = Color.Green; }
                    else if (fields[i, j] == 5)
                    { myPen.Color = Color.Green; }
                    else if (fields[i, j] == 6)
                    { myPen.Color = Color.Gray; }
                    gO.DrawRectangle(myPen, 880 + i, 85+j, 1, 1);
                }

Visit me @ www.beremote.net

M
1.439 Beiträge seit 2005
vor 16 Jahren
  1. Du zeichnest immer alles neu. Meistens musst du aber nur einen gewissen Ausschnitt neu zeichnen. Und auch hier nicht die gesamte Welt sondern nur den sichtbaren Ausschnitt.
    =>Graphics.Clip
    2)Sind deine Bitmaps auch 16x16 Pixel? Wenn nein, dann erzeuge dir neue im Format 16x16. Dann kannst du auch Graphics.DrawImage(Image, int, int) verwenden. Diese Funktion ist um einiges schneller als die anderen Überladungen mit Größenangabe.
M
1.439 Beiträge seit 2005
vor 16 Jahren

Btw.: Du musst deine Grafikobjekte disposen(Pen, Brush, ...) wenn du sie nicht mehr brauchst. Auch solltest du diese Objekte nicht immer neu anlegen sondern dir die Objekte irgendwo speichern, oder gleich Pens.(Black|Blue|...) verwenden.

Hunv Themenstarter:in
193 Beiträge seit 2005
vor 16 Jahren

Wenn ich jetzt aber das Spielfeld (das große) weiter nach rechts scrolle, dann muss ich nunmal alles neu machen und nicht nur einzelne Felder. Und das ist das, was so tierisch lange dauert und noch schlimmer dann das ganze in Kombi mit der Minikarte.

Visit me @ www.beremote.net

S
1.047 Beiträge seit 2005
vor 16 Jahren

du mußt aber bei dem großen nur den sichtbaren bereich quasi zeichnen, nicht das gesammte feld...

deine minimap mußt du natürlich 1x initialisieren

nachtrag:
wies scheint machst du ja nen editor für ne map...

mal zurück zu meinr anfangsidee: deine gesammte große map kannst du ja in eine bitmap zeichnen, diese benutzt du dann als hintergrund und zeigst immer nur den ausschnitt an den du gerade brauchst

wenn du nun was neus auf die map zeichen nwillst übermalst du das bild an der stelle einfach

deine minimap erstellst du einmal komplett wenn du die große map gezeichnet hast und änderst sie ebenfalsl wenn du die große änderst

denk mal so könnte das funzen, oder?

Hunv Themenstarter:in
193 Beiträge seit 2005
vor 16 Jahren

das Problem ist nur, dass sich die Form ständig "aktualisiert" (oder was auch immer).
Jedenfalls ist danach immer alles komplett weg, sodass ich das ständig alles neuzeichnen muss

Visit me @ www.beremote.net

5.742 Beiträge seit 2007
vor 16 Jahren

Ich glaube, in deinem Fall (Spiel) rentiert sich DirectDraw - das ist wesentlich schneller.

S
1.047 Beiträge seit 2005
vor 16 Jahren

Original von Hunv
das Problem ist nur, dass sich die Form ständig "aktualisiert" (oder was auch immer).
Jedenfalls ist danach immer alles komplett weg, sodass ich das ständig alles neuzeichnen muss wenn alles immer weg is dann hast du noch ne nanderen fehler drin

meine obigen post hab ihc mal editiert, lies mal meinen vorschlag...

dann würde ic hdas so machen das ich im paintevent oder im onpaint immer den aktuellen ausschnitt der hintergrundkarte zeichnen lasse bzw die minimap

d.h. einmal ein teil eines bildes und einmal ein kleines bild

wenn du nun mit der maus was veränderst (wasser da wo vorher grün is) würde ich dsa große bild ander stelle ändern und dsa bild der minimap und ein repaint anfordern

5.941 Beiträge seit 2005
vor 16 Jahren

Hallo zusammen

Original von winSharp93
Ich glaube, in deinem Fall (Spiel) rentiert sich DirectDraw - das ist wesentlich schneller.

DirectDraw sollte man nicht mehr benutzen, das ist veraltet.

@Hunv
Ich würde dir du DirectX raten, Direct3D kann man gut für zweidimensionale Darstellungen benutzen, das Buzzword dazu heisst: Direct2.5D 😉

Schau mal hier vorbei.

Oder allgemein, die Suchbegriffe benutzen: directx, direct3d, 2d, ...

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

Hunv Themenstarter:in
193 Beiträge seit 2005
vor 16 Jahren

@sheitman: Ich habe auch schon daran gemacht das so zu machen, aber dann wäre eine Map seeeehr groß (jetzt in der Entwicklungsphase haben wir die maps auf 250x250, aber später sollen die so groß wie möglich werden, mindestens 1000x1000 felder => heißt eine Bitmap von 17000x17000 Pixeln (16 pixel ein bild, 1 pixel der rand). Das wäre etwas zu sehr groß.

Direct Draw hatte ich schon gefunden - auch ein Tut, aber als ich angefangen hab, stand in den Tooltips, das man das nicht mehr benutzen sollte - hab dann mal n bisschen im Internet geguckt und wollte dann das mit Direct3D machen.

Zu Direct3D habe ich bis jetzt aber nur 3D-Tuts gefunden oder Tuts, die den Aspekt, dass man Grafiken anzeigen lassen will total ignorieren.

@Peter: Ich guck mal deine Links an.

Visit me @ www.beremote.net

M
1.439 Beiträge seit 2005
vor 16 Jahren

Solche Spiele wie deines habe ich auch schon auf einem 300MHz Rechner gespielt. Und diese wurden auch nicht mit directX erstellt. *Zeichne so viel wie nötig und nicht mehr. *Deine Bilder sollten bereits die richtige Größe haben, damit nicht ständig skaliert werden muss. *Verwende einen anderen Graphics.DrawImage Overload. Da gibt es sehr große *Performanceunterschiede zwischen den einzelnen Versionen. *Cache so viel wie nur möglich: Die Minimap musst du nur einmal erstellen(Graphics.DrawLine ist vermutlich auch nicht die schnellste Methode zum erstellen...); Die Hauptkarte würde ich nicht zwischenspeichern, da bei großen Karten der Speicherverbrauch schnell steigt.

5.941 Beiträge seit 2005
vor 16 Jahren

Hallo marsgk

Original von marsgk
Solche Spiele wie deines habe ich auch schon auf einem 300MHz Rechner gespielt. Und diese wurden auch nicht mit directX erstellt.

Ja, aber sicher nicht mit GDI oder GDI+ 😉
Und ausserdem ziemlich sicher relativ hardwarenah?

Mit C# ist die schnellste und vorallem bequemste Variante immer noch, eine Grafik API zu benutzen. Sei es OpenGL oder DirectX.

Managed DirectX ist IMO die komfortabelste Möglichkeit, um schnelle und anspruchsvolle Grafik auf den Schirm zu bringen.

Mit GDI+ z.B. hast du einen riesen und komplizierten Aufwand, um es einigermassen schnell hinzukriegen. Und wenn die Ansprüche dann mal steigen, hast du schon wieder ein Problem.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

Hunv Themenstarter:in
193 Beiträge seit 2005
vor 16 Jahren

Ich würde auch liebend gerne Direct X benutzen, nur finde ich halt leider keine tutorials/hilfen oder ähnliches, die annährend auf das eingehen, was ich brauche. Die Tuts gehen an der 2D-Ebene fast immer so schnell wie möglich vorbei.

Visit me @ www.beremote.net

M
1.439 Beiträge seit 2005
vor 16 Jahren

Ich habe es gerade ausprobiert:
Tilegröße: 16x16; den ganzen Fensterinhalt mit Bitmaps füllen @ 1200MHz Athlon mit 768 MB Ram .Net 2.0

=>1024x768 Pixel(3072 Bitmaps) ~65ms pro frame -> ~15 fps
=>800x600(1875 Bitmaps) ~35ms pro frame ->27 fps
=>640x480(1200 Bitmaps) ~20ms pro frame -> 50 fps

Dies allerdings ohne Optimierungen(immer gesamten Fenster neu zeichnen, ohne cachen).

5.941 Beiträge seit 2005
vor 16 Jahren

Hallo marsgk

1024x768 Pixel ist meiner Meinung nach ein Minimum für sinnvolle Spiele im Strategie Bereich.
Wenn jetzt noch pixelgenaues Scrolling, mehrere Layer, Einheiten, Bäume, etc.. dazukommen, stösst man sehr schnell an die Grenzen mit GDI+.
Ganz zu schweigen von Partikeleffekten, hier muss man sogar mit DirectX auf die Performance schauen 🙂

Meiner Meinung nach ist GDI+ nett für ein kleines Spielchen, sobald aber ein wenig mehr Ansprüche fällig werden, siehts eng aus.
Ganz zu schweigen von dem grausligen Code, wenn man mit Optimierungen alles rausholen möchte, und trotzdem steht man wieder an.

@Hunv

Wenn du dann tiefer in die Materie eingehen willst, bzw. auch schon jetzt, ist Englisch praktisch Pflicht.
Zudem gibt es viele relevante Tutorials nicht in C#, sondern z.B. in Delphi, C++, etc...
Das Grundprinzip bleibt aber gleich.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

B
1.529 Beiträge seit 2006
vor 16 Jahren

Zeichne im Hintergrund einfach nur dann, wenn sich etwas ändert in zwei Bitmaps. In die eine die komplette Karte (also jeweils die Gelände-Bitmaps), in die andere einfach nur farbige Punkte für die Minimap.
Dazu bietet es sich an, die Datenstruktur komplett zu ändern.

public enum UndergroundTypes
{
   Empty = 0,
   Water = 1,
   Meadow = 2,
   Moor = 3,
   Field = 4,
   Forest = 5,
   Mountain = 6,
}
public struct UndergroundGraphics
{
   public Image MapImage;
   public Pen MinimapPen;
}

// hier die Verknüpfung von GeländeTypen mit den Zeichenobjekten
public Distionary< UndergroundTypes, UndergroundGraphics> UndergroundData = ... ;

public class GameMaps
{
   // der Faktor zwischen Spielkoordinaten und Bildschirmkoordinaten
   public const int TileSize = 16; // Größe 16 * 16

   // hier die Verknüpfung von Spielkoordinaten mit Geländetypen
   private UndergroundTypes[,] Tiles;

   // hier das Spielfeld
   internal Bitmap Map;
   // und die Minimap
   internal Bitmap Minimap;

   // Spielfeld-Graphics
   private Graphics MapGraphics;
   private Graphics MinimapGraphics;

   // erzeugt die Karten mit der maximalen Größe x und y
   public GameMaps( int x, int y)
   {
      Tiles = new UndergroundTypes[ x , y ];
      Map = new Bitmap( x * TileSize, y * TileSize );
      MapGraphics = Graphics.FromImage( Map );
      Minimap = new Bitmap( x, y );
      MinimapGraphics = Graphics.FromImage( Minimap );
   }

   public UndergroundTypes GetUnderground( int x, int y )
   {
      return Tiles[x,y];
   }

   public void SetUnderground( int x, int y, UndergroundTypes undrgrnd )
   {
      Tiles[x,y] = undrgrnd;
      MapGraphics.DrawImageUnscaled( UndergroundData[undrgrnd].MapImage, x * TileSize, y * TileSize );
      MinimapGraphics.DrawRectangle( UndergroundData[undrgrnd].MinimapPen, x, y, 1, 1 );
   }
}

Jetzt wird in die Bitmaps nur gezeichnet, wenn sich etwas ändert. Im Paint brauchst du dann nur noch Map und Minimap mittels Graphics.DrawImage(Image,Rectangle,Rectangle,GraphicsUnit) darstellen.

Hunv Themenstarter:in
193 Beiträge seit 2005
vor 16 Jahren

@Peter: Der erste Link gefällt mir im ersten Moment schon super. Sieht bis jetzt genau nach dem aus, was ich brauche! Großes Danke!

@Borg: Wie ich schon erwähnte, würde ich im Hinblick auf die Zukunft des Spiels lieber DirectX benutzen, da es für eben soetwas gedacht ist. Es ist außerdem zu rechenaufwendig (soweit ich das jetzt überschauen kann) nach jeder Änderung eine neue Bitmap zu erzeugen und anzeigen zu lassen.

@all: Danke nochmal! Mal sehen, was ich jetzt so hinbekommen kann. Beim nächsten Problem oder der nächsten Frage melde ich mich wieder 😉

Visit me @ www.beremote.net

M
1.439 Beiträge seit 2005
vor 16 Jahren

Die Karte würde ich nicht vorgenerieren. Bei einer (durchaus realistischen) Größe von 1000x1000 Tiles wobei jede Tile 16x16 Pixel hat würde die resultierende Bitmap ~ 1GB Speicher verbrauchen.

M
1.439 Beiträge seit 2005
vor 16 Jahren

Btw.: Mit java 1.6 und 1024x768 komme ich auf ~30fps.

5.941 Beiträge seit 2005
vor 16 Jahren

Hallo marsgk

Original von marsgk
Btw.: Mit java 1.6 und 1024x768 komme ich auf ~30fps.

Mit was genau?

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

M
1.439 Beiträge seit 2005
vor 16 Jahren

PC Athlon 1200MHz mit 768MB RAM und Windows XP sp2
Fenster mit 20x20 Tiles füllen:

.Net 2.0
1280x1000 => ~80ms/frame; 12fps
1024x768 => ~50ms/frame; 20fps
800x600 => ~26ms/frame; 38fps
640x480 => ~16ms/frame; 62fps

java 1.6.0
1280x1000 => ~35ms/frame; 28fps
1024x768 => ~23ms/frame; 43fps
800x600 => ~14ms/frame; 70fps
640x480 => ~9ms/frame; 110fps