Wie teilt ihr euren Code auf?

Übersicht BlitzBasic Allgemein

Neue Antwort erstellen

 

PacMani

Betreff: Wie teilt ihr euren Code auf?

BeitragSo, Jan 06, 2013 13:59
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo zusammen,

ich wollte mal eine "Umfrage" machen, wie ihr euren Code von etwas größeren Projekten aufteilt. Da BlitzBasic ja nicht objektorientiert ist, fallen einige gestalterische Freiheiten ja leider weg, die ich gerne hätte.
Nichts desto trotz habe ich auch für BlitzBasic eine Aufteilung entwickelt, die mir noch nicht 100% zusagt und wohl auch nie wird, aber ich erläutere sie einmal. Vielleicht hilft sie auch Einsteigern und gibt ihnen Idee zur Aufteilung ihres Spiels.
Kritik an den vorgestellten Umsetzungsmethoden ist ausdrücklich erwünscht Smile Es muss natürlich nicht so ewig lang werden wie bei mir, ich hatte nur mal Lust, meine Arbeitsweise aufzuschreiben.

Die Hauptdatei
  • Es gibt eine Main.bb-Datei, in der Includes auf allgemein verwendete Dateien / Drittanbieter-Dateien gemacht werden (z.B. meine BlitzExtensions oder SimpleUDP3).
  • Darunter folgen Includes auf die Spielspezifischen-Dateien (z.B. "Game.bb", "Menu.bb", "Block.bb", "Item.bb" etc.)
  • Main.bb enthält weiterhin nur Variablen zur Steuerung der Hauptschleife (damit ein Auflösungswechsel zur Laufzeit möglich wird) wie Run, Running und Restart.
  • Außerdem gibt es Konstanten und eine Variable zum Halten des ganzheitlichen Spielstatus. Die Konstanten bezeichnen nur die Stati wie "StateNone" (beim Erststart), "StateMenu", "StatePlay" oder "StateEdit"
  • Es wird die Hauptschleife aufgerufen, die in der Funktion "Main" sitzt (damit der Code nicht auf Hauptprogramm-Ebene rumlagert - bei mir liegt nie "ausführbarer" / "Logik"-Code auf der Hauptebene).


Die Hauptschleife in der Hauptdatei
  • Wie bereits gesagt sitzt diese in der Methode "Main". Sie besteht genauergenommen aus einer äußeren Neustart-Schleife, die das Spiel ggf. bei Auflösungswechsel neu startet, und der eigentlichen inneren Hauptschleife, die so lange läuft, wie die Variable "Run" True ist.
  • Bevor die innere Hauptschleife betreten wird, aber schon in der Neustartschleife, werden alle Ressourcen des Spiels initialisiert, die bei einem Auflösungswechsel verloren gehen könnten (wie Meshes, Texturen, etc.). Dazu wird die Funktion "InitRessources" aufgerufen, die auch in der Hauptdatei steht.
  • InitRessources() ruft die einzelnen InitXyzRessources()-Funktionen der weiteren Spiel-spezifischen Include-Dateien auf, wie z.B. "InitGameRessources()" wenn man eine "Game.bb" angebunden hat (diese setzt dann z.B. auch den Grafikmodus und richtet das Ausgabefenster ein, erstellt die globale Kamera oder lädt eine globale Schriftart, etc.) oder "InitItemRessources()" wenn es etwas wie Items gibt. Hiermit registriert sich sozusagen jedes Spielelement in der Initialisierungs-"Liste".
  • Es werden die Variablen zur Steuerung der Hauptschleife(n) gesetzt (die Kommentare im nachfolgenden Code erläutern sie genauer).
  • Dann kommt die eigentliche, innere Schleife. Diese verarbeitet zuerst durch Aufruf der Methode Input() alle möglichen Eingaben. Genau das ist ein wenig doof: Diese Input-Methode liegt zentral in der Hauptdatei, aber je nach Spielstatus (Menü oder Ingame oder Leveleditor) würde ich diesen Code am liebsten in passendere Dateien einzeln auslagern. Momentan würde ich das mit einem Select Case machen und dann je nach Spielstatus die passenderen "InputXyz"-Methoden in den einzelnen Dateien aufrufen.
  • Das Spielgeschehen wird in der "Update"-Methode aktualisiert. Hier das gleiche Problem wie bei Input(): Die Methode ist zentral und muss je nach Spielstatus kleinere Methoden aufrufen.
  • Das Spiel wird mit "Draw()" gezeichnet. Es wird auch hier wieder je nach Spielstatus eine andere Methode aufgerufen werden.
  • Wird irgendwann von einem Programmteil die "Run"-Variable auf False gesetzt, so wird diese innere Schleife verlassen und FreeRessources aufgerufen. Diese Methode gibt alle verwendeten Ressourcen von den Spielteilen ordentlich frei, sodass keine Geister mehr nach ggf. anfallendem Auflösungswechsel bleiben (so habe ich gesehen, dass ohne FreeXyz-Aufrufe die Handles von Variablen weiterhin nicht 0 sind, aber beim Zugriff darauf nur Fehler auftreten).
  • Die "Restarting"-Variable wird auf True gesetzt, damit in Programmteilen erkannt werden kann, dass nun, wenn überhaupt erwünscht, ggf. ein Neustart zur Laufzeit auftritt. Ich glaube, ich habe damit unterdrückt, dass z.B. die Menümusik neu gestartet wird, wenn die Auflösung geändert wird. So läuft sie einfach weiter.
  • Hat ein Programmteil neben "Run" auch "Restart" auf True gesetzt, so läuft die äußere Neustart-Schleife wieder von oben los, initialisiert die Ressourcen nun für die neue Auflösung.


Hier der lange Beispielcode, mit meinen typischen Sektions-Kommentaren:
Code: [AUSKLAPPEN]
;Main #############################################################################################

;Includes =========================================================================================
Include "..\..\Includes\BlitzExtensions.bb"
Include "..\..\Includes\FrameIndependency.bb"
Include "Common.bb"
Include "Game.bb"
Include "Menu.bb"

;Variablen & Konstanten ===========================================================================

;Allgemein ----------------------------------------------------------------------------------------
Global Run        = True  ;Wenn True wird die Hauptschleife ausgeführt
Global Restart    = False ;Wenn True wird das Spiel nach Verlassen der Hauptschleife neu gestartet
Global Restarting = False ;Wenn True wird das Spiel gerade neu gestartet

;Spielzustand -------------------------------------------------------------------------------------
Const StateNone = 0
Const StateMenu = 1
Global State = StateNone

;Spiel ausführen ----------------------------------------------------------------------------------
Main()

;Funktionen =======================================================================================

;Hauptfunktion des Spiels -------------------------------------------------------------------------
Function Main()
    Repeat
        ;Initialisierung durchführen
        InitRessources()
       
        Run = True
        Restart = False
        Restarting = False
       
        ;Hauptschleife
        While Run
            Input()
            Update()
            Draw()
        Wend
       
        ;Ressourcen freigeben
        FreeRessources()
       
        Restarting = True
    Until Restart = False
    End
End Function

;Initialisiert alle Spiel-Ressourcen --------------------------------------------------------------
Function InitRessources()
    InitGameRessources()
End Function

;Gibt alle Spielressourcen frei -------------------------------------------------------------------
Function FreeRessources()
    FreeGameRessources()
End Function

;Bearbeitet Eingaben ------------------------------------------------------------------------------
Function Input()
   
End Function

;Aktualisiert den Spielzustand --------------------------------------------------------------------
Function Update()
    ;Standardzustand setzen
    If State = StateNone Then
        SetState(StateMenu)
    End If
   
    If State = StateMenu Then
        UpdateMenu()
    End If
End Function

;Zeichnet das Spiel -------------------------------------------------------------------------------
Function Draw()
    ;Bildschirm löschen
    Cls()
   
    ;3D-Grafik
    RenderWorld()
   
    ;2D-Grafik
    If State = StateMenu Then
        DrawMenu()
    End If
   
    ;Buffer austauschen
    FlipT() ;FlipT = "Frameunabhängiger Flip", in meiner FrameIndependency.bb
End Function

;Setzt den aktuellen Spielzustand -----------------------------------------------------------------
Function SetState(NewState)
    State = NewState
End Function


So sieht dann z.B. die Game.bb-Datei dazu aus:
Code: [AUSKLAPPEN]
;Game #############################################################################################

;Variablen & Konstanten ===========================================================================

;Bemessungen --------------------------------------------------------------------------------------
Const BlockSize = 1
Const BlockDepth = 0.4

;3D-Grafik ----------------------------------------------------------------------------------------
Global CamMain
Global LitMain
Global ObjPlane
Global TexPlane

;2D-Grafik ----------------------------------------------------------------------------------------
Global FntGame

;Funktionen =======================================================================================

;Gibt die Haupt-Spielressourcen frei --------------------------------------------------------------
Function InitGameRessources()
    ;Ausgabefenster einrichten
    AppIcon("Icon.ico")
    AppTitle("Irgendwastolles 3D")
    HidePointer()
   
    ;Grafikmodus setzen
    GraphicsMode(1024, 768, GraphicsModeFakeFullscreen)
   
    ;Zufallsgenerator
    SeedRnd(MilliSecs())
   
    ;3D-Grafik
    CamMain = CreateCamera()
    PositionEntity(CamMain, 0, 10, 0)
    RotateEntity(CamMain, 90, 0, 0)
   
    LitMain = CreateLight()
   
    ObjPlane = CreatePlane()
    PositionEntity(ObjPlane, 0, -BlockDepth / 2, 0)
    TexPlane = LoadTextureEx("Plane")
    EntityTexture(ObjPlane, TexPlane)
    EntityPickMode(ObjPlane, EntityPickModePolygon)
   
    ;2D-Grafik
    FntGame = LoadFont("Tahoma", 20)
    SetFont(FntGame)
End Function

;Gibt die Haupt-Spielressourcen frei --------------------------------------------------------------
Function FreeGameRessources()
    ;2D-Grafik
    FreeFont(FntGame)
End Function

Function InputGame()
    ;Ingame-Eingaben verarbeiten
End Function

Function UpdateGame()
    ;Ingame aktualisieren
End Function

Function DrawGame()
    ;Ingame zeichnen
End Function



Ich versuche letztendlich auch, Hauptdateien und Includes etc. in der Projekthierarchie sauber zu trennen. Bei IDEal sieht das von Bomby 3D so aus:
user posted image
 

PhillipK

BeitragSo, Jan 06, 2013 14:49
Antworten mit Zitat
Benutzer-Profile anzeigen
In grundzügen machen wir es ähnlich. Allerdings kann ich es nicht so recht auf BB beziehen, da ich blitzmax nutze. Prinzipiell lässt sich die idee aber auch anderweitig anwenden:

- MainDatei -> "Spielname.bb" oder sonstiges.
Sie enthält die einbindung von modulen (Import.. gibts das unter BB?). Im prinzip gekapselte programmteile die eigenständig laufen.
Aussderdem gibts diverse includes für allerlei, sortiert nach Typ. Erst wichtiges, dann grundlagen / hiflszeugs.
ABER: Die mainDatei includiert nicht alles. Include ich als beispiel "gui.bb", werden untergruppen die zu gui gehören im kopf der gui.bb includiert -> GuiButton.bb, GuiFenster.bb, etc.
die hauptdatei hält also nur übergruppen-includes.

Danach folgen wichtige initialisierungen ( allerdings gekapselt: Ich lade keine 1000 texturen, sondern habe zb LoadTextures() welche ich mit einem string (dateipfad) aufrufe )
Die Hauptdatei bleibt immer SAUBER - 100 zeilen code sind schon viel.

- Untergruppen -> Jedes logische bündel (spieler, gegner, waffen, quests, whatever) wird in einer gesonderten datei behandelt.
Für bb (sprich nichts objektorientiert) würde ich ebenfalls für jedes logische bündel eine art prefix in den funktionen verwenden: GUI_CreateButton() als beispiel. Oder eben: Player_Init()
In blitzmax sieht das ganze ein bissl einfacher aus: TGuiButton.Create() , TPlayer.Init()


Soviel zur programmstruktur.
In der dateistruktur teile ich im hauptordner mehrere untergruppen ein. Jedes eigenständige päckel (Beispiel: Gui) erhält einen unterordner. Sollte es zuviel sein, landen sie im "SpielPfad\Imports\Name", andernfalls im ordner "SpielPfad\Name".
Die includes landen im unterordner "SpielPfad\Include".
Ressourcen landen in dem unterordner Ressourcen.
Temporäre dateien die ich erarbeite, als beispiel roh-export von Blender, welche noch mit Gimp überarbeitet werden sollen, landen in "SpielPfad\Workspace\" - dieser ordner DARF nicht eingebunden werden. Es diehnt lediglich zur sortierung meiner ressourcen. Sind die daten fertig, werden sie unter "SpielePfad\Ressourcen\GFX" abgelegt. Sounds landen als beispiel in "SpielePfad\Ressourcen\sounds\"

Kleine anmerkung:
Um die maindatei sauber zu halten, habe ich auch einen include "Constants.bb" - diese landet auch im include der maindatei.
Sauber kommentiert ist auch hier alles wieder in bündel aufgeteilt: ";------- GUI -------" -> darunter landen zb "GUI_SLIDER_VERTICAL%" oder auch "GUI_WINDOW_DRAGABLE". Merke: konstanten sind gut ausgeschrieben und haben uppercase-buchstaben. Wörter sind durch _ getrennt.
Locals sind überwiegend kurz gehalten, nen kleiner kommentar daneben und kein _.
global sind etwas "besser" ausgeschrieben: "WindowWidth" - anfangsbuchstabe eines buchstabens ist groß, keine _ zwischen den trennung. So kann man schon direkt beim lesen erkennen, was was ist.
Funktionen fangen meist klein an: "isWindowDragable%()" - kurz, aussagekräftig, return bereits am anfang zu erkennen. "is" ist mein schlagwort für "gib mir boolean, du funktion!!11" -> in bb / bmx also ein integer der typischerweise 0 oder 1 ist.
So kann man das ewig fortführen, aber naja. Ich belass es bei der kurzen einleitung zu meinem stil.

DAK

BeitragSo, Jan 06, 2013 19:11
Antworten mit Zitat
Benutzer-Profile anzeigen
Passt vielleicht nicht ganz direkt auf das Thema, aber Objektorientierung lässt sich ja leicht genug faken (vielleicht mit Ausnahme der Vererbung und von Interfaces).

Ich weiß, dass gerade das das Tolle ist, aber von der Codeaufteilung geht das trotzdem ähnlich gut.

BlitzBasic: [AUSKLAPPEN]
Type TVector2f
Field x#
Field y#
EndType

Function TV2f_add(vector1%, vector2%)
v1 = Object(vector1%)
v2 = Object(vector2%)
v1\x = v2\x
v1\y = v2\y
EndFunction

Function TV2f_length(vector%)
vec = Object(vector%)
Return Sqr(vec\x*vec\x+vec\y*vec\y)
EndFunction

Neue Antwort erstellen


Übersicht BlitzBasic Allgemein

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group