[OpenSource] GameUtils

    • Beta
    • Open Source

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

      @Artentus: Warum gibt es eig. kein Punkt?
      Naja egal, erstmal vielen Dank das Du dein Code mit uns teilst.

      War gerade auf der suche nach fertigen Vetor Klassen etc. und da viel mir dein GameUtils ein.
      Ist zwar etwas Overkill da ich doch mit GDI+ zeichen wollte mhhhhhhhh.

      Wenn ich da durch steige, würd ich auch dein Renderer nehmen und falls das Mini-Projekt fertig wird, kann ichs dir geben (z. B. könntest Du dies dann als Beispiel (Für 2D) reinstellen) falls dann noch bedarf bestehen sollte.

      Edit: Die TestApp ist ja schon dabei

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Eistee“ ()

      Warum gibt es eig. kein Punkt?
      Was für nen Punkt?

      Erstmal vielen Dank das Du dein Code mit uns teilst.
      Gern geschehen. Ich werde in Zukunft vermutlich alles, was kein fertiges Programm ist, OpenSource hier reinstellen.

      War gerade auf der suche nach fertigen Vetor Klassen etc. und da viel mir dein GameUtils ein.
      Wenn es dir um Vektorarithmetik geht, dann muss ich dich leider enttäuschen, der Vector2 in GameUtils kann im Moment weniger, als der PointF aus GDI, da er nur als Speicher für Koordinaten gedacht ist.
      Du solltest mal einen Blick auf MathUtils werfen, da ist alles drin, was man im Umgang mit Vektoren braucht.
      Hi
      Vector und Punkt sind trotzdem voneinander verschieden. Punkte können verschoben werden, Vektoren sind quasi alle Pfeile. Daher benötigt man auch immer einen Aufpunkt, wenn man Vektoren zeichnet (dieser ist häufig auch einfach (0|...|0)).
      Die Regeln könnten so aussehen:
      Point + Vector = Point
      Point - Point = Vector
      und einen impliziten Operator für PointN->VectorN könnts dann halt auch geben. Box wäre bspw. ein Point3 und ein Vector3 bzw. ein zweiter Point3, aus dem sich der Vektor erzeugen lässt, die das Volumen aufspannen.
      Sauber wär's dann halt mit dieser Unterscheidung in den einzelnen Fällen.

      Gruß
      ~blaze~
      Hab jetzt mal die TestApp ausprobiert - immerhin ein Rechteck zu sehen.
      Nu wollte ich machen, dass es sich auch bewegt, und finde, die Engine unterstützt einen ühaupt nicht darin.
      Also von einer Engine täte ich mindestens erwarten, dass ich mit Tastatur die Richtung und Geschwindigkeit bewegter Objekte beeinflussen kann.

      Gerne auch, dass ich Objekte selektieren kann, drehen, oder auch ein Kollisionens-Ereignis bekomme.

      Update - Echtzeit Physikengine und mehr

      Nach langer Zeit gibt hier endlich auch mal wieder ein Update.

      Highlight ist die 2D-Physikengine, die auch der Grund war, warum es so lange gedauert hat. Später sollen auch noch Sachen wie Reibung, Seile usw. hinzukommen, aber für den Augenblick belasse ich es erst mal so. Einen Überblick über die aktuelle Funktionalität bekommt ihr in der Testanwendung, die ich speziell für die Physikengine angepasst habe.
      Ein dickes Danke geht an @jvbsl: , der mir wirklich enorm dabei geholfen hat, ohne ihn hätte ich es vermutlich nicht geschafft (also liken, abonnieren und favorisieren ;) ).

      Weiterhin habe ich auch am Rendersystem weitergearbeitet. Das Geometry-Interface ist nun bis auf die AddArc-Methode, die ich leider immer noch nicht implementieren konnte (hier ist der Thread), fertig. Auch die Vector2-Struktur hat einige neue Funktionen spendiert bekommen. Ein ITextureBrush-Interface ist auch schon vorhanden, das wird aber erst im nächsten Update fertig sein.

      Außerdem habe ich auch sonst einige kleinere oder größere Änderungen vorgenommen, und die Stabilität des Gameloops verbessert.


      Als nächstes sind jetzt Netzwerk und Input an der Reihe, danach folgt dann UI und vielleicht setzte ich mich auch nochmal an die Physikengine dran (siehe oben). Diese Liste kann natürlich beliebig erweitert werden, je nachdem, was mir oder euch noch so einfällt.


      Edit: ach noch was an die Mods: könnte der Thread vielleicht in den Showroom verschoben werden? Da passt der imo besser rein.
      Ah - jetzt habichs erfolgreich auf 2010 runterschrauben - nun kann man ja schon was sehen :thumbsup:

      Und dann kannman auch den Plan dahinter verstehen, und versuchen zu erklären:

      Also Kern eines Spiels ist das GameLoop - Objekt. Da hinein tut der SpieleEntwickler seine SpielObjekte.
      Der Gameloop tut nix als vonne SpielObjekte immer .Update() und .Render(IRenderer renderer) aufzurufen, also ein SpielObjekt, was sichtbar sein soll, muß IRenderable implementieren, und wenns auch noch beweglich ist, oder sonstwie seinen Status ändert, dann sollte es auch IUpdateable implementieren, damit es eine .Update()-Funktion hat, mit ders sich dann updatet.
      (kann man jetzt diskutieren, ob der GameLoop nicht einfacher 2 Events feuern täte, und ein Spiel-Objekt könnte halt das GameLoop.Update-Event abonnieren, wenns sich updaten möchte.)

      Ein anners KernKonzept von GameUtils speziell ist nun, dass verschiedene Zeichnungs-Technologien unterstützt werden sollen, etwa GDI und SharpDx. Das erfordert einen enormen Aufwand, nämlich es muß ein allgemeingültiger Werkzeugkasten definiert werden, der ausreichend Mal-Möglichkeiten bereitstellt, um Objekte gescheit darzustellen. Das ist hier definiert:

      C#-Quellcode

      1. using System;
      2. using Artentus.GameUtils.Graphics;
      3. namespace Artentus.GameUtils {
      4. public interface IRenderer : IDisposable {
      5. IFactory Factory { get; }
      6. void BeginRender();
      7. void EndRender();
      8. void DrawLine(Vector2 p1, Vector2 p2, IBrush brush, float strokeWidth);
      9. void DrawEllipse(Ellipse ellipse, IBrush brush, float strokeWidth);
      10. void FillEllipse(Ellipse ellipse, IBrush brush);
      11. void DrawRectangle(Rectangle rect, IBrush brush, float strokeWidth);
      12. void FillRectangle(Rectangle rect, IBrush brush);
      13. void DrawText(string text, IFont font, IBrush brush, Rectangle rect, ITextFormat format);
      14. void DrawText(string text, IFont font, IBrush brush, Vector2 pt);
      15. void DrawBitmap(IBitmap bmp, Rectangle rect, float opactiy);
      16. void DrawBitmap(IBitmap bmp, Vector2 pt, float opacity);
      17. void Clear(Color4 color);
      18. void DrawGeometry(IGeometry geometry, IBrush brush, float strokeWidth);
      19. void FillGeometry(IGeometry geometry, IBrush brush);
      20. void Initialize(System.Windows.Forms.Control surface, RendererPresentation presentationOption);
      21. void DrawPolygon(Polygon polygon, IBrush brush, float strokeWidth);
      22. void FillPolygon(Polygon polygon, IBrush brush);
      23. }
      24. }
      Dassis jetzt der allgemeine Standard, den ein Renderer, ob nun auf Gdi-Basis oder auf SharpDx-Basis, erfüllen muß.
      Sieht logisch aus, und einfacher, als es ist, denn die ganzen Typen, die da vorkommen: Ellipse, IBrush, Rectangle, IFont, ITextFormat, Vector2, IBitmap, Color4, IGeometry, Polygon - die sind weder in Gdi noch in SharpDx bekannt.
      Natürlich kennt Gdi Ellipsen, Brushes, Rectangles - aber halt seine eigenen Klassen. Und ebenso SharpDx - das hat auch seine eigenen Klassen dafür.
      Also ein Renderer muß eine ganze Infrastruktur an Konvertern schreiben, sodass etwa ein Gdi-Renderer eine Ellipse in eine Gdi-Ellipse konvertiert (gibts bei dene nich, die nehmen Rectangles dafür), und ein SharpRenderer musse in eine Sharp-Ellipse konvertieren, um rendern zu können.

      Also die 3-fache Arbeit, als wenn einfach nur eine Gdi-Engine gemacht worden wäre:
      1. Standard-Infrastruktur aus Interfaces und Standard-Klassen coden
      2. Konverter-Schicht coden, die den standardisierten Kram in Gdi-Objekte übersetzt
      3. das eigliche Zeichnen mit Gdi
      Aber man hat den Gewinn, dass man auch einen Sharp-Renderer coden kann, und der muß nur noch 2) und 3) bedienen.
      Und es handelt sich um eine echte mehrschichtige Architektur, und zwar an einem sinnvollem Beispiel - sowas ist extrem selten! :thumbsup:
      Die Mehrschichtigkeit kann man auch als Umsetzung des Strategy-Patterns auffassen, also die untere Schicht kann man austauschen (Sharp-Renderer oder Gdi-Renderer) und die Spiel-Logik in der standardisierten Schicht merkt das nichtmal.

      Also der Spiele-Entwickler ist aufgefordert, SpielObjekt-Klassen zu coden, die (nach Bedarf) IUpdateable und IRenderable implementieren, und die werden dem Gameloop zugefügt. Dassis der Rahmen, in dem sich für ihn (den Spiele-Entwickler) alles abspielt.
      Beim Rendern stehen ihm die oben aufgeführten standardisierten Zeichenmethoden zur Verfügung.
      (Dabei frage ich mich grade, obs gut ist, dass IRenderer auch die Factory-Property und Begin-/End-Render() veröffentlicht - das sollte ein Spiel-Objekt doch besser nicht kennen, oder?)

      Irritieren tut mich auch beim PhysikManager, dass da auf eine statische Instanz innerhalb der Program-Klasse zugegriffen wird.
      Da sollte man doch einen Singleton machen, inne PhysikManager-Klasse angelegt, oder?

      Ansonsten ist das mittm PhysikManager sehr fein: Der ist ganz auf der standardisierten Ebene geproggt (hoffe ich jdfs), und bedient dadurch ohne es zu wissen sowohl den Gdi-Renderer als auch den Sharp-Renderer.

      Im Kontext des grundsätzlichen Konzepts binnich mitte Interfaces hier im Großen und ganzen versöhnt, also mit Interfaces kann man sowas lösen (wie auch mit Basisklassen, Delegaten oder auch Events).
      Nur sowas:

      C#-Quellcode

      1. public interface IGameComponent : IDisposable
      2. {
      3. }
      hä?

      Auch mit IBitmap habichn Problem - scheinbar ist nicht vorgesehen, Bitmaps auch schräg anzuzeigen. Ich könnte mir eine Basisklasse Localizable vorstellen, die 3 Punkte einer Raute angibt. Damit kann man sowohl waagerechte Rechtecke darstellen als auch schiefe, und sogar rautenmäßig verzerrte.
      Gdi+-DrawImage(image As System.Drawing.Image, destPoints() As System.Drawing.Point) etwa zeichnet die Bitmap genau in die durch 3 Punkte aufgespannte Fläche.
      vlt. kannman IBitmap komplett rausschmeissen und durch FillRectRectangle ersetzen, wenn man letzterem einen TextureBrush mitgibt. Ich weiß nur nicht, ob GameUtils ein ITextureBrush vorsieht.
      Für die GameUtils ist ja uninteressant, dass eine Bitmap hinter diesem Interface stecken soll - denkbar wären ja auch annere rechteckige Objekte wie Rechtecke oder auch Text.
      Wie gesagt: das IBitmap versteh ich nicht recht: Eiglich könnte es Width und Height doch auch weglassen, und die Draw-Funktion gliche der FillRectAngle-Funktion, nur statt des Brushes übergäbe man die IBitmap - ein leeres Interface, von dem nur der Renderer die BildDaten abzurufen weiß.
      Vielen Dank, das nenne ich mal konstruktive Kritik. :thumbsup:
      Ich werd jetzt einfach mal die einzelnen Punkt abklappern.

      ErfinderDesRades schrieb:

      Dabei frage ich mich grade, obs gut ist, dass IRenderer auch die Factory-Property und Begin-/End-Render() veröffentlicht - das sollte ein Spiel-Objekt doch besser nicht kennen, oder?
      Bei BeginRender und EndRender bin ich einverstanden, nur kann man leider auf einem Interface keine Internal-Member deklarieren. Vielleicht ändere ich das aber auch in ne abstrakte Klasse, so wie @~blaze~: das vorgeschlagen hat, dann würde das funktionieren.
      Die Factory hingegen muss unbedingt bekannt sein, über die werden ja die ganzen Sachen wie Brushes, Fonts, Bitmaps usw. erstellt. Ich bin mit dem Factory-System aber eh etwas unzufrieden, da werde ich möglicherweise nochmal was ändern.

      ErfinderDesRades schrieb:

      Da sollte man doch einen Singleton machen, inne PhysikManager-Klasse angelegt, oder?
      Durch die Möglichkeit, mehrere Physikmanager zu erstellen, gebe ich dem User (also User im Sinne desjenigen, der meine Lib verwendet, nicht im Sinne des Endanwenders) die Möglichkeit, mehrere Physik-"Layer" zu erstellen. Man kann dann z.B. Objekt A und B in den ersten Manager stecken, und Objekt C und D in den zweiten, und dann wird jeweils für A und B und für C und D separat Physik berechnet.

      ErfinderDesRades schrieb:

      Nur sowas:

      C/C++-Quelltext
      1
      2
      3
      public interface IGameComponent : IDisposable
      {
      }
      hä?
      Dieses Interface ist dafür da, IRenderables und IUpdateables in ein und der selben Liste halten zu können, damit ich nicht Klassen, die beides implementieren, einmal zur Renderer-Liste und einmal zur Update-Liste hinzufügen muss.

      ErfinderDesRades schrieb:

      Auch mit IBitmap habichn Problem - scheinbar ist nicht vorgesehen, Bitmaps auch schräg anzuzeigen.
      Das Grafik-Subsystem ist noch nicht fertig. Es sind später Matrizen und ähnliches vorgesehen, mit denen man derlei Zeichenvorgänge manipulieren können soll. Das ist aber alles ein Haufen Arbeit, deswegen ist das momentan noch nicht enthalten.

      Artentus schrieb:

      Dieses Interface ist dafür da, IRenderables und IUpdateables in ein und der selben Liste halten zu können, damit ich nicht Klassen, die beides implementieren, einmal zur Renderer-Liste und einmal zur Update-Liste hinzufügen muss.
      also bei so ganz schrägen Code-Stücke, wie dem hier - da bin ich wieder ganz entschieden für bisserl Kommentation. ;)

      Aber was hälste von solch Add-Methoden:

      C#-Quellcode

      1. public sealed class GameLoop {
      2. //...
      3. public void AddComponent(IRenderable cmp) { }
      4. public void AddComponent(IUpdateable cmp) { }
      5. public void AddComponent<T>(T cmp) where T : IRenderable, IUpdateable { }
      Da kannst du die Dinge sogar in verschiedene Listen räumen, was die Loop-Thread-Codes vereinfacht und einfach logisch stimmiger ist.
      Die letzte Überladung ist die lustigste, der kannste nur Componenten adden, die beides implementieren :D
      kapier ich nix von - scheinbar sollen gewisse Changes erst verzögert zur Wirkung kommen - k.A., ob das sinnvoll ist.

      ich schnall ja schon hierbei ab:

      C#-Quellcode

      1. bool locked = false;
      2. do {
      3. Monitor.Enter(components.GetLocker(component), ref locked);
      4. } while(!locked);
      5. if(components[i] is IUpdateable)
      6. (components[i] as IUpdateable).Update();
      7. Monitor.Exit(components.GetLocker(component));
      Bist du dir sicher, dass Locking so kompliziert sein muss?
      Und ist doch auch sinnlos, falls die Komponente garnet IUpdateable ist.

      Also da sehe ich keinen Grund, nicht 2 verschiedene Listen zu verwenden.

      Und noch genauer hingeguckt: Diese Synchronisation soll ja glaub verhindern, dass ein Objekt gerendert wird und gleichzeitig geupdated. Das kann aber nur auftreten bei Objekten, die auch beide Interfaces implementieren. Alles annere sollte doch von solch PerformanceFressern verschont bleiben, oder?
      Wäre recht einfach zu lösen auch mit meine 3 AddComponent-Überladungen: Nur in der 3. Überladung würden den Componenten Lock-Objekte zugeordnet..
      Das geht leider nicht anders. Frag mich nicht warum, aber wenn ich den Lock in der If-Abfrage durchführe, dann funktionierts nicht wie gewollt. Ebensowenig funktioniert ein normaler lock-Block.
      Wenn Monitor.Enter erfolgreich ausgeführt wird, sollte das aber keinen merklichen Performanceverlust nach sich ziehen.
      k.A., was du meinst - folgendes scheint jdfs. zu gehen:

      C#-Quellcode

      1. private void UpdateLoop() {
      2. Stopwatch sw = new Stopwatch();
      3. while(loopRunning) {
      4. sw.Start();
      5. for(int i = 0; i < components.Count; i++) {
      6. var component = components[i] as IUpdateable;
      7. if(component != null) {
      8. lock(components.GetLocker(component)) {
      9. component.Update();
      10. }
      11. }
      12. }
      13. components.ApplyChanges();
      14. sw.Stop();
      15. double targetMilliseconds = 1000.0 / TargetUpdatesPerSecond;
      16. if(sw.ElapsedMilliseconds < targetMilliseconds)
      17. Thread.Sleep((int)(targetMilliseconds - sw.ElapsedMilliseconds));
      18. sw.Reset();
      19. }
      20. }

      und wenn ich da noch länger drauf gucke, wird daraus:

      C#-Quellcode

      1. private void UpdateLoop() {
      2. Stopwatch sw = new Stopwatch();
      3. while(loopRunning) {
      4. sw.Start();
      5. foreach(var component in components.OfType<IUpdateable>()) {
      6. lock(components.GetLocker(component)) {
      7. component.Update();
      8. }
      9. }
      10. components.ApplyChanges();
      11. sw.Stop();
      12. double targetMilliseconds = 1000.0 / TargetUpdatesPerSecond;
      13. if(sw.ElapsedMilliseconds < targetMilliseconds)
      14. Thread.Sleep((int)(targetMilliseconds - sw.ElapsedMilliseconds));
      15. sw.Reset();
      16. }
      17. }