Grafik und Logik unabhängig voneinander, Grafik leicht austauschbar.



  • Hallo.

    Von meinem Professor haben ich + ein Kollege in Prog 1 noch eine Zusatzaufgabe gestellt bekommen:

    Wir sollen das Spiel "Tic Tac Toe" programmieren (Sprache: Java), jedoch so, dass Grafik und Logik vollkommen voneinander getrennt sind.

    Ziel ist das: Wir sollen als Grafikaufgabe zunächst die normale Console benutzen, jedoch soll es einem unsere Softwarearchitektur möglichst leicht machen, die Grafik später durch ein komplett anderes Grafikmodul auzutauschen (z.b. Darstellung mit swing, oder Darstellung mit OpenGL... oder oder oder).

    Ich gehe das im Moment so an, dass ich eine abstrakte Basisklasse Graphics mit ein paar Methoden habe (draw(), createOutputWindow(), clearOutputWindow() etc.), von der später eine konkrete Implementation erbt.

    Ich benutze das dann so, indem ich eine eigentliche Spielklasse "TicTacToe" habe, die in ihrem Konstruktor

    TicTacToe(Graphics g)

    einen Verweis auf eine Implementation der Graphics-Klasse übernimmt (z.b. ConsoleGraphics, OGLGraphics, SwingGraphics, oder was auch immer), was ja kein problem ist da diese von Graphics erben (Terminologie fehlt mir hier... das ist Polymorphie oder?)

    Innerhalb der TicTacToe Klasse gibts dann eine render() Methode, welche die lokale gespeicherte Referenz auf ein Graphics Objekt benutzt, um davon die diversen Methoden aufzurufen (z.b. myGraphics.draw()).

    Im Moment werden der Methode Graphics.draw() ein Objekt einer anderen abstrakten Basisklasse "Drawable" übergeben. Also so:

    myGraphics.draw(someDrawable).

    Wobei es sich bei den Drawables natürlich auch um irgendwelche Implementationen der abstrakten Drawable Klasse heisst. (Also z.b. ConsoleQuad, SwingSprite, oder sowas).

    Mein Problem besteht jetzt darin:

    Das Ziel ist ja (zumindest, wenn ich die Aufgabenstellugn nicht falsch verstanden habe), dass ich das komplette Grafik Backend austauschen kann, ohne irgendwas in der TicTacToe.render() zu verändern.

    Es soll also möglich sein, eine andere Implementation von Graphics anzufertigen (z.b. SwingGraphics anstatt ConsoleGraphics), diese Implementation dann an TicTacToe zu übergeben, und am Ende soll das Programm immernoch genauso funktionieren, nur eben anders aussehen.

    Und ich habe jetzt das Problem, dass ich einfach nicht auf einen Ansatz komme, wie ich die Basisklassen Graphics und Drawable so weit verallgemeinern kann, dass ich diese innerhalb von TicTacToe.render() so verwenden kann, onne dass beim Austausch der Graphics und Drawable Implemenation irgendwas in TicTacToe.render() ändern muss.

    Hat jemand da von euch eine Idee?



  • ratloser schrieb:

    Ich gehe das im Moment so an, dass ich eine abstrakte Basisklasse Graphics mit ein paar Methoden habe (draw(), createOutputWindow(), clearOutputWindow() etc.), von der später eine konkrete Implementation erbt.

    Dass das Unsinn ist, kannst du schon daran erkennen, dass keine der genannten Methoden auf die Konsolenausgabe passt.
    Es ist nicht nötig krampfhaft zu versuchen alle Ausgabenimplementierungen auf einen Nenner zu bringen.

    Ich benutze das dann so, indem ich eine eigentliche Spielklasse "TicTacToe" habe, die in ihrem Konstruktor

    TicTacToe(Graphics g)

    einen Verweis auf eine Implementation der Graphics-Klasse übernimmt (z.b. ConsoleGraphics, OGLGraphics, SwingGraphics, oder was auch immer), was ja kein problem ist da diese von Graphics erben (Terminologie fehlt mir hier... das ist Polymorphie oder?)

    Innerhalb der TicTacToe Klasse gibts dann eine render() Methode, welche die lokale gespeicherte Referenz auf ein Graphics Objekt benutzt, um davon die diversen Methoden aufzurufen (z.b. myGraphics.draw()).

    Versteh ich nicht, warum hat deine TicTacToe Instanz (ich nehme an dort werden die Züge gemacht, das Spielfeld gespeichert und analysiert?) eine Referenz auf die Ausgabe? Warum hat die selbe Klasse eine draw() Methode? Das ist doch beides Konkraproduktiv.

    Im Moment werden der Methode Graphics.draw() ein Objekt einer anderen abstrakten Basisklasse "Drawable" übergeben. Also so:

    myGraphics.draw(someDrawable).

    Wobei es sich bei den Drawables natürlich auch um irgendwelche Implementationen der abstrakten Drawable Klasse heisst. (Also z.b. ConsoleQuad, SwingSprite, oder sowas).

    Das macht am wenigsten Sinn. Die draw() Methode ist doch in der Graphics (also Ausgabe) Klasse. Wenn ich dieser was übergeben müsste dann doch das Model. Wenn ich dem Ausgabesystem noch sagen muss was es ausgeben muss (SwingSprite - ?) läuft das dem Zweck total zuwider.

    Lies dir mal im Wikipedia den MVC Artikel durch, :xmas1: :xmas2:



  • Vorab: Mir ist klar, dass der Prof auf das MVC Pattern hinaus will (das kannte ich davor auch schon), aber das hilft mir 0 weiter, weil ich jetzt immernoch keinen Ansatz zur Lösung habe.

    Ich versuche auch NICHT das MVC Pattern direkt abzubilden, da ich irgendwie nicht drauf komme wie sich das hier 1:1 abbilden lässt ohne vermurksungen. Wenn du weisst wie, dann werde bitte etwas konkreter.

    BierzeltOmi schrieb:

    ratloser schrieb:

    Ich gehe das im Moment so an, dass ich eine abstrakte Basisklasse Graphics mit ein paar Methoden habe (draw(), createOutputWindow(), clearOutputWindow() etc.), von der später eine konkrete Implementation erbt.

    Dass das Unsinn ist, kannst du schon daran erkennen, dass keine der genannten Methoden auf die Konsolenausgabe passt.
    Es ist nicht nötig krampfhaft zu versuchen alle Ausgabenimplementierungen auf einen Nenner zu bringen.

    Die Methoden heißen eigentlich "createBuffer" und "clearBuffer" und auch mit meiner momentanen Konsolenimplementation der Grafik erzeuge ich damit ein x*y großes Feld, in der dann das Zeug drin steht, das dann auf der Konsole angezeigt werden soll. Bei OpenGL etc. wird das ja nicht anders gehandhabt, da ist der Buffer dann halt das Fenster. Aber wie das gehandlet wird überlasse ich ja der konkreten Implementation.

    Die Namensgebung ist ja auch nur Terminologie ums dem Anwender der Klasse einfacher zu machen. Fakt ist aber: Irgendwie muss ich die Grafik so verwalten können dass sie leicht austauschbar ist. Und ohne von einer abstrakten Klasse von der die austauschbaren Implementationen erben geht das nicht. Wenn doch, dann wäre ich dir SEHR dankbar, das mal zu erklären, bitte auch mit Beispielcode.

    BierzeltOmi schrieb:

    Ich benutze das dann so, indem ich eine eigentliche Spielklasse "TicTacToe" habe, die in ihrem Konstruktor

    TicTacToe(Graphics g)

    einen Verweis auf eine Implementation der Graphics-Klasse übernimmt (z.b. ConsoleGraphics, OGLGraphics, SwingGraphics, oder was auch immer), was ja kein problem ist da diese von Graphics erben (Terminologie fehlt mir hier... das ist Polymorphie oder?)

    Innerhalb der TicTacToe Klasse gibts dann eine render() Methode, welche die lokale gespeicherte Referenz auf ein Graphics Objekt benutzt, um davon die diversen Methoden aufzurufen (z.b. myGraphics.draw()).

    Versteh ich nicht, warum hat deine TicTacToe Instanz (ich nehme an dort werden die Züge gemacht, das Spielfeld gespeichert und analysiert?) eine Referenz auf die Ausgabe? Warum hat die selbe Klasse eine draw() Methode? Das ist doch beides Konkraproduktiv.

    Irgendwie muss die TicTacToe Instanz ja auf ein Graphics Objekt (bzw. dadurch auf das "reinpolymorphierte" ConsoleGraphics etc.) zugreifen können, um seine Methoden aufrufen zu können, oder?

    Oder wie meinst du, soll ich das machen?

    Die Klasse TicTacToe hat keine draw() Methode, sondern eine render() methode, die keine Argumente bekommt. Diese 3 Methoden der TicTacToe Klasse render(), input() und update() sind nur zur übersichtlichen Trennung der 3 Ablaufsabschnitte da, ich könnte das auch alles direkt in run() reinschreiben (oder dann gleich die gesamte Anwendung in main())

    BierzeltOmi schrieb:

    Im Moment werden der Methode Graphics.draw() ein Objekt einer anderen abstrakten Basisklasse "Drawable" übergeben. Also so:

    myGraphics.draw(someDrawable).

    Wobei es sich bei den Drawables natürlich auch um irgendwelche Implementationen der abstrakten Drawable Klasse heisst. (Also z.b. ConsoleQuad, SwingSprite, oder sowas).

    Das macht am wenigsten Sinn. Die draw() Methode ist doch in der Graphics (also Ausgabe) Klasse. Wenn ich dieser was übergeben müsste dann doch das Model. Wenn ich dem Ausgabesystem noch sagen muss was es ausgeben muss (SwingSprite - ?) läuft das dem Zweck total zuwider.

    Ich versteh nicht wo das Problem ist? Wie soll die Graphics.draw() Methode IRGENDWAS ausgeben, wenn sie nicht mal weiß, was?

    Das Drawable erachte ich als extrem nützlich, weil man daraus alles mögliche machen kann. z.B. ein Viereck, ein Bild, einen einfachen String etc.

    Aber ich wende mich gerne davon ab, wenn du etwas konkretere Beispiele bringen könntest, wie du es machen würdest.



  • Übrigens: So gesehen ist das Drawable ja nur ein Model. Das wird vorher in der TicTacToe Klasse angefertigt und dann an Graphics.draw() übergeben.

    Die Drawable Klasse zeichnet ja nichts. Sie enthält nur Daten, die von Graphics.draw() aus ihr rausgeholt werden und dann gezeichnet werden.

    Und genau bei diesen Daten der Drawable Klasse liegt mein eigentliches Problem. Weil ich iregndwas brauche, was sowohl Konsole, als auch Swing, als auch OpenGL aus ihr rausholen kann, damit ich auch tatsächlich bei allen 3 Implementierungen etwas sinniges auf den Bildschirm zeichnen kann, ohne in der TicTacToe Beschreibung des Spiels rumzufummeln.

    Im Moment dachte ich es etwa so:Die abstrakte Drawable hat ein [x][y] Array (den Datentyp weiss ich noch nicht. Ich dachte vll. an eine Art Struktur: "RGBA"), welches sozusagen ihren "Inhalt" darstellt, als Muster, und eine Position als 2 floats (kann ich evtl. noch in ner kleinen Vektorklasse kapseln).

    Das holt sich dann die Graphics.draw() Klasse raus und malt es irgendwie auf den Bildschirm. Wie es das genau macht, wird ja erst in der Implementierung festgelegt.

    Also wie gesagt, das Hauptproblem ist jetzt beim festlegen der gemeinsamen Nenner innerhalb der Drawables.

    Ich mein, ich weiß, man hat in OGL auch nur ein [x][y] Feld in 2D, aber das wird ja in der Grafikkarte, bzw. in OpenGL ganz anders dargestellt. Kann man das einfach so irgendwie in ein [x][y] RGBA Array reinparsen, wenn man sich dazu entscheidet, meine Drawable Klasse abzuleiten und darin für OGL darstellbare Logik darzustellen?



  • Versteh grad nicht wo das Problem ist oder was du für RGB und float werte den Graphics übergeben willst, schließlich ist das Aufgabe des Ausgabesystems festzustellen was und wie gezeichnet wird.

    public class TicTacToe {
       private Feld[][] spielfeld = ...[3][3];
    
       public void zug(...) { ... }
       public Spieler hatJemandGewonnen() { ... }
    }
    
    public class Konsole {
       public Konsole(TicTacToe spiel) {...]
    }
    
    public class SwingGUI {
       public Konsole(TicTacToe spiel) {...]
    }
    public class Main {
       psvm(...) {
          TicTacToe spiel = new TicTacToe(...);
          new Konsole(spiel);
       }
    


  • BierzeltOmi schrieb:

    Versteh grad nicht wo das Problem ist oder was du für RGB und float werte den Graphics übergeben willst, schließlich ist das Aufgabe des Ausgabesystems festzustellen was und wie gezeichnet wird.

    public class TicTacToe {
       private Feld[][] spielfeld = ...[3][3];
    
       public void zug(...) { ... }
       public Spieler hatJemandGewonnen() { ... }
    }
    
    public class Konsole {
       public Konsole(TicTacToe spiel) {...]
    }
    
    public class SwingGUI {
       public Konsole(TicTacToe spiel) {...]
    }
    public class Main {
       psvm(...) {
          TicTacToe spiel = new TicTacToe(...);
          new Konsole(spiel);
       }
    

    Das würde mein Problem lösen. Ist aber zu spezifisch (daher nur auf mein Spiel beschränkt).

    Was mache ich bei deinem Ansatz, wenn ich nicht die Grafik, sondern mein Spiel austauschen will? Sagen wir die Grafik übernimmt diverse Dinger, z.B. paar Shader zeichnen, Sachen auf ne bestimmte Art und Weise transformieren etc.

    Und jetzt will ich diese Grafikschnittstelle wiederverwenden, weil mir ihr Resultat eben ganz gut gefällt. Was mache ich da?

    Richtig: Gar nix. Dann hab ich nämlich ins Klo gegriffen und muss die Grafikschnittstelle entweder komplett umschreiben um sie an das neue Spiel anzupassen oder gleich eine neue Grafikschnitstelle speziell für mein neues Spiel schreiben.

    Ich weiß, dein Ansatz funktioniert gut in den meisten anderen Regionen wo diess MVC-typische Verhalten erzeugt wird, aber für Spiele ist das imo Mist.

    Da besteht mein Spiel nämlich u.U. auch noch aus verschiedenen Zuständen (Hauptmenü, imSpiel, Abspann) etc, die von verschiedenen Klassen gehandled werden. Soll ich jetzt etwa alles, was in diesen Spielzuständen gezeichnet werden soll, in ner großen Management Klasse bereit halten damit deine Implementation der Grafik die Daten rausbekommt? Das ist Resourcenverschwendung hoch n!, da meine Grafik nicht interessiert was evtl. in Hauptmenü enthalten sein könnte während es gerade nur Abspann zeichnen muss.

    Zusätzlich müsste ich solche Sachen wie Layers (verschiedene Sachen werden in bestimmter Reihenfolge gezeichnet etc.) in der Grafikimplementation bei dir schreiben, was aber jegliche Folgebenutzung für ein anderes Spiel wahrsch. ziemlich kaputt macht.

    Oder habe ich da was falsch verstanden?

    Falls nicht, hat jemand ne bessere Idee?

    Die Beschreibung, wie und was die Grafik ausgeben soll, sollte schon irgendwie in meiner Spielklasse erfolgen. Nur das zeichnen an sich selbst sollte die Grafikschnittstelle übernehmen.



  • Eventuell wäre der Thread ja besser im Spieleprogrammierungsforum aufgehoben?

    Ich habs in Rund um die Programmierung gemacht weil ich dachte, dass das eigentlich ein allgemeines Softwarearchitekturproblem ist und ich hier somit eine größere Kundschaft hätte. Aber anscheinend wäre mir dort wirklich mehr geholfen.

    Kann ein Mod evtl. verschieben?



  • Deine Aufgabe ist: Grafik austauschbar machen.
    Nicht: Spiel austauschbar machen.

    Und erst recht nicht: beides austauschbar machen. Das Erste ist einfach(genau deswegen ist da seure Aufgabe), das zweite schon fast unmöglich schwer, das Dritte völlig unmöglich.

    Wie mein Prof mal so schön sagte:
    "Um generalisieren zu können, muss man einschränkende Annahmen über die Daten machen."

    In deinem Fall ist die generalisierung:

    class Grafik{
        zeichneSpielfeld(Spielfeld)
        zeichneMenü(Menü)
    }
    


  • OK, wenn man nur die Aufgabe des Profs löst, ist die Lösung von oben natürlich 100% korrekt.

    Aber rein aus Interesse: Wenn ich mir eine feste Grafikengine zusammenbastle, die leicht erweiterbar sein soll, die ich in verschiedenen Spielen verwenden will, wie wäre dann mein Ansatz?



  • ratloser schrieb:

    Was mache ich bei deinem Ansatz, wenn ich nicht die Grafik, sondern mein Spiel austauschen will? Sagen wir die Grafik übernimmt diverse Dinger, z.B. paar Shader zeichnen, Sachen auf ne bestimmte Art und Weise transformieren etc.

    Und jetzt will ich diese Grafikschnittstelle wiederverwenden, weil mir ihr Resultat eben ganz gut gefällt. Was mache ich da?

    Das ist doch Blödsinn hoch Zehn. Warum sollte ich meine Tic Tac Toe GUI plötzlich als Tetris GUI verwenden wollen? Absurd. Die Zeichenoperationen werden eh vom Subsystem (Swing, OpenGL, Konsole - naja eingeschränkt, gell) erledigt.

    Da besteht mein Spiel nämlich u.U. auch noch aus verschiedenen Zuständen (Hauptmenü, imSpiel, Abspann) etc, die von verschiedenen Klassen gehandled werden.

    Wenn du sowas wiederverwenden willst, dann programmier deine Menüs vernünftig und objektorientiert. Swing wurde auch nicht für exakt eine Applikation geschrieben, sondern wiederverwendbar.

    Die Beschreibung, wie und was die Grafik ausgeben soll, sollte schon irgendwie in meiner Spielklasse erfolgen.

    Nein eben nicht. Dein Spiel besteht aus einem Zustand (Spielfeld, Model) und den Operationen die daraus aufgeührt werden (Züge, Controller). Die Ausgabe ist reine Sache des Ausgabesystems (View!!!)



  • Ich denke du willst zuviel auf einmal... du brauchst etwas, was dir ein Tic-Tac-Toe Feld zeichnet. Also z.B. 'x' auf der Konsole ausgibt oder "KreuzRot.jpg" in einem Fenster anzeigt, wenn der erste Spieler einen Zug gemacht hat. Darum kommst du nicht herum und das wirst du auch schlecht verallgemeinern können. Diese Klasse ist sehr spezifisch (sie muss kein Tetris Feld zeichnen können) und muss für jede neue Ausgabemöglichkeit neu geschrieben werden.

    Was du mit der Engine meinst, kann diese Aufgaben nicht beinhalten, die sind zu spezifisch und nur auf dein Spiel anwendbar. Du kannst dir trotzdem eine "Konsolen-Engine" schreiben, die Dinge enthält wie Text farbig schreiben, Konsole löschen, initialisieren, was immer dir einfällt und du nützlich findest (im Fall von Grafik dein Beispiel mit verschiedenen Layers). So was kannst du dir auch für SWIG schreiben, aber es ist unrealistisch (und auch nicht clever) zu versuchen sie so zu schreiben dass die dann in jedem deiner Projekte einfach gegeneinander austauschbar sind.
    Hingegen kann deine SWIG-Viewer-Klasse die "SWIG-Engine" intern verwenden, ohne dass dein Spiel etwas davon weiss.

    Die Beschreibung, wie und was die Grafik ausgeben soll, sollte schon irgendwie in meiner Spielklasse erfolgen. Nur das zeichnen an sich selbst sollte die Grafikschnittstelle übernehmen.

    Genau diese Beschreibung sollte in der view Klasse enthalten sein, versuch nicht sie irgendwie ins Modell zurück zu würgen.


Anmelden zum Antworten