Spielaufbau bzw. Codeaufbau



  • Ja gut. Das wäre die api. Ich dachte jetzt eher an das spiel. Also nicht die engine. Wie teile ich das ein?
    Ich mach ein spiel in dem man panzer fahren kann (top-down) auf einer tilemap und das im netzwerk, also multiplayer. Aufgeteilt hab ich das in: scenemanager, dann eine class für die kommunikation mit dem server, eine klasse für den panzer/spieler und eine für multiplayer allgemein (hud etc.).

    Wie würdet ihr es machen?
    Das ist meine eigentliche frage 😛



  • kralo9 schrieb:

    Wie würdet ihr es machen?

    Mehr Klassen 😉 Unsere Software hat etwa 14 000 Klassen, und du willst hier mit unter 10 auskommen?



  • Mechanics schrieb:

    kralo9 schrieb:

    Wie würdet ihr es machen?

    Mehr Klassen 😉 Unsere Software hat etwa 14 000 Klassen, und du willst hier mit unter 10 auskommen?

    😮 OMG

    14k ?

    Was für eine Mörder-Software habt ihr da ^^

    Wie fein sollte ich das unterteilen? Ich würde wohl nichtmal auf 100 kommen 😕



  • Nach dem Single Responsibility Prinzip sollte jede Klasse genau eine Aufgabe erfüllen. Daran sollte man sich auf jeden Fall auch halten.
    So wie du das beschrieben hast, sind es recht komplexe Bereiche, und da reicht jeweils eine Klasse nicht. Vor allem "eine Multiplayer (hud etc.)".
    So allgemein darüber zu spekulieren macht aber keinen Sinn. Du musst zuerst eine genaue Vorstellung von dem haben, was du im Endeffekt haben willst. Denk das erstmal in Ruhe durch, schreib dir alle Anforderungen auf, stell dir vor, wie du sie implementieren würdest. Wenn du schon möglichst viele Anforderungen kennst, denk nochmal über dein Design nach, was kann man zusammenfassen, was passt nicht so ganz dazu, was schaut nach einem Hack aus usw.
    Das Ergebnis kann je nach Entwickler sehr unterschiedlich aussehen. Bei manchen werden in der Tat nur paar wenige dutzend Klassen rauskommen, bei anderen hunderte. Ich tendiere meist dazu, meine Software aus vielen kleinen Komponenten aufzubauen. Andere schreiben sehr große Klassen, die alles machen. Find ich aber nicht empfehlenswert.



  • kralo9 schrieb:

    Mechanics schrieb:

    kralo9 schrieb:

    Wie würdet ihr es machen?

    Mehr Klassen 😉 Unsere Software hat etwa 14 000 Klassen, und du willst hier mit unter 10 auskommen?

    😮 OMG

    14k ?

    Was für eine Mörder-Software habt ihr da ^^

    Wie fein sollte ich das unterteilen? Ich würde wohl nichtmal auf 100 kommen 😕

    das hängt natürlich vom jeweiligen Projekt ab. Du musst eben sinnvolle Moduleinteilungen erkennen, dazu brauch es eben auch Erfahrung.
    Eine Klasse sollte halt für eine Sache dasein und nicht für drölfzigtausend.



  • Ok danke. Ihr habt mir schon etwas weitergeholfen 🙂

    Würde nur zu gern wissen, was für eine Software 14k Klassen hat 😃

    Gruß



  • kralo9 schrieb:

    Würde nur zu gern wissen, was für eine Software 14k Klassen hat 😃

    Es ist jetzt nicht so wahnsinnig viel... Es gibt sicher tausende Programme, die ähnliche groß oder größer sind. Die Software ist halt über 20 Jahre gewachsen (gut, der ganze uralte Code ist mittlerweile neugeschrieben worden, vieles was älter als 10 Jahre ist haben wir nicht mehr), wir haben tausende verschiedener Kunden, die auch viele unterschiedliche Anforderungen haben, deswegen ist die Software auch entsprechend umfangreich.
    Gibt hier auch sehr unterschiedliche "Stile". Es gibt Klassen mit 10 000+ Zeilen, und es gibt Klassen mit 50-100 Zeilen. Ersteres finde ich wie gesagt nicht empfehlenswert. Das kommt dann von Leuten, die dann sagen, ich schreib einen PDM Connector, also heißt meine Klasse "PdmConnector" und macht ALLES, was man für die PDM Anbindung braucht.



  • Und wie würdest du einen pdmconnector unterteilen?



  • kralo9 schrieb:

    Und wie würdest du einen pdmconnector unterteilen?

    Die Frage könnte ich zwar beantworten, weil ich den Code und die Anforderungen kenne, aber zum einen will ich darauf nicht unbedingt eingehen, zum anderen würde es dir nichts bringen, weil du als Außenstehender wenig damit anfangen könntest.
    Ich kann dir vielleicht ein anderes Beispiel nennen, Einlesen und Kategorisieren von externen Daten. Die ursprüngliche Implementierung war auch eine riesige Klasse, mit paar Funktionen zum Einlesen von verschiedenen Datenformaten, und paar Funktionen um die Daten auszuwerten und sie möglichst automatisch oder halb-automatisch zu kategorisieren und anzureichern. Eine Klasse. Dann sind noch etliche Anforderungen hinzugekommen und es wurde Zeit, das zu erweitern oder umzubauen. Wir habens komplett umgebaut. Jetzt gibts ein Framework, das nach dem Pipes and Filters Konzept aufgebaut ist. Jede Klasse macht genau eine Sache, z.B. ein Eingabeformat erkennen, oder versucht die Daten einer Kategorie zuzuordnen, oder führt eine Transformation durch. Sind jetzt über hundert Klassen, ist wesentlich flexibler und wiederverwendbar. Und plötzlich erkennt man auch, dass man das auch an vielen anderen Stellen im Programm verwenden könnte, weil das nicht mehr eine Klasse ist, die eine unüberschaubare Aufgabe löst, sondern ein Framework, dass zwar auch die eine Aufgabe lösen kann, aber auch anderes zusammengesteckt werden kann.
    Die erste Implementierung war direkter, es war eine direkte Lösung des damals vorliegenden Problems (das am Anfang natürlich nicht ganz so komplex war, wie Jahre später). Die neue Lösung ist auch nichts besonderes vom Konzept her, wenn man schon etwas Erfahrung hat. Es ist vielleicht auch gar nicht die beste Lösung, aber auf jeden Fall eine wesentlich besser als eine einzige riesige Klasse, und es ist eine Lösung, die sich bewährt hat, und mit der man gut weitermachen kann. Muss man aber natürlich nicht so lösen. Da sind natürlich etliche Klassen durch die Frameworkstruktur an sich reingekommen. Finde ich auch durchaus nicht schlecht im Hinblick auf Wiederverwendbarkeit, aber man hätts auch direkter lösen können, indem man die ursprüngliche Klasse in "nur" 10-20 neue aufteilt. Wie du siehst, kann man sowas je nach Herangehensweise sehr unterschiedlich lösen.
    Genauso ist es bei deinem Spiel... Wenn du sagst "Multiplayer", sieht mancher vielleicht eine Klase, und ein anderer sieht vielleicht gleich hunderte.
    Ich würde an deiner Stelle nicht versuchen, hier gleich eine optimale Lösung zu finden, das wirst du sehr wahrscheinlich nicht schaffen. Du musst eigene Erfahrung sammeln und Bücher lesen, und in paar Jahren kannst du dann nochmal über die Problemstellung nachdenken.



  • Wow danke für deine ausführliche antwort. Vielen Dank! ( sowas kann man nicht genug loben 😃 ) hast mir sehr geholfen. Jetzt weiss ich zumindest schonmal, wie fein man ein problem zerteilen kann. Wie fein ich jedoch machen werde weiss ich noch nicht. Aber vermutlich so fein wie moeglich, wie es mir geraten wird.

    Danke nochmal 🙂 :xmas1:



  • Hi Kralo9,

    ich habe bei mein letzen Spiel was ich geschrieben habe 3 Programmiertricks gelernt/verfestigt, welche sich als sehr sinnvoll für die Spielprogrammierung erwiesen haben. Ich werde sie in Zukunft nur noch nehmen. Das sind die Tricks:

    1. Model-View-Controler-Prinzip:

    Unterteile alle Klassen in folgende 3 Bereiche:
    -Model = private-Variablen, welche dein aktuellen Zustand von einen Objekt(Panzer, Spielfeld, Menü) beschreiben
    -View = Zeichenmethode, welche wie folgt aussieht: Draw(IDrawing draw) -> Diese Klasse IDrawing erkläre ich bei Trick 2
    -Controler = Eventhandler: Timer_Tick, Key_Press, Mouse_Klick, ...

    2. IDrawing, ISound-Prinzip
    Du hast eine abstracte Klasse IDrawing, welche die Methoden DrawPixel, DrawImage, Draw3DModel, .... hat. D.h. lauter Grund-Zeichenfunktionen. Von dieser Klasse leitest du dann z.B. SFMLDrawing, OpenGLDrawing, Direct3DDrawing ab. D.h., du implementierst die jeweiligen Zeichenfunktionen in der Grafikbibliothek deines Vertrauens.

    3. Level-Klasse mit einer ILevelItem-Liste:
    Dein Spielfeld/Level enthält alle Objekte, welche im Spiel auftauchen. Alle Spielobjekte(Panzer) sind von der abstrakten Klasse ILevelItem abgeleitet, welche wieder nach dem MVC-Prinzip aufgeteilt ist. Die Levelklasse muss also bei jeden Draw()-Aufruf nur noch durch die ILevelItem-Liste durchgehen und item->Draw() aufrufen. Das gleiche gilt für die Controler(Event-Handler)-Methoden.

    Diese Tricks lassen sich sowohl für 2D, als auch 3D-Spiele anwenden. Vielleicht hilft es dir ja weiter. Mir hat es sehr bei meinem SPiel geholfen.



  • Verstehe ich das richtig, dass sich bei dir die Objekte selber zeichnen?



  • Ja, so würde sich z.B. der Panzer zeichnen:

    class Panzer : ILevelItem
    {
    private int x,y;
    public override Draw(IDrawing draw)
    {
    draw->DrawImage("Bild.bmp", x,y);
    }
    }



  • Ok, danke und das hat sich bewährt? Bin mir bei sowas nach wie vor unsicher ob sich die objekte selber zeichnen sollten oder es eine renderklasse dafür herangezogen werden sollte



  • Also man hat ja wahrscheinlich eine endliche Menge von Datenrepräsentationen für grafische Inhalte (Irgendwelche Meshes mit deren Daten etc.) und deren Transformationen, die im Sinne der Renderlogik entkoppelt von den eigentlichen logischen Objekttypen sind (Weswegen ein Panzer-Objekt auch keine eigene Zeichenroutine haben sollte, sondern es sollte eher ein Mesh veröffentlichen).
    Der Renderer kann sich dann die Meshes mit den Transformationen etc. von den Objekten besorgen und selber entscheiden, wie er das ganze rendert. Der Zeichenvorgang muss dann nicht sonderlich viel mit der logischen Hierarchie der einzelnen Objekte zu tun haben...
    So könnte man zum Beispiel ein Overview-Renderer implementieren, der nicht die Meshes rendert, sondern meinetwegen die B-Boxen der Objekte nimmt. Will meinen, ein und das selbe Objekt könnte verschiedene visuelle Repräsentationen bekommen, ohne dass jedes Objekt (Panzer, Haus, Männikiekens) die alle implementieren müsste.



  • Namenloser324 schrieb:

    Ok, danke und das hat sich bewährt? Bin mir bei sowas nach wie vor unsicher ob sich die objekte selber zeichnen sollten oder es eine renderklasse dafür herangezogen werden sollte

    Hat sich bewährt. Du teilst ja die Zeichenfunktionen in allgemeine Zeichenfunktionen(SFMLDrawing) und Objektspezifische Funktionen auf.

    Bei ein starren 3D-Objekt dürfte die Draw-Funktion vom ILevelItem nur eine Zeile enthalten:

    draw->drawTriangleList(this->dreiecke); //Zeichne jedes Dreieck aus der Liste

    Oder: draw->draw3DObjekt(this->_3DObjket); //Das _3DObjekt ist ein Oktree, wo die einzelnen Würfel weitere Unterwürfel oder Dreiecke enthalten



  • Sehr wichtig ist, dass du verschiedene Funktionalität getrennt hältst und möglichst kleine Abhängigkeiten dazwischen hast. Dadurch kannst du gut einzelne Teile ändern, ohne jedes Mal das ganze Projekt anzupassen. Beispiel: Grafik, Benutzereingabe und Spiellogik.

    Mach so wenig wie möglich global und vermeide Singletons weitgehend. Am schlimmsten finde ich Manager-Klassen für alles mögliche, die als Singleton "ja objektorientiert" sind. Dadurch hast du in kurzer Zeit wahnsinnig viele Abhängigkeiten, was Wartung und Fehlerbehebung massiv erschwert. Zudem eröffnet sich eine ganze Reihe von weiteren Problemen im Bezug auf Reihenfolge globaler Initialisierung/Zerstörung und Multithreading.

    Vererbung hat ebenfalls seine Schattenseiten, eine Alternative ist z.B. component-based design. Auch hier gilt: Schau, dass du die einzelnen Teile möglichst modular halten kannst.


Anmelden zum Antworten