Spieleentwicklung mit VB.net und GDI

    • Allgemein

    Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von Markus Jacobs.

      Spieleentwicklung mit VB.net und GDI

      Hallo Zusammen,
      der Schlüssel zu einem erfolgreichen Spiel ist ein guter Aufbau und eine gute Struktur. In dem folgenden Tutorial erkläre ich dich wichtigsten Grundelemente, die bei der Entwicklung helfen. Ich setze Grundkentnisse zu VB.net und GDI vorraus. Dann mal los:

      1.) Start & Screen-Management
      Spoiler anzeigen

      Zuerst richten wir ein neues Windows Forms Projekt ein. "Form1" habe ich hier in "Form" umbenannt. Zuerst ändern wir die Größe der Form und optimieren die Darstellung.

      VB.NET-Quellcode

      1. Public Class Form
      2. Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      3. 'Form adjustieren
      4. Me.Width = 800
      5. Me.Height = 500
      6. Me.Text = "VB-Paradise Beispielspiel...spiel...spiel"
      7. 'Verhindert das Flackern von Elementen
      8. SetStyle(ControlStyles.AllPaintingInWmPaint, True)
      9. SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
      10. End Sub
      11. End Class


      Wenn wir ein Spiel mit verschiedenen Ansichten und Menüs programmieren wollen, empfiehlt es sich, diese in verschiedene Klassen zu trennen. So können wir z.B. das Start-Menu und das Spiel in 2 verschiedene Klassen packen. Dazu erstellen wir zuerst eine Klasse, von der die anderen Klassen erben können. Wir nennen diese Klasse "Screen" und geben ihr die Eigenschaft "MustInherit". Nun erstellen wir 3 Methoden "Draw", "KeyDown" und "KeyUp" und geben ihnen die selben Überladungen wie die Events der Form. In der "New" Methode verknüpfen wir die drei Methoden mit den Events der Form ("AddHandler"). Natürlich kannst du auch ganz einfach neue Events der Form verknüpfen.

      VB.NET-Quellcode

      1. Public MustInherit Class Screen
      2. Public Sub New()
      3. AddHandler Form.KeyDown, AddressOf KeyDown
      4. AddHandler Form.KeyUp, AddressOf KeyUp
      5. AddHandler Form.Paint, AddressOf Paint
      6. End Sub
      7. Friend Overridable Sub Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
      8. End Sub
      9. Friend Overridable Sub KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs)
      10. End Sub
      11. Friend Overridable Sub KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs)
      12. End Sub
      13. End Class


      Und was soll das ganze? Jetzt haben wir diese drei Events der Form auf eine externe Klasse übertragen. Wie können nun ganz einfach neue Klassen erstellen, die von dieser "Screen" Klasse erben und können zum Beispiel die "Draw" Methode füllen. So trennen wir verschiedene Screens und machen das Projekt übersichtlicher.
      Nun erstellen wir zwei Screens, ein Menü-Screen und ein Spiel-Screen. Ich nenne sie jeweils "MenuScreen" und "GameScreen". Mit "Inherits Screen" erben wir Methoden und Variablen der "Screen" Klasse und wir können auf die Paint Methode zugreifen. Um die Funktion zu testen zeichne ich in der Draw Methode jeweils einen Text.

      VB.NET-Quellcode

      1. Public Class MenuScreen
      2. Inherits Screen
      3. Friend Overrides Sub Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
      4. e.Graphics.DrawString("Ich bin hier im Menü", New Font(FontFamily.GenericMonospace, 18), Brushes.Black, New Point(50, 50))
      5. End Sub
      6. End Class

      VB.NET-Quellcode

      1. Public Class GameScreen
      2. Inherits Screen
      3. Friend Overrides Sub Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
      4. e.Graphics.DrawString("Hier seht ihr meinen supertollen Game-Screen", New Font(FontFamily.GenericMonospace, 18), Brushes.Black, New Point(50, 50))
      5. End Sub
      6. End Class


      Wenn wir nun das Spiel starten passiert ... nicht viel. Zuerst müssen wir noch eine Variable erstellen um den Code der Klassen ausführen zu können. Ich erstelle in der "Form" Klasse eine Variable "Public Screen as Screen" und bestimme damit, dass es sich um irgendeine Screen-Klasse handeln muss. Im Form_Load Event deklariere ich diese Variable mit "Screen = New MenuScreen". Damit habe ich festgelegt, dass nur die "MenuScreen" Klasse ausgeführt wird.

      VB.NET-Quellcode

      1. Public Class Form
      2. Public Screen As Screen
      3. Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      4. 'Form adjustieren
      5. Me.Width = 800
      6. Me.Height = 500
      7. Me.Text = "VB-Paradise Beispielspiel..spiel...spiel"
      8. 'Wir setzen die Styles, um das Flackern von Elementen zu verhindern
      9. SetStyle(ControlStyles.AllPaintingInWmPaint, True)
      10. SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
      11. Screen = New MenuScreen
      12. End Sub
      13. End Class


      Wenn wir nun die Anwendung ausführen, wir nur das Paint-Event des Menus gezeichnet. Damit ist dieser Teil beendet.


      2.) Enitäten & Environment
      Spoiler anzeigen

      Im nächsten Schritt möchten wir unsere Spiel auch Inhalt geben. Alle Objekte, die sich in unserem Level befinden sollen, nennen wir Entität (engl. Entity). Alle Entitäten speichern wir in einer Liste. Die Klasse "Environment" soll alle Entitäten verwalten und die Umgebung des Levels simulieren. Zuerst erstellen wir eine neue Klasse mit dem Namen Entity. Diese Klasse enthält alle Informationen über ein Objekt im Level, also Position und Größe. Da die Entität aber immer wissen soll in welcher Umwelt sie sich befindet fügen wir auch noch eine Variable Environment hinzu und deklarieren sie als Environment (Wir fügen diese Klasse gleich hinzu).

      VB.NET-Quellcode

      1. Public Class Entity
      2. 'Public Variables
      3. Public Environment As Environment
      4. Public Position As Point
      5. Public Size As Size
      6. End Class

      Danach erstellen wir die Klasse Environment. Diese Klasse enthält eine Liste mit allen Entitäten, die sich im Level befinden. Wir programmieren dazu noch eine Methode "RegisterEntity" (mit einem Entity als Überladung), die das Entity der Liste hinzufügt und die Variable "Environment" der Entität mit sich selbst verknüpft.

      VB.NET-Quellcode

      1. Public Class Environment
      2. Public Entities As New List(Of Entity)
      3. Public Sub RegisterEntity(ByVal Entity As Entity)
      4. Entity.Environment = Me
      5. Entities.Add(Entity)
      6. End Sub
      7. End Class

      Nun wollen wir ein Environment erstellen, dem Objekt Entitäten übergeben und diese auf der Form zeichnen. Um den Game-Screen anzuzeigen setzen wir im Form_Load Event der Form Screen = New MenuScreen auf Screen = New GameScreen. In der GameScreen-Klasse erstellen wir eine Environment, Player- und eine Obstaclevariable und deklarieren sie als Entitity. In "Sub New" stellen wir jeweils die Position und Größe der Entitäten ein und registrieren sie im Environment Objekt. Danach löschen wir den Inhalt der Zeichenmethode, die einen Text angezeigt hat. In der Zeichenmethode erstellen wir eine Schleife und zeichnen mit der Größe und Position der Entitätenlisten Rechtecke auf die Form.

      VB.NET-Quellcode

      1. Public Class GameScreen
      2. Inherits Screen
      3. 'Variablen
      4. Public Environment As New Environment
      5. Public Player As Entity
      6. Public Obstacle As Entity
      7. Public Sub New()
      8. Player = New Entity
      9. Player.Size = New Size(70, 100)
      10. Player.Position = New Point(15, 15)
      11. Obstacle = New Entity
      12. Obstacle.Size = New Size(300, 50)
      13. Obstacle.Position = New Point(50, 300)
      14. Environment.RegisterEntity(Player)
      15. Environment.RegisterEntity(Obstacle)
      16. End Sub
      17. Friend Overrides Sub Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
      18. For Each Entity As Entity In Environment.Entities
      19. e.Graphics.FillRectangle(Brushes.Red, New Rectangle(Entity.Position, Entity.Size))
      20. e.Graphics.DrawRectangle(Pens.Black, New Rectangle(Entity.Position, Entity.Size))
      21. Next
      22. End Sub
      23. End Class

      Wenn wir unser Spiel starten, werden beide Entitäten gezeichnet.

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „J-F“ ()

      hmm - ich sehe eine eklatante Verwechslungsgefahr, und eine bereits eingetretene Verwechslung bzw. überhaupt fehlende Unterscheidung
      • "Form1" habe ich hier in "Form" umbenannt.
        Also eine Klasse "Form" zu nennen, wos doch schon die Klasse Form gibt, und dein Form erbt auch noch vom Form - das ist so dermaßen zwingend auf Verwechslung hin-programmiert, dass man glaub meiner Beschreibung davon kaum noch folgen kann.

      • VB.NET-Quellcode

        1. AddHandler Form.KeyDown, AddressOf KeyDown
        Hier verwendest du die Klasse Form, als sei es ein Objekt. Die typische fehlende Unterscheidungsfähigkeit von Klasse und Objekt, der VB leider so starken Vorschub leistet - gugge VeryBasics


      (äh - und der Expander nervt)
      Ich finde da macht ein Interface aber mehr Sinn, als eine Klasse...
      Dann wird im eigentlichen Event vom aktuell ausgewählten Interface(bzw. Klasseninstanz) die Methode aufgerufen, denn man verweist innerhalb einer Klasse nicht auf ein Formular, denn eigt. sind diese nicht statisch, ist nur so eine VB Sache, die schnell zu Fehlern führen kann(wie auch bereits EDR bemängelt hat)
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      naja, das Tut soll ja wohl weitergehen, und ich bin gespannt, wie man iwann mal die Szenen umschalten können wird.

      Um so eine Entkopplung zu erreichen muss man zw. den beiden Architektur-Pattern "gemeinsame Basisklasse" und "gemeinsames Interface" wählen - hat jeweils Vor- und Nachteile, und hier ist halt "gemeinsame Basisklasse" gewählt.
      Aber du sagst es doch selber:

      J-F schrieb:

      Zuerst müssen wir noch eine Variable erstellen um den Code der Klassen ausführen zu können.

      Das ist eine ganz allgemein richtige Feststellung, gültig für jede Klasse, und also auch für die Form-Klasse

      VB.NET-Quellcode

      1. dim frm As New Form1
      2. frm.Show


      VB.NET-Quellcode

      1. Form1.Show
      geht leider auch, und scheint bequem. Aber tatsächlich verhindert es Unterscheidungsfähigkeit.
      Das geht auch nur mit Erben der Form-Klasse, für alle anneren Klassen gilt der von dir selbst geprägte Satz: Erst ein Dingsbums erstellen, bevor es etwas tun kann.
      Ja, mir ist natürlich klar. Doch ich bin davon ausgegangen, dass beim Ausführen der Anwendung mit der Klasse "Form1" als "Bauanleitung" ein Objekt "Form1" erstellt wurde, ansonsten könnte ich ja gar nicht auf Variablen und Methoden zugreifen. Wenn ich nun zum Beispiel mit AddHandler AddHandler Form.KeyDown, AddressOf KeyDown das KeyDown Event mit meiner Methode verknüpfe, greife ich nicht auf die Klasse "Form", sondern auf das Objekt "Form" zu.

      Gruß, Jan

      J-F schrieb:

      Leide habe ich, auch nachdem ich mir den Text von ErfinderDesRades durchgeleesen habe, noch nicht verstanden, warum "Form" (ehemals "Form1") eine Klasse und kein Objekt ist.
      Leider ist das auch nicht so einfach :(

      FORM1 ist eine Klasse (steht ja auch immer ganz oben). Genauer gesagt eine von System.Windows.Forms.Form abgeleitete Klasse, mit Deinen eigenen Controls.Das Inherit Statement steht in der zugehörigen Form1.Designer.vb.

      Nun möchte VB.NET als Nachfolger von VB6 besonders einsteigerfreundlich sein und es Anfängern erlauben, die Instanz dieser Form1-Klasse direkt anzusprechen. Dazu legt es im Hintergrund eine Instanz mit gleichem Namen an. Das ist zwar für Neueinsteiger hilfreich, führt aber später zu ärgerlichen und schwer zu findenen Fehlern.

      Zum Beispiel wenn man versucht in einem Thread auf die Form1 zuzugreifen: .NET findet die Instanz nicht mehr und legt wieder hilfsbereit eine eigene Form1-Instanz für den Thread an.
      prinzipiell schon. Am wichtigsten ist halt, du weißt was du tust.
      Und wenn du dieses DefaultForm-Feature nutzen willst, kein Threading verwendest, und deine Forms nicht in einer Klassenbibliothek erstellst, dann geeeht das auch.
      Wichtig ist halt, man weiß was man tut, und da habich bei solchen Konstruktionen einen anneren Eindruck.

      Und du sagst halt selbst:
      der Schlüssel zu einem erfolgreichen Spiel ist ein guter Aufbau und eine gute Struktur.
      Von dem her würdich eher die Finger von dieser VB-Spezialität lassen.
      Und halt klare Benamungen - sagte ich bereits: Von Form ableiten, und den Erben Form nennen ist Wirrsal pur.
      Ähnliche Doppelbenamung auch bei der Screen-Klasse: Wieso heißt die Screen-Instanz ebenfalls "Screen"? In Anbetracht zukünftiger Umschaltbarkeit scheint mir "CurrentScreen" der treffendere Name.

      Ist "Screen" ühaupt der richtige Name (weil sone Klasse gibts ebenfalls bereits, und die bedeutet "Bildschirm")?
      Aber bei Spielen gehts doch immer um Szenen - oder täuschichmich?

      Markus Jacobs schrieb:

      Damit man hier auch mal etwas komplett richtiges im Forum findet.

      Wie kommst du darauf, dass EDR den Maßstab bezüglich Richtigkeit setzt? Jeder Programmierer entwickelt anders. Klar gibt es gewisse Guidelines. Jedoch gehen oftmals die Meinungen stark auseinander. So gibt es auch hier im Forum sehr oft Diskussionen über Punkte in denen sich die Meinungen unterscheiden. Aber du kannst seinen Programmierstil gerne in diversen Tutorials etc. nachlesen.


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