[OpenSource] GameUtils

    • Beta
    • Open Source

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

      Ja, im Testprojekt ist der Direct2D1.1-Renderer ausgewählt, der funktioniert erst ab Windows 8. Nimm den Direct2D1-Renderer oder den GDI-Renderer, dann sollte es funktionieren.
      Um das olle Testprojekt kümmer ich mich eigentlich eh nicht wirklich, mit [C#] [OpenSource] FillTheRow hat man ja sogar ein voll funktionsfähiges kleines Spiel, nen besseres Testprojekt kann man sich gar nicht wünschen. ;)
      was soll das?
      Ein Testprojekt, was nicht lauffähig ist, und FillTheRow gibts nur als Exe?

      Also wenn qualifiziertes Feedback dich interessiert, sollteste gucken, dass man dein Projekt auch testen kann.

      naja - vmtl. stell ich mich malwieder nur doof an.
      Also in FillTheRow finde ich kein SolutionFile, daher weiß ich nicht, wieso du das als OpenSource bezeichnest.

      ah - Github.
      Hmm - da kann ich was downloaden, aber dann fehlen alle möglichen Verweise :(


      also nochmal: es muß doch möglich sein, seine Sources in einem Zip auf VBP einzustellen, und das entpackt man dann, und läuft - oder?

      Oder ich bin halt draussen weil ich nur Vista, und nur 2010.
      Geht es dir darum, dass du allgemein kein NuGet kennst? Oder willst du wissen wie die Pakete heißen?
      @ErfinderDesRades

      Wenn du es noch gar nicht kennst, dann hast du wirklich etwas vom Besten überhaupt verpasst. NuGet ist ein Open-Source Paketmanager. Wird hauptsächlich verwendet um 3rd Party Libs einzubinden. Bei mir ist das bei großen Projekten schon quasi Pflicht, da ich da teilweise über 40-50 Libs drinnen hab und sich da um Updates kümmern ist nahezu unmöglich. Außerdem klickst du da auf installieren und das ist oben. Kein umständliches Herunterladen, Erstellen,... Für mehr Infos einfach hier mal durchlesen: docs.nuget.org/ docs.nuget.org/docs/start-here…packages-using-the-dialog


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

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

      Könntet ihr bitte mal testen, ob ihr bei der angehängten kleinen Anwendung Microruckler bekommt?
      Ich hab das komplette Updateing-System neu geschrieben, um bessere Multicore-Unterstützung zu haben, aber ich hab leider ganz komische Microruckler auf meinem PC. Blaze hats aber auch bei sich schon ausprobiert und hatte nichts dergleichen. Ich würde jetzt gerne wissen, ob es nur an mir liegt, oder an generell was es überhaupt liegt.
      Danke.
      Dateien
      Nope, läuft alles sauber.
      Ab und zu gabs minimale Rucklern beim Resizen. Sonst liefs tadellos.
      Mfg: Gather
      Private Nachrichten bezüglich VB-Fragen werden Ignoriert!


      Jaja, alles normal, während dem Resize wird das FPS-Limit aufgehoben, damit es nicht flackert. ;)

      WEnn nicht zu viele Leute Probleme haben, was ja zum Glück nicht der Fall zu sein scheint, dann werd ich das übrigens vermutlich demnächst releasen.
      Nach langem gibts hier auch mal wieder ein Update. Aber die Wartezeit war nicht umsonst, ich präsentiere das bislang größte Update seit dem ersten Erscheinen von GameUtils - einen kompletten Coreengine-Rewrite.
      Das alte System, basierend auf den zwei Haupt-Interfaces IUpdateable und IRenderable war ebenfalls schon weit ausgebaut, allerdings hatte es einige grundlegende Probleme, die in dem Grundkonzept selbst verankert waren und somit nicht einfach geflickt werden konnten (auch wenn ich das am Anfang versucht habe). Zu nennen wären hier die Asynchronität zwischen Update-Thread und Render-Thread, die zur Folge hatte, dass teile des Gerenderten schon geupdated waren und andere nicht. Außerdem war ein enormer Aufwand nötig, um die Sache wenigstens soweit zu synchronisieren, dass es nicht zu Abstürzen kam, wobei immer noch ein Teil beim Programmierer hängen blieb, sonst gab es immer noch Abstürze. Auch war die Reihenfolge, in der die Objekte geupdatet wurden entscheident, wenn diese miteinander interagieren sollten, und je nach dem konnten unterschiedliche Ergebnisse entstehen. Und zu guter letzt hat es mich auch gestört, dass die Engine, trotz des ganzen Aufwandes, "nur" zwei Threads verwenden konnte.
      All dem setzt das neue System nun ein Ende, IUpdateable und IRenderable sind völlig verschwunden, sie wurden ersetzt durch IBufferedState und IStateRenderer, zwei wesentlich mächtigere - aber auch auf den ersten Blick komplexere - Interfaces. Das System basiert darauf, dass von jedem Objekt im Spiel immer zwei Zustände gespeichert werden, einer zum Rendern und einen zum Updaten. Nach jedem Zyklus werden diese getauscht, sodass der gerade geupdatete Status gerendert werden kann. Als Folge erhält man eine wesentlich komplexere Struktur, aber jegliche Asynchronität und somit auch die daraus resultierenden Probleme verschwinden von der Bildfläche. Da der Update-Vorgang immer auf dem vorherigen Status basiert, ist die Reihenfolge der Updates nun auch unwichtig geworden, was es erlaubt, den Update-Vorgang komplett zu parallelisieren, alle Kerne der CPU werden nun gleichmäßig belastet. Da der Render-Vorgang aufgrund von Beschränkungen in DirectX und GDI+ nachwivor nicht parallelisiert werden kann, ist es daher entscheidend, so viel wie möglich ins Updateing zu verlagern.
      Aber genug geredet, ich werde alles nochmal detailliert in den bald kommenden Tutorials erklären. Um genau zu sein ist dies hier nicht mal ein Full-Release, da ich FillTheRow noch nicht fertig geupdatet habe (gibt momentan Probleme, alte Codes sind nicht wirklich portierbar), ich wollte nur schon die Engine raushauen, damit ich die Tutorials schreiben kann. Ihr könnt sie bereits verwenden, an der Struktur wird sich zum Final-Release nichts mehr ändern, aber es könnten noch Bugs enthalten sein, Benutzung auf eigene Gefahr.

      Edit: ach und @Mods:
      Bitte verschiebt das doch mal in den Showroom, ich weiß nicht, warums hierher geschoben wurde, aber es gehört hier nicht rein.

      Tutorial - Kapitel 1: Grundlagen der Engine

      Info:
      Da die Tutorials später vermutlich nicht alle hintereinander stehen werden, werde ich alle im Startpost chronologisch verlinken, sowie unter jeden Tutorial einen Link zum nächsten einstellen.




      Willkommen zum ersten Tutorial zu meiner Engine. Hier werde ich grundlegende Dinge zur Funktionsweise der Engine erklären, deren Verständnis für alle nachfolgenden Tutorials zwingend Voraussetzung ist. Außerdem werde ich die ersten Schritte, die bei der Entwicklung mit GameUtils immer gleich sind, besprechen und auch Codebeispiele geben.

      Komponenten in der Engine
      Es gibt zwei Hauptkategorien von Objekten, die für die Engine von Bedeutung sind, die erste davon sind die Engine-Komponenten, repräsentiert durch das IEngineComponent-Interface. Dies sind die ausführenden Teile der Engine, jede Komponente hat nach Spezifikation genau eine bestimmte Aufgabe. Manche Komponenten können auf anderen Komponenten basieren, andere wiederum können untereinander Inkompatibel sein und werden bei gleichzeitiger Verwendung Fehler auslösen. Engine-Komponenten können als Singletone designet sein, müssen es aber nicht. Komponenten müssen Klassen sein, andernfalls, werden sie nicht von der Engine akzeptiert (dies habe ich so gelöst, da ich keine immutablen Komponenten haben möchte - zu viel unvorhersehbares).

      Spoiler anzeigen

      Wie ihr seht besitzt das Interface nicht viele Member, ich werde die zwei vorhandenen nun genau erklären:

      Tag: eine eindeutige Identifizierung für die Komponente, anhand der sie später auch abgerufen werden kann. Die Eigenschaft ist vom Typ Object (wobei zumindest ich hier meist einfach Strings verwende) und im Interface als Getter-only implementiert, auch wenn die meisten Komponenten auch einen öffentlichen Setter bereitstellen. 'Null' ist ein gültiger Tag.

      IsCompatibleTo: mit dieser Funktion werdet ihr normalerweise nicht in Berührung kommen, sie ist nur für die Erstellung eigener Komponenten interessant. Mit ihrer Hilfe werden Inkompatibilitäten zwischen verschiedenen Komponenten geprüft, dazu jedoch erst später, in einem anderen Teil des Tutorials, mehr.


      Ressourcen in der Engine
      Die zweite Gruppe von Objekten bilden die über das IRessource-Interface realisierten Engine-Ressourcen. Im Kontrast zu den Komponenten sind dies die Objekte, die die Daten in der Engine repräsentieren. Alle Ressourcen sind zwingend gepuffert, das bedeutet, sie übernehmen angewendete Änderungen an sich selbst erst, sobald sie ein Signal der Engine dafür bekommen.

      Spoiler anzeigen


      Tag: wie auch die Engine-Komponenten besitzen die Ressourcen eine Tag-Eigenschaft vom Typ Object, über die sie angefordert werden können.

      ApplyChanges: diese Methode darf nur von der Engine aufgerufen werden, sie sollte daher explizit implementiert werden. Hiermit sendet die Engine das Signal zum Anwenden aller zwischengespeicherten Änderungen.


      Die GameEngine-Klasse
      Wie der Name bereits vermuten lässt, ist dies die Kernklasse der Engine, hier werden alle Komponenten und Ressourcen verwaltet. Die Klasse ist statisch, ihr könnt nur ein Spiel pro Programm laufen lassen (logisch). Zusätzlich kümmert sich die GameEngine um das Message-System (nur, dass ihr euch nicht über die Methode wundert, ich werde diese erst in einem späteren Tutorial erklären, wenn es ums Message-System geht).

      Spoiler anzeigen


      PostMessage + RegisterMessageListener: werde ich später erklären, siehe oben.

      RegisterComponent: registriert eine Komponente in der Engine. Hier werden die Komponenten auf Kompatibilität untereinander geprüft und global verfügbar gemacht. Die Funktion gibt ein sogenanntes EngineHandle zurück, dieses ist die einzige Möglichkeit, Komponenten wieder zu deregistrieren, indem das Handle disposed wird, sodass sie nicht mehr global verfügbar sind. Wenn ihr nicht plant, eine Komponente wieder zu entfernen, dann könnt ihr diesen Rückgabewert einfach ignorieren.

      (Try)QueryComponent: ruft eine Komponente anhand ihres Typs und wahlweise ihres Tags ab. Das Typargument muss hier zwingen angegeben werden, der Tag ist optional, wird jedoch notwendig, wenn mehrere Komponenten des angegebenen Typs vorhanden sind (wird in diesem Fall kein Tag angegeben, wird immer die erste Komponente zurückgegeben). In der normalen Version wird ein Fehler ausgelöst, sollte keine Komponente mit passendem Typ und Tag gefunden werden, bei der Try-Version wird 'Null' zurückgegeben.

      RegisterResource: macht eine Ressource global verfügbar. Ähnlich zu RegisterComponent wird hier ein ResourceHandle zurückgegeben, dass genauso funktioniert wie ein EngineHandle, bloß eben für Ressourcen.

      QueryResource: ruft eine Ressource anhand ihres Typs und wahlweise ihres Tags ab. Hier gibt es gewollt keine Try-Variante.


      Rendering in Grundzügen
      Da das Thema rund ums Rendering sehr komplex ist (der vielleicht größte Teil der Engine), werde ich mich hier auf das nötigste beschränken und in einem späteren Tutorial tiefer auf die Materie eingehen.

      Eine essentielle Komponente eines jeden Spieles ist der Renderer. GameUtils ist hierbei so entworfen worden, dass alle verfügbaren Renderer absolut identisch funktionieren, egal auf welcher Grafikengine sie basieren. Von Haus aus kommen ein GDI+- und ein Direct2D-Renderer (in einer Version für Windows 7 und einer Version für Windows 8) mit.
      Der Renderer ist für die Darstellung im Spiel verantwortlich und sollte immer als erstes registriert werden (außer ihr wollt einen Logger registrieren, das ist aber ein anderes Thema). Es kann immer nur ein Renderer gleichzeitig in der Engine registriert sein. Ist ein Renderer ersteinmal registriert, sollte auf ihn nicht mehr direkt zugegriffen werden.

      Der Renderer bietet sehr viele Funktionen, wenn ihr schon mit GDI+ gearbeitet habt, werden euch die meisten bekannt vorkommen, sie sind aber oft nicht vollkommen gleich aufgebaut. Diese Funktionen wollen alle mit sogenannten Factory-abhängigen Resourcen gefüttert werden, aber auch dazu in einem anderen Tutorial mehr, für den Moment wollen wir noch gar nichts zeichnen.

      Was uns in diesem Tutorial aber interessiert, ist, wie wir an unseren Renderer kommen.
      Prinzipiell stehen euch zwei Möglichkeiten zur Verfügung, ihr könnt zum einen selbst einen Renderer erstellen, ihr könnt aber auch die Engine für euch wählen lassen, ich werde beide Wege beschreiben.

      Spoiler anzeigen
      Manuell
      Wollt ihr einen Renderer manuell erstellen, so dürft ihr ihn auf keinen Fall instantiieren, stattdessen könnt ihr die statisch Create-Funktion in der Renderer-Klasse verwenden, der ihr mindestens das Control, auf dem gerendert werden soll, mitgeben müsst.
      So sieht es z.B. aus, einen GDI-Renderer zu erstellen:

      C#-Quellcode

      1. var gameWindow = new Form1();
      2. Renderer renderer = Renderer.Create<GdiRenderer>(gameWindow);

      VB.NET-Quellcode

      1. Dim gameWindow As New Form1()
      2. Dim renderer As Renderer = Renderer.Create(Of GdiRenderer)(gameWindow)
      Beachtet, dass der Renderer als Typparameter angegeben wird und ihr nur einen Renderer zurückbekommt, nicht den von euch angegebenen Renderer (den werdet ihr niemals benötigen). Um auf die Renderer-Klasse zugreifen zu können, müsst ihr außerdem GameUtils.Graphics importiert haben.

      Die Funktion hat noch zwei optionale Parameter, presentationOption und antiAliasMode.
      Für ersteren könnt ihr entweder Limited oder Immediately auswählen, hier könnt ihr also angeben, ob ihr die Framerate limitieren wollt (im Normalfall bedeutet das VSync, dies ist aber nicht zwingen der Fall für z.B. GDI, welches VSync nicht unterstützt). Der Standard-Wert ist Limited.
      Für den zweiten Parameter stehen euch Aliased und AntiAlias zur Verfügung, hier bestimmt ihr, ob Kantenglättung aktiviert sein soll. Der Standard-Wert ist AntiAlias.

      Achtung: manche Renderer laufen nicht auf manchen Systemen, in diesem Fall wird die Funktion einen Fehler auslösen.

      Automatisch
      Wenn ihr auf neueren Systemen die beste Perfomance liefern, aber trotzdem noch ältere Systeme unterstützen wollt, dann könnt ihr die Engine mit der IntelligentSelect-Funktion den besten Renderer für das aktuelle System auswählen lassen. Nachteil ist, dass ihr die DLLs für alle Renderer, die ihr mit in die Auswahl nehmt, mitliefern müsst.
      Die Funktion verlangt ein Array oder eine andere Auflistung, die IEnumerable<T> implementiert, von Renderern, die ihr vorher auch alle mit new erstellt haben müsst. Keine Sorge, die so erstellten Renderer sind noch nicht initialisiert und müssen auch nicht disposed werden, die nicht genutzten Renderer könnt ihr also einfach unberührt lassen.
      So sieht es z.B. aus, wenn ihr aus allen drei aktuell bestehenden Renderern automatisch auswählen lassen wollt:

      C#-Quellcode

      1. var gameWindow = new Form1();
      2. var renderers = new Renderer[] { new GdiRenderer(), new Direct2DRenderer(), new Direct2D_1Renderer() };
      3. Renderer renderer = Renderer.IntelligentSelect(renderers, gameWindow);

      VB.NET-Quellcode

      1. Dim gameWindow As New Form1()
      2. Dim renderers() As Renderer = { New GdiRenderer(), New Direct2DRenderer(), New Direct2D_1Renderer() }
      3. Dim renderer As Renderer = Renderer.IntelligentSelect(renderers, gameWindow)
      Auch hier stehen euch wieder die beiden optionalen Parameter zur Verfügung.

      Es wird immer der Renderer mit den höchstmöglichen Systemanforderungen verwendet. Ist keiner der angegebenen Renderer mit dem aktuellen System kompatibel, wird die Funktion einen Fehler auslösen.


      Ein Einblick in den GameLoop
      Auch der GameLoop ist ein komplexes Thema, vor allem seit dem 2.0-Update, wir wollen uns also zunächst nur mit dem Erstellen und dem Starten/Stoppen widmen.

      Beides ist recht einfach, den GameLoop könnt ihr einfach mit seinem Konstruktor erstellen. Dabei wird euch vielleicht der optionale parameter auffallen, dieser soll uns jetzt aber noch nicht kümmern, last ihn also einfach aus.
      Starten und stoppen ist dann fast noch einfacher, ruft dafür einfach die entsprechenden Methoden auf. Es wird übrigens nichts passieren, wenn ihr versucht, einen bereits laufenden GameLoop erneut zu starten oder einen gestoppten GameLoop versucht zu stoppen. Wichtig ist, der GameLoop kann nicht gestartet werden, solange kein Renderer in der Engine registriert wurde (ihr werdet einen Fehler erhalten).
      Da der GameLoop eine Engine-Komponente ist, solltet ihr ihn auch bei der Engine registrieren.

      Spoiler anzeigen
      Erstellen und Starten des GameLoop sieht dementsprechend einfach so aus (ohne das Erstellen des Renderers, ich nehme einfach mal an, dass ihr weiter oben schon alles gelesen habt):

      C#-Quellcode

      1. var loop = new GameLoop();
      2. GameEngine.RegisterComponent(loop);
      3. loop.Start();

      VB.NET-Quellcode

      1. Dim [loop] As New GameLoop()
      2. GameEngine.RegisterComponent([loop]);
      3. [loop].Start()


      Das Anhalten ist dann doch etwas schwieriger, da der Loop gestoppt werden muss, bevor das Control-Handle, auf das gezeichnet wird, zerstört wurde, oder einfacherer ausgedrückt, bevor die Form geschlossen wurde. Ausgehend vom Code unter Rendering werden wir also aufs FormClosing-Event reagieren:

      C#-Quellcode

      1. gameWindow.FormClosing += (sender, e) => loop.Stop();

      VB.NET-Quellcode

      1. AddHandler gameWindow.FormClosing, Sub(sender, e) [loop].[Stop]()


      Das erste "Spiel"
      Es wird zeit, das gelernte in einem ersten kleinen "Siel" anzuwenden. Ihr werdet einen Renderer und einen GameLoop erstellen und anschließend die Anwendung starten. Es wird nur ein shwarzes Fenster zu sehen sein, aber hey, aller Anfang ist schwer. ;)

      Zuerst aber verrate ich euch noch einen kleinen Trick: nicht jedes Fenster/Control ist automatisch geeignet als Rendertarget. Da ihr vermutlich nicht selbst ein solch passendes Control schreiben wollt, könnt ihr die Klasse GameWindowBase aus dem GameUtils.Graphics-Namespace verwenden. WEnn ihr nichts spezielles vorhabt, könnt ihr immer diese verwenden, da sie bereits einige nützliche Funktionen enthält, die euch das Leben möglicherweise erleichtern.
      Da die Klasse abstrakt ist, müssen wir von ihr erben, das nutzen wir gleich mal um dem Konstruktor uns passende Werte zu übergeben:

      C#-Quellcode

      1. public class GameWindow : GameWindowBase
      2. {
      3. public GameWindow()
      4. :base(false, false)
      5. { }
      6. }

      VB.NET-Quellcode

      1. Public Class GameWindow : Inherits GameWindowBase
      2. Public Sub New()
      3. MyBase.New(False, False)
      4. End Sub
      5. End Class
      Das wars auch schon, der erste Parameter des Konstruktors gibt an, ob wir im Vollbildmodus starten wollen, der zweite, ob wir den "Schließen"-Button rechts oben deaktivieren wollen. Beides wollen wir im Moment aber nicht, deshalb false.

      Jetzt könnt ihr wirklich loslegen. Ich empfehle euch, eure Spiele immer von der Main-Methode aus zu starten, andernfalls werdet ihr Probleme bekommen und andere Lösungen werde ich auch nicht präsentieren. Ihr legt also ein neues WinForms-Projekt an und löscht die Form1. In VB müsst ihr zusätzlich noch das Anwendungsframework deaktivieren und die Main-Methode selbst erstellen (schaut einfach im Beispielprojekt nach, wenn ihr nicht versteht, was ich meine).
      Ich werde als beispiel den GDI-Renderer verwenden, ihr müsst also zusätzlich zur GameUtils.dll auch doch die GameUtils.Renderers.GDI.dll referenzieren und GameUtils.Renderers.Gdi importieren.
      So sollte dann die Game-Klasse/das Game-Modul aussehen:

      C#-Quellcode

      1. static class Game
      2. {
      3. [STAThread]
      4. static void Main()
      5. {
      6. Application.EnableVisualStyles();
      7. Application.SetCompatibleTextRenderingDefault(false);
      8. var window = new GameWindow();
      9. GameEngine.RegisterComponent(Renderer.Create<GdiRenderer>(window));
      10. var loop = new GameLoop();
      11. GameEngine.RegisterComponent(loop);
      12. loop.Start();
      13. window.FormClosing += (sender, e) => loop.Stop();
      14. Application.Run(window);
      15. }
      16. }

      VB.NET-Quellcode

      1. Module Game
      2. <STAThread()>
      3. Sub Main()
      4. Application.EnableVisualStyles()
      5. Application.SetCompatibleTextRenderingDefault(False)
      6. Dim window As New GameWindow()
      7. GameEngine.RegisterComponent(Renderer.Create(Of GdiRenderer)(window))
      8. Dim [loop] As New GameLoop()
      9. GameEngine.RegisterComponent([loop])
      10. [loop].Start()
      11. AddHandler window.FormClosing, Sub(sender, e) [loop].Stop()
      12. Application.Run(window)
      13. End Sub
      14. End Module
      Die ersten beiden Zeilen sollten immer dort stehen, das ist vom .Net-Framework so vorgesehen, wir beachten sie also gar nicht.
      Als erstes wird das das Fenster des Spiels erstellt, ich verwende die GameWindow-Klasse von oben.
      Dann erstelle ich den GDI-Renderer mit dem gerade erstellten Fenster als Renderziel und füge ihn im selben Schritt der Engine hinzu. Wie ich schon sagte brauchen wir den Renderer nicht direkt, wir müssen ihn also nicht in einer Variablen speichern, sondern können ihn gleich der Engine übergeben.
      Nun ist der GameLoop dran, wir erstellen ihn einfach so wie oben bereits gezeigt.
      Zu guter letzt müssen wir das Programm noch starten, dafür übergeben wir einfach das erstellte Fenster als Startformular (hier würde normalerweise Form1 übergeben).

      Wenn ihr nun debuggt, sollte ein Fenster mit schwarzem Hintergrund erscheinen. Herzlichen Glückwunsch, ihr habt soeben euer erstes Spiel mit GameUtils erstellt. :)


      Weiter: Tutorial - Kapitel 2: Update-Zyklus und Zustände
      Dateien

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