Verständnisfrage
-
árn[y]ék schrieb:
c0ff33.alex schrieb:
Dann plane deine Hierarchien bevor du sie implementierst damit sowas nicht vorkommt.
Sehr naiver Ansatz!
Wenn er aufhört bei mir zu funktionieren suche ich mir einen neuen .
Ich will ja nicht sagen dass OOP gut für alles ist. Aber wenn man sich dafür entscheidet, soll man halt vorausplanen.
-
Da ich zur Zeit Teilzeit an einem Casual Game arbeite, stelle ich immer häufiger fest, das ein rein Objektorientierter Ansatz irgendwie immer in einer Sackgasse endet.
Man ist sich nicht über Verantwortlichkeiten im Klaren, "Kommunikation" zwischen Objekten wird letztendlich über Delegates o.ä. Prinzipien abgefackelt und da du i.d.R. später bestimmten Klassen mehr Funktionalitäten einräumst als jemals geplant waren, endet das Ganze bei uns momentan in einer recht monolitischen Sprite Klasse und einem Rahmensystem (Screens etc.), welche untereinander per Delegates Kommunizieren.
Ich stelle immer wieder fest, das OOP toll ist, wenn man theoretische Beispiele mal schön veranschaulichen will. In der Praxis wird der Ansatz eigentlich meistens dazu benutzt, gemeinsame EIGENSCHAFTEN zu gruppieren, aber wie im Beispiel von Rapso schön zu sehen ist, ändert sich ständig der Kontext.
Du müsstest also dein OOP Modell ständig anpassen, mal abgesehen von der statischen Verhaltensweise.
In Spielen möchtest du im Idealfall dynamisch sagen können, welche Eigenschaften und Verhaltensweisen ein Objekt hat. Du willst Rotation? Füge Sie dynamisch hinzu, leite aber nicht von einem Rotationssprite ab.
Zugegeben, für die Standard World Transformationsmember hinkt der Vergleich vielleicht ein wenig, aber alles darüber kannst du statisch eigentlich kaum mehr kontrollieren.Ich kenne allerdings auch leider keine 99% Lösung, wie man diesbzgl. am besten vorgeht. In den einschlägigen Magazinen zur Spieleentwicklung scheint man aber immer mehr in Richtung dynamischer Modelle zu wandern, wie immer das im Kern aussehen mag.
-
xaoC schrieb:
Man ist sich nicht über Verantwortlichkeiten im Klaren, "Kommunikation" zwischen Objekten wird letztendlich über Delegates o.ä. Prinzipien abgefackelt und da du i.d.R. später bestimmten Klassen mehr Funktionalitäten einräumst als jemals geplant waren, endet das Ganze bei uns momentan in einer recht monolitischen Sprite Klasse und einem Rahmensystem (Screens etc.), welche untereinander per Delegates Kommunizieren.
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
-
c0ff33.alex schrieb:
Das Beispiel mit dem Bauhof, der mal Gebäude und mal Fahrzeug ist, macht jede Hierarchie platt.
Dann plane deine Hierarchien bevor du sie implementierst damit sowas nicht vorkommt. Ich meine, wenn man weiss, dass man später von mehreren Klassen mit der gleichen Basis ableitet, soll man halt sein Design überdenken.
Es mag ja sein, dass *Mehrfach*vererbung einem Probleme bereiten kann, aber das ist kein Grund, auf OOP zu verzichten.Naiv ist, OO mit Vererbung gleichzusetzen.
Vererbung ist oft ein gutes Mittel in Bibliotheken. Bei der Modellierung von Objekten aus der realen Welt ist es eben meistens keine simple "ist ein" Beziehung mehr.
-
dot schrieb:
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
"Kaputtes Design" ist in 99% der Fälle ein Totschlargument.
-
lolalter schrieb:
dot schrieb:
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
"Kaputtes Design" ist in 99% der Fälle ein Totschlargument.
Na schau dir doch mal an was er schreibt:
xaoC schrieb:
Man ist sich nicht über Verantwortlichkeiten im Klaren, "Kommunikation" zwischen Objekten wird letztendlich über Delegates o.ä. Prinzipien abgefackelt und da du i.d.R. später bestimmten Klassen mehr Funktionalitäten einräumst als jemals geplant waren, endet das Ganze bei uns momentan in einer recht monolitischen Sprite Klasse und einem Rahmensystem (Screens etc.), welche untereinander per Delegates Kommunizieren.
Wenn das für dich nicht nach kaputtem Design klingt, dann kann ich auch nicht helfen...
-
dot schrieb:
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
Es gibt kein Design. Es ist unser erstes Projekt. Natürlich gibt es Überlegungen und Ideen, wie man etwas umsetzt, basierend auf Erfahrungen, aber es ist meiner Erfahrung nach so gut wie nie Praktikabel, etwas "Durchzudesignen".
Speziell im Game-Design muss man erstmal ein Gefühl dafür entwickeln, wie man ein einfaches Framework aufzieht, wie sich Objekte unterhalten, und zwar an konkreten Anforderungen. Ich will keine Engine schreiben, die in der Theorie toll aussieht, mit der man aber praktisch nichts anfangen kann.
Die Anforderungen ergeben sich durch das Spielekonzept. Genau dieses muss so klar wie möglich sein. Dann fängst du an, dir Gedanken zu machen wie du anhand dieses Konzept ein primitives Framework aufziehen musst.
Und hier passiert nun mal die Drecksarbeit. Du stellst schnell fest, dass das Design des Frameworks sich dynamisch an Anforderungen anpassen muss und sich dadurch teils erheblich ändert. Du hast noch keine "Foundation". Du musst erst in die Fehler rennen, um sie zu verstehen. Dann kannst du dir Gedanken darüber machen, wie du dein OOP Konzept aufsetzt (oder ob überhaupt ;-)).
Anders herum programmierst du einen theoretischen, unbrauchbaren Wasserkopf ohne Praxisrelevanz.
-
xaoC schrieb:
Natürlich gibt es Überlegungen und Ideen, wie man etwas umsetzt, basierend auf Erfahrungen, aber es ist meiner Erfahrung nach so gut wie nie Praktikabel, etwas "Durchzudesignen".
Exakt, darum ist es auch so wichtig, ständig zu refactoren...
xaoC schrieb:
Speziell im Game-Design muss man erstmal ein Gefühl dafür entwickeln, wie man ein einfaches Framework aufzieht, wie sich Objekte unterhalten, und zwar an konkreten Anforderungen.
Richtig, daher fängt man ungefähr mal grob irgendwo an und passt das Design iterativ entlang des Weges an. Ein Prozess bekannt als "Refactoring".
xaoC schrieb:
Und hier passiert nun mal die Drecksarbeit. Du stellst schnell fest, dass das Design des Frameworks sich dynamisch an Anforderungen anpassen muss und sich dadurch teils erheblich ändert.
Mit anderen Worten: Du musst ständig refactoren.
xaoC schrieb:
Du musst erst in die Fehler rennen, um sie zu verstehen.
Und wenn du die Fehler verstanden hast, besserst du sie aus -> Refactoring.
-
dot schrieb:
Wenn das für dich nicht nach kaputtem Design klingt, dann kann ich auch nicht helfen...
Nochmal kurz:
Schnapp dir deinen Gamma, kritzel dir Kombinationen aus 10 Design Patterns zusammen und sei glücklich, wenn am Ende alles aufgeht.
Hab ich auch schon oft probiert. Und mindestens genau so oft verworfen.
Grundlagenforschung ohne tiefere Relevanz, eigentlich nur dann sinnvoll, wenn du abgesteckte, relativ statische Anforderungen hast. Dann kann dir so ein Entwurfsmuster weiterhelfen.Vielleicht verstehe ich dich ja falsch, aber bei dem Wort "Design" denke ich an stundenlanges Grübeln über abstrakte Anwendungsfälle, um den "perfekten" Entwurf zu basteln.
Wenn ich 5 Interfaces implementieren muss, um einem Sprite neue Funktionalität hinzuzufügen, ist das IMO der falsche Ansatz, darum geht es mir.
-
Dann verstehst du mich falsch. Unter Design versteh ich den Entwurf von Software. Das ist in meinem Augen ein sehr organischer Prozess, Design ist einer ständigen Evolution unterworfen. Stundenlanges Grübeln und malen irgendwelcher Diagramme hilft vielleicht beim wählen der Anfangswerte der Iteration, aber das eigentliche Design ist nicht statisch, sondern entwickelt sich während die Software entwickelt wird.
-
dot schrieb:
[...] -> Refactoring.
Irgendwie sehe ich da jetzt keinen Widerspruch zu meinem Geschreibsel.
Außer evtl. dass du ein klassisches, statisches OO Klassenkonzept nicht mal eben "Refactorst".
-
xaoC schrieb:
Außer evtl. dass du ein klassisches, statisches OO Klassenkonzept nicht mal eben "Refactorst".
Was genau verstehst du unter einem "klassischen, statischen OO Klassenkonzept"?
-
Ein hauptsächlich auf Vererbung basierendes Konzept, welches Eigenschaften und Verhaltensweisen in Klassen oder Strukturen presst.
Beispielsweise die grundsätzliche Trennung von grafischen Eigenschaften (Draw) und nicht grafischen Eigenschaften (Update Only) in getrennten Klassen, womöglich nochmals abgeleitet von einem abstrakten "WorldEntity" oder so ähnlich.
Was ist z.B. ein BoundingVolume? Es hat eine WorldPosition, eine Rotation u.s.w., aber eigentlich keine Zeichenfunktionalität. Später würdest du gerne mal alle Boundingvolumes zu Debug Zwecken per Draw ausgeben, aber deine Klassenhierarchie verbietet dir das. Deine Logik bzgl. BoundingVolumes baut mittlerweile auf "WorldEntity" o.ä. auf, Refactoring bedeutet also: Du hast ein grundsätzliches Problem, was sich durch den gesamten Code zieht.
Umbauen ist evtl. nicht nur zeitaufwendig, sondern auch gefährlich, weil du getesteten Code quasi wieder neu validieren musst.
In meinen Augen hört bei sowas das statische Design auf, praktikabel zu sein.
Wenn ich die Zeit hätte, würde ich versuchen, ein kleines, aber sauberes OO Konzept zu designen, mit welchem ich dynamisch auf Objektebene zur Laufzeit Eigenschaften und Verhaltensweisen definieren kann.Natürlich habe ich immer noch einen OO Entwurf, aber um meine Objekte zu erweitern. Ich presse aber keine Funktionalität in statische Klassen.
Jetzt klarer was ich meine?
-
Ja, mir ist jetzt klar was du meinst. Was du nennst sind aber eben alles Beispiele dafür, wie man OOP nicht betreiben sollte. Die Tatsache, dass man OOP falsch verstehen und falsch machen kann, bedeutet nicht, dass OOP prinzipiell fehlgeleitet ist und gemieden werden muss. Einem Boundingvolume eine "Zeichenfunktionalität" hinzuzufügen, verletzt beispielsweise eines der Grundprinzipien der OOP, nämlich das Single Responsibility Principle. Wenn deine Klassenhierarchie irgendwas "verbietet", dann passt deine momentane Abstraktion eben offenbar nichtmehr mit deinem momentanen Problem zusammen und muss dementsprechend überdacht werden. Wenn die notwendigen Änderungen sich durch den ganzen Code ziehen, dann ist das eben so. Was wäre die Alternative? Früher oder später musst du refactoren, ansonsten werden die Probleme sicher nicht weniger werden...
Wie gesagt, all diese Programmierstile ergänzen sich wunderbar. OOP ist nur ein (durchaus wichtiges) Werkzeug von vielen. Und in der OOP geht es ganz sicher nicht darum, wer mehr Design Pattern in sein Projekt zu stopfen vermag. Um das Beispiel mit dem Bounding Volume nochmal aufzugreifen: Ein Bounding Volume sollte z.B. in der Regel vermutlich überhaupt einfach nur ein normaler Werttyp sein, fernab jeglicher OOP. Das heißt nicht, dass das komplette System auf einer höheren Ebene nicht nach OOP Prinzipien designed werden darf. Und man kann OOP wunderbar verwenden, um ein System zu bauen, welches data driven ist...
-
dot schrieb:
Ja, mir ist jetzt klar was du meinst. Was du nennst sind aber eben alles Beispiele wie man OOP nicht betreiben sollte. Die Tatsache, dass man OOP falsch verstehen und falsch machen kann, bedeutet nicht, dass OOP prinzipiell fehlgeleitet ist und gemieden werden muss. Einem Boundingvolume eine "Zeichenfunktionalität" hinzuzufügen, verletzt beispielsweise eines der Grundprinzipien der OOP, nämlich das Single Responsibility Principle. Wenn deine Klassenhierarchie irgendwas "verbietet", dann passt deine momentane Abstraktion eben offenbar nichtmehr mit deinem momentanen Problem zusammen und muss dementsprechend überdacht werden. Wenn die notwendige Änderung sich durch den ganzen Code zieht, dann ist das eben so. Früher oder später musst du refactoren, ansonsten werden die Probleme sicher nicht weniger werden...
Wie gesagt, all diese Programmierstile ergänzen sich wunderbar. OOP ist nur ein (durchaus wichtiges) Werkzeug von vielen.
Korrekt.
Letztendlich braucht's Erfahrung, um eben jene Single Responsibilities zu erkennen, zu erkennen ob der Entwurf generell Sinn macht oder man sich eines anderen Ansatzes bedienen muss.Deswegen macht es IMO keinen Sinn, komplexe Designs zu entwerfen, ohne dabei konkrete Erfahrungswerte im Hinterkopf zu haben. Eben jene Erfahrungswerte sammeln wir zur Zeit. Und wir haben schon viel refactored. Naja, man wächst an seinen Fehlern.
-
Kleine Zwischenfrage, die mir beim Mitlesen so eingefallen ist: Auch wenn es natürlich vom ganzen Rest abhängt, aber würdet ihr es eventuell für sinnvoll halten, den DoD beim fahrbaren Bauhof so zu lösen, dass man von Anfang an gar nicht zwischen Gebäude und Fahrzeug unterscheidet, sondern Fähigkeiten wie Fahren, Ballern usw. als decorator an seine Objekte hängt, die dann commands verarbeiten können oder eben nicht? Welche gerade dranhängen könnte man z.B. über states regeln.
-
Ohne weitere Infos, würde ich den fahrenden Bauhof vermutlich als zwei verschiedene Einheiten modellieren, die ineinander umwandelbar sind.
-
Wie meinste das? Bei sowas fällt mir gerade nix ein, wie das mit dem Umwandeln sinnvoll ginge.
class Gebaeude{}; class Fahrzeug{}; class StehenderBauhof : public Gebaeude {}; class FahrenderBauhof : public Fahrzeug {}; void Spiel() { vector<Gebaeude*> rumsteher; vector<Fahrzeug*> herumfahrer; }
Hattest du sowas im Kopf?
class Einheit{}; class Fahrzeug{}; class BauhofState{}; class BauhofStandingState : public BauhofState{}; class BauhofDrivingState : public BauhofState{}; class Bauhof : public Einheit { BauhofState* state_; };
-
dot schrieb:
xaoC schrieb:
Man ist sich nicht über Verantwortlichkeiten im Klaren, "Kommunikation" zwischen Objekten wird letztendlich über Delegates o.ä. Prinzipien abgefackelt und da du i.d.R. später bestimmten Klassen mehr Funktionalitäten einräumst als jemals geplant waren, endet das Ganze bei uns momentan in einer recht monolitischen Sprite Klasse und einem Rahmensystem (Screens etc.), welche untereinander per Delegates Kommunizieren.
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
das klingt fuer mich wie ein missbrauch von 'refactoring'.
refactoring sollte ein design unterstuetzen und verbessern (im rahmen des designs).wenn ein design kaputt ist, braucht man ein redesign. dabei sollte man sich die grundlegene konzepte anchauen und nicht nur die architektur bestehender konzepte und sie alternieren.
wenn man es mal auf was anderes uebertraegt z.b. ein haus, wenn das design "kaputt" ist wie du sagst, hilft renovieren/refactoring nichts, das design bleibt (z.B. member in andere klassen schieben oder klassen unterteilen/mergen. natuerlich spreche ich fuer 'meistens' und nicht absolut fuer 'das ist in 100% der faelle so').
wenn man den designentwurf behaelt, und es einfach nochmal baut und versucht darauf zu achten nicht dieselben problemstellen herzustellen, ist das design dennoch kaputt, es wird nur verdeckt. eventuell laeuft es gut, eventuell laeuft man aber auch wieder in design spezifische probleme.ich stimme dir zu, DDS ist keine substitution fuer OOP, es sind verschiedene dinge. das eine ist die art die architektur zu designen, das andere ist das mittel wie man das design verwirklicht/programmiert. gerade deswegen sollte man sich von der objekt orientierten idee nicht das design vorgeben lassen, z.b. dass daten und funktionalitaet zusammenhaengen muss, denn dem ist nicht so und oft eine quelle von fehldesign wenn man sich auf idealisiertes OOP verlaesst.
natuerlich ist idealisiertes DDS (wie ich schon schrieb im ersten beitrag) eine naive sache und ebenfalls nicht problemfrei, man kann zwar mit wirklich idealem DDS alles erstellen, aber meistens bekommt man dann starke performance probleme da eben daten zur laufzeit evaluiert werden und nicht an funktionalitaet gebunden sind.ich denke OOP und DDS zusammen kann sehr gut ineinander greifen, aber genausogut kann man DDS und strukturiertes c schreiben. ich wollte im ersten post ausdruecken, dass DDS prinzipien sich heutzutage durchsetzen, als gegengewicht zu dem idealisierten OOP (mit impliziertem design), nicht dass es OOP ersetzt, sorry falls das falsch rueberkam.
ich habe das hier schon paar mal gepostet, aber wenn man ein wenig programmieren kann, ist das sicherlich mal unterhaltsam (und lehrreich) den vortrag zu lesen:
http://macton.smugmug.com/gallery/8936708_T6zQX#!i=593426709&k=ZX4pZthe right tool, for the right job.
-
Dobi schrieb:
Wie meinste das? Bei sowas fällt mir gerade nix ein, wie das mit dem Umwandeln sinnvoll ginge.
class Gebaeude{}; class Fahrzeug{}; class StehenderBauhof : public Gebaeude {}; class FahrenderBauhof : public Fahrzeug {}; void Spiel() { vector<Gebaeude*> rumsteher; vector<Fahrzeug*> herumfahrer; }
Hattest du sowas im Kopf?
class Einheit{}; class Fahrzeug{}; class BauhofState{}; class BauhofStandingState : public BauhofState{}; class BauhofDrivingState : public BauhofState{}; class Bauhof : public Einheit { BauhofState* state_; };
ich wueste nicht wieso man zwei klassen braucht, ob ein bauhof sich bewegt oder steht, ist eine eigenschaft, keine funktionalitaet.
pEntity->MaxSpeed(...)
fertig.
du willst ja am ende nicht zig mal durch objekte iterieren und pro eigenschaft einen container mit objekt referencen haben, sondern einmal ein
for_each(...)
pObject->Update();
aufrufen.ansonsten muestest du ja, wenn ein bauhof waffen bekommt und sich verteidigen kann, auch ein objekt machen in das du umwandelst, bzw hast du dann 4 objekte
static-nodefense
static-defense
moving-nodefense
moving-defenseoder wie entscheidet man ob eine eigenschaft ein neues objekt braucht?
wenn ein bauhof ein wirklich besonderes objekt ist, weil es z.b. andere objekte erstellen kann, dann wuerde es meiner meinung nach eine spezialisierung legitimieren, weil es zwar nicht unmoeglich ist jedem objekt eine liste mit objekten zu geben (als property) die es erstellen kann, aber in n:1 faellen waere das vermutlich unsinnig. (ein fall wo DDS zwar funktioniert, aber so over engineered ist wie ein idealisiertes OOP-design).
vielleicht erstellt man im laufe des spieles eine werft und eine space station, die beide einheiten erstellen koennen, die wuerde ich vom selben "bauhof" objekt instanzieren, aber dann wieder keine neue klasse, da sie zwar andere objekte erstellen (was eher eine eigenschaft ist), aber die selbe funktionalitaet haben.