[OpenSource] GameUtils

    • Beta
    • Open Source

    Es gibt 152 Antworten in diesem Thema. Der letzte Beitrag () ist von Artentus.

      ich bin ja immer sehr mißtrauisch gegenüber dem Interface-Kult, den c#-ler so gerne treiben.
      Nach meiner Erfahrung meist ein Verstoß gegen das Kiss-Prinzip, also eine unnötige Verkomplizierung.
      ZB solange es nur eine einzige Implementierung eines Interfaces gibt, darf es ühaupt kein Interface geben.
      Ein Interface darf man erst extrahieren, wenn tatsächlich eine alternative Strategie für dieselbe Funktionalität implementiert wird (etwa, wenn einer hergeht, und einen DirectX- oder OpenGL-Renderer bastelt) - vorher nicht!

      Was hier also getrieben wird, ist "vorauseilende Verkomplizierung", und verstößt gegen das Kiss-Prinzip, nach dem alles so einfach wie möglich gemacht werden soll.

      Aber seisdrum - c#ler glauben das gewöhnlich nicht. jdfs, wenn Interface, dann auch richtig polymorph, und da fund ich dieses pseudo-polymorphe Fundstück im Renderer:

      C#-Quellcode

      1. public void DrawBitmap(IBitmap bmp, Artentus.GameUtils.Graphics.Rectangle rect) {
      2. if(!rendering)
      3. throw new InvalidOperationException("Zeichenvorgänge sind nur zwischen \"BeginRender\" und \"EndRender\" gültig.");
      4. Bitmap gdiBmp;
      5. if(bmp is GDIBitmap)
      6. gdiBmp = (bmp as GDIBitmap).GetBitmap();
      7. else
      8. throw new ArgumentException("Diese Bitmap ist nicht kompatibel.");
      9. device.DrawImage(gdiBmp, GDIHelper.ConvertRectangle(rect));
      10. }
      Ich nenne das "pseudopolymorph", weils eben keine allgemeine IBitmap.Draw() - Methode gibt, sondern intern wird doch das "gekapselte" (isses ja nun nicht mehr) Objekt rausgeholt, und nach Ergebnis einer Typüberprüfung wird verfahren.

      Also der Renderer ist nur mit IBitmap-Dingern kompatibel, wo olle Drawing.Bitmap drinne ist - also im Grunde hat das Interface garnix entkoppelt, und eine einfacherere Architektur hätte gleich und ohne Interface die Drawing.Bitmap übergeben, dann braucht man nicht erst zu prüfen, ob eine drin ist.

      Also meiner Ansicht nach sollten die Interfaces alle raus, und die Geschichte einfach und erstmal nur für GDI implementieren.

      Aber da fürchte ich, wird kaum was übrig bleiben: ein Fenster, was ausserhalb der Window-Message-Queue läuft, 2 Dauer-Threads, die immer rund laufen - (das hätten vlt. auch Timer geschafft - es gibt glaub auch hoch-präzise Timer?)
      Alles annere ist in System.Drawing doch bereits enthalten, also System.Drawing und System.Drawing2D - das ist doch schon eine Rendering-Engine.
      @EDR

      Es geht ja darum, von Anfang an eine Hohe Abstraktionshöhe zu errreichen um später einfach Renderer auszutauschen ohne das es ein Problem gibt.

      C#-Quellcode

      1. SGL.Run(new GdiRenderer());
      2. /// SGL.Run(new OpenGLRenderer());


      Um das zu erreichen, muss man von Anfang an auf Interfaces setzen z.B das IGraphicRenderer Interface, welches vorgibt, welche Aufgaben ein Renderer zu erfüllen hat, um Akzeptiert zu werden, was der Renderer macht, um das Ziel zu erreichen ist völlig egal.

      Die Sache mit IBitmap verstehe ich auch nicht, logisch wär eine Klasse Texture, die ein Bitmap enthält, gegebenfalls serialisierbar ist, Texture enthält dann ein Bitmap Objekt - Keine Typüberprüfung nötig, wir wissen ja was wir haben ;)

      Sicherlich ist es einfach - wie du sagst - zuerst einfach GDI zu implentieren, meistens wird es dann aber ein gemorkse, und wenn man anfängt andere Renderer zu implementieren, muss man meist alles nochmal umstellen -> Also lieber gleich an die Regeln eines Interfaces heißt, so ist gewährt, das später alles glatt geht.

      Auch mit den Timern stimme ich nicht mit dir überein:

      Wir haben uns als TargetFrameRate 60 gesetzt, dies entspricht etwa 16ms per Draw. Dauert ein Draw allerdings nur 9ms, muss die restliche Zeit gewartet werden, und die Render bzw Update Loop neu synchronisiert werden, soweit ich weiß funktioniert das mit einen Timer eher schlecht, außerdem sind Timer nix anderes als Threads.


      Alles annere ist in System.Drawing doch bereits enthalten, also System.Drawing und System.Drawing2D - das ist doch schon eine Rendering-Engine.


      Wie du vielleicht oben gelesen hast, reicht die Abstraktionshöhe für eine eigene Gameengine nicht aus.
      Im Prinzip hast du ja Recht, aber wie ThuCommix schon richtig gesagt hat, wenn ich jetzt auf DirectX wechseln will, dann müsste ich auch überall im restlichen Programm die Typen ändern. So lasse ich einfach die Interfaces überall stehen, in ihnen verbirgt sich aber ein anderes Objekt.

      ThuCommix schrieb:

      logisch wär eine Klasse Texture, die ein Bitmap enthält
      Das musst du mir jetzt nochmal genauer erklären.
      Schau dir einfach mal das an:

      C#-Quellcode

      1. using System;
      2. using System.Drawing;
      3. using System.IO;
      4. using System.Runtime.Serialization.Formatters.Binary;
      5. namespace SharpexGL.Framework.Renderer
      6. {
      7. [Serializable]
      8. public class Texture : IDisposable
      9. {
      10. /// <summary>
      11. /// Gets or sets the texture.
      12. /// </summary>
      13. public Bitmap Texture2D
      14. {
      15. get;
      16. set;
      17. }
      18. /// <summary>
      19. /// Returns a Texture based on the specific file.
      20. /// </summary>
      21. /// <param name="path">The Path.</param>
      22. /// <returns>Texture</returns>
      23. public static Texture FromPath(string path)
      24. {
      25. if (File.Exists(path))
      26. {
      27. Texture result;
      28. if (!path.EndsWith(".snb"))
      29. {
      30. try
      31. {
      32. var texture = new Texture
      33. {
      34. Texture2D = (Bitmap)Image.FromFile(path)
      35. };
      36. result = texture;
      37. return result;
      38. }
      39. catch (Exception)
      40. {
      41. throw new ArgumentException("The given resource is not image.");
      42. }
      43. }
      44. var binaryFormatter = new BinaryFormatter();
      45. result = (Texture)binaryFormatter.Deserialize(new FileStream(path, FileMode.Open, FileAccess.Read));
      46. return result;
      47. }
      48. throw new FileNotFoundException("The given resource could not be located");
      49. }
      50. /// <summary>
      51. /// Disposes the texture.
      52. /// </summary>
      53. public void Dispose()
      54. {
      55. Texture2D.Dispose();
      56. }
      57. }
      58. }


      Damit kannst du später deinen eigenen ContentManager bauen, der Texturefiles ( in meinen Fall .snb) laden kann. Prinzipiell habe ich damit gemeint, wenn man eine Klasse hat, in der nur ein Bitmap ist, sollte man noch zusätzliche Funktionen gewährleisten, ansonsten hättest du ja nur ein Bitmap Objekt gewrappt.
      Achso, ja, es ging mir auch in erster Linie mal nur ums Wrappen. Erweiterte Funktionalität ist noch nicht mit inbegriffen, soll aber noch kommen (siehe Startpost).
      Sowas wie "FromPath" funktioniert bei meinem System halt nicht, da ja alle Objekte von der Factory erstellt werden, und die hat auch schon ne Funktion mit Pfadangabe.
      Abstrahieren ist kein Selbstzweck.
      Zum Abstrahieren muß man mehrere Strategien haben, die man betrachtet. Und dann findet man gleiche Strukturen, sodass man Code schreiben kann, der diese Gleichheiten über einen Kamm schert.
      Das Herausarbeiten dieser Gleich-Strukturen kann aber erst erfolgen, wenn man mehrere Strategien hat, die man vergleichen kann.
      Du kennst derzeit scheinbar nur die GDI-Strategie, wie gezeichnet wird, und denkst dir, DirectX wird iwie schon genauso ticken. Damit wirst du aber glaub böse auf die Nase fallen, also wennich an DirectX denke, 3-Dimensionalität, Camera, Light - da sehe ich nicht, wie du das unter einen Hut bekommst - und dassis halt das Wesen von Abstraktion, und der Sinn von Interfaces: verschiedene Dinge unter einen Hut bekommen.

      Und deine gewissermaßen am grünen Tisch entstandene Interface-Struktur wird dir schön im Weg stehen, wenn inne Realität die DirectX-Strategie sich zu deim Interface-Wust als ganz inkompatibel entpuppt.
      Das will man dann womöglich nicht einsehen, dasses schlicht inkompatibel ist, und verkompliziert da immer mehr dran rum, bis schließlich mords das Framework bei rauskommt, was jedermann zutiefst beeindruckt durch seine riesige Code-Menge, aber tun tuts eiglich nicht besonders viel.

      Ich sags nochmal - einfache Faustregel: Solange nicht mindestens 2 konkrete Klassen ein Interface implementieren, solange ist das Interface gewissermaßen Onanie, und stört nur die Weiterentwicklung entlang wirklicher Anforderungen.
      @ErfinderDesRades: Also GDI+, OpenGL und DirectX lassen sich schön unter einen Hut bringen, wenn man richtig abstrahiert. Denn so groß sind die Unterschiede Tatsächlich nicht.
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      jetzt nochmal im Detail.
      sowas ist pseudoPolymorph:

      C#-Quellcode

      1. public void DrawBitmap(IBitmap bmp, Artentus.GameUtils.Graphics.Rectangle rect) {
      2. if(!rendering)
      3. throw new InvalidOperationException("Zeichenvorgänge sind nur zwischen \"BeginRender\" und \"EndRender\" gültig.");
      4. Bitmap gdiBmp;
      5. if(bmp is GDIBitmap)
      6. gdiBmp = (bmp as GDIBitmap).GetBitmap();
      7. else
      8. throw new ArgumentException("Diese Bitmap ist nicht kompatibel.");
      9. device.DrawImage(gdiBmp, GDIHelper.ConvertRectangle(rect));
      10. }
      scheinbar wird gegen ein Interface geproggt, aber praktisch wird auf die konkrete Klasse getestet und gecastet, und am schlimmsten ist vlt. noch, dass wenn der cast gelingt, dass dann zusätzlich auch noch das gewrapperte Objekt ausgepuhlt wird.
      Da kannst du wirklich gleich von Anfang an eine Drawing.Bitmap reingeben, und alles ist ganz klar, unds gibt nur eine einzige Zeile.

      richtige Polymorphie wäre, wenn die IBitmap sich dann auch wirklich über einen Kamm scheren ließe, wenn also wirklich IBitmap.Draw(rct) aufgerufen werden könnte, ohne herumgecaste und Fallunterscheidung.

      Ich hab nu kein Schimmer von DirectX2D - wie malt man damit eine Bitmap?
      Bei Direct2D geht das so:

      VB.NET-Quellcode

      1. RenderTarget.DrawBitmap(Bitmap, Rectangle)
      also genauso wie bei GDI, nur die Klassen sind andere.

      Ich könnte es natürlich leicht so machen, dass man statt Renderer.DrawBitmap(IBitmap) IBitmap.Draw() aufrufen kann, das wäre nicht das Problem. Nur machen es nunmal alle Grafik-APIs so, dass man ein Objekt hat, dass alles zeichnet, und alles andere wird nur den entsprechenden Funktionen übergeben. Bei GDI+ ist es ja auch Graphics.DrawImage(Image) und nicht Image.Draw().
      wassis mit Drawing.Drawing2D.Matrix und Drawing.Drawing2D.GraphicsPath?
      Gibts dergleichen auch in DirectX2D?
      Bei meinen Zeichnungs-Übungen hat sich ergeben, dassich nix anneres brauche: Nur .DrawImage(Bitmap) und .DrawPath(GraphicsPath)
      Keine Rectangles, Lines, Texts - das kannich alles mit GraphicsPath darstellen.

      gugge Gezieltes OwnerDrawing


      Ansonsten - wie gesagt: nix inkompatibles zusammenrühren. Da würdich eher immer 2 Überladungen proggen, eine für GDI, eine für DirectX2D, und jeweils auf die spezifischen Typen ausgelegt. Ist insgesamt immer noch weniger Code als diese Casterei, und v.a. übersichtlicher, weil nicht alles ständig umgeleitet wird.

      Aber tatsächlich würde ich getrennte Engines machen, wobei die GDI-Engine ja eiglich fertig ist.

      Den GameLoop kannman ja lassen, aber im Grunde wirklich nur als SpezialTimer für Frames.
      Und dann hat man entweder den GDIRenderer, der rendert mit GDI-Objekten, oder den DirectX2DRenderer, der mittm DirectX-Kram rumfuchtelt.

      Und dann bastelt man sein Game halt entweder mit GdiObjekten oder mit DirectX2DObjekten.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Schau dir mal das an:
      github.com/XemioNetwork/GameLibrary
      Ist ziemlich genau das, was du vor hast (wenn ich nichts falsch verstanden habe) und bietet noch einige Funktionen mehr (z. B. noch Network-, Sound- und Scriptingengines).
      Von meinem iPhone gesendet

      Artentus schrieb:

      Das kommt mir irgendwie nicht richtig vor, so ist das Dispose-Pattern nicht gedacht.
      Gibt es nicht vielleicht auch einen Weg, bei dem die ganzen Objekte Zeit haben, sich ordentlich zu entsorgen?
      Ich sehe den Thread erst jetzt und weiß auch nicht ob es sich inzwischen schon geklärt hat. Jedoch hat ThuCommix recht. Für die IDisposable Schnittstelle ist eine "spezielle" Implementierung vorgesehen(zumindest bei Klassen -> bei Strukturen kann diese so nicht angewendet werden).
      Diese sieht ca. so aus:

      C#-Quellcode

      1. private bool _disposed;
      2. public void Dispose()
      3. {
      4. if(!_disposed)
      5. {
      6. _disposed = true;
      7. Dispose(true);
      8. GC.SuppressFinalize(this);
      9. }
      10. }
      11. protected virtual void Dispose(bool disposing)
      12. {
      13. if(disposing)
      14. {
      15. //dispose managed
      16. }
      17. }
      18. ~ClassName()
      19. {
      20. Dispose(false);
      21. }

      Dies hat mehrere Gründe. Zum einen sollten managed Objekte nicht vom GC-Finallizer Thread freigeben werden und zum anderen kannst du so die Dispose-Methode überschreiben, hast den Zugriff darauf jedoch weiterhin schön geregelt. Wem das Tippen zu dumm wird. Im Anhang hab ich mein Snippet drinnen. Siehe [C#] [OpenSource] [Projektvorstellung] Langzeitprojekt - GameUtils
      Bei Visual Studio 2012 einfach unter "C:\Users\Benutzername\Documents\Visual Studio 2012\Code Snippets\Visual C#\My Code Snippets" einfügen. Bei Visual Studio 2010 müsst ihr selbst schauen, das weiß ich leider nicht mehr auswendig.

      PS: Finde so ne Bibliothek hat durchaus Potential. Da du dich ja auch schon mit der ganzen Mathematik dahinter befasst hast, sollte das auch machbar sein. Fände es toll wenn das sowas wird wie z.b. MonoGame oder das SharpDX Toolkit,...


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „thefiloe“ ()

      @thefiloe
      Das sieht schonmal gut aus, bedeutet aber auch gleichzeitig, dass es so eben nicht geht. Es geht mir ja eben um genau den unmanaged Teil, der dann im Speicher bleib, wenn ich das Programm einfach beende.
      Vielleicht sollte ich da einfach ein Event schmeißen, in dem die Anwendung dann noch ihren "Müll" aufräumen kann.
      Nein wieso? Der Unmangedteil sollte auch freigegeben werden wenn der Destruktor aufgerufen wird(natürlich sofern das Zeug nicht schon manuell freigegeben worden ist).
      Ich muss aber ehrlich sagen, dass mir selbst die Unterscheidung nicht immer ganz leicht fällt. Zum Beispiel ist ein FileStream irgendwie Managed aber hat im Hintergrund ein natives FileHandle welches vom System vergeben wird. Was also?
      Du kannst es übrigens hier nachlesen: msdn.microsoft.com/de-de/libra…o/b1yfkh5e(v=vs.100).aspx wobei mir auffällt, dass meine Implementation minimal abweicht. Aber wie auch immer.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.