Kompilierzeitprobleme mit modularer Programmierung
-
Nur jemand oder etwas von Aussen, kann den Zustand der Spielfigur verändern.
Es ist also nicht die Aufgabe der Spielfigur, ihren Zustand zu ändern.Ja, aber die Figur weiss, wie man geht. Man muss der Figur also nur noch sagen: "geh rauf!". Und dann erledigt die Figur den Rest, was hald dazugehört.
(Bin mir jetzt nicht ganz sicher, ob das in etwa die Frage war.. )Zeichnen musst du die Dinger sowieso und ob du nun vorher noch 3 Zahlen überschreibst oder nicht, fällt meistens nicht so ins Gewicht.
Genau. Also bei meinem Partikelsystem läuft das so, dass ich ein Sprite habe, was den Partikel simuliert und dann habe ich eine Liste von Partikeln, die dann Position, Rotation usw. beinhalten. Und dann wird das Sprite hald jedes mal verschoben und rotiert und gerendert. Das ist reine Berechnung und im Release Stadium sind auch 10000 Partikel nicht das Problem der verschiebung des Sprites, sondern eher das rendern selbst.
segfault in function world_domination at location 0x0badc0de
Hmm. Hatte ich auch. In diesem Falle liegt das nicht an uns. Da stimmt was mit dem Code der Realität nicht.

LoL, alles Schweizer hier? Ist ja mal wieder interessant. So Diskussionen entstehen nur mit Schweizern!
Hehe. Ich glaube wir sind alle aktiven Schweizer hier.
Ich schick euch beiden mal eine Runde msn-Adressen. Wir können ja mal wenig quatschen. 
-
drakon schrieb:
Ja, aber die Figur weiss, wie man geht. Man muss der Figur also nur noch sagen: "geh rauf!". Und dann erledigt die Figur den Rest, was hald dazugehört.
(Bin mir jetzt nicht ganz sicher, ob das in etwa die Frage war.. )Ich will mal sehen, wie du einer Figur auf dem Brett sagst, wohin sie gehen soll. Da kannst du lange "geh rauf!" sagen. Die Figur weisst nicht, wie sie sich bewegen soll. Die Regeln schreiben vor, wie sie sich zu bewegen hat. Jemand kennt die Regeln und wendet diese auf die Figur an.
Der Figur kann so auch scheiss egal sein, wie die Regeln aussehen. Man kann also Änderungen an den Regeln vornehmen, ohne etwas an der Figur zu ändern.Grüssli
-
Ja, das ist schon klar.
Aber wenn ich die Figur jetzt bewegen will, dann muss ich das Objekt ja verändern. Ob das jetzt mit einer Funktion "gehrauf" oder mit einem Vector gemacht wird, spielt ja keine Rolle, aber irgendwie musst du das dem Objekt ja mitteilen.
-
drakon schrieb:
Ja, das ist schon klar.
Aber wenn ich die Figur jetzt bewegen will, dann muss ich das Objekt ja verändern. Ob das jetzt mit einer Funktion "gehrauf" oder mit einem Vector gemacht wird, spielt ja keine Rolle, aber irgendwie musst du das dem Objekt ja mitteilen.Du veränderst das Objekt, in dem du dem Objekt am Ende der Bewegung mitteilst, wo es ist. Wie ich schon mal früher als Beispiel hingeschrieben habe, zum Beispiel über eine
set_locationFunktion. Es ist eigentlich wie mit dem Sprite, du veränderst nur die Koordinaten/das Feld, wo die Figur steht. Somit hat die Figur kein "geh irgendwohin", sondern eine art "setze neuer Standort". Und die Figur wird damit nichts anfangen, sondern diesen neuen Ort einfach nur intern abspeichern. Und wenn das nächste Mal eine Bewegung erfolgt, weiss die Regel, durch abrufen der Funktionget_location, wo die Figur aktuell steht.Grüssli
-
Noch eine Sache wegen der Klassenaufteilung.
Ich weis nicht, ob das eventuell schon einmal so deutlich gesagt wurde, zwischenzeitlich wurde es durch die Menge an quotes extrem unübersichtlich.
Als Designziel einer Klasse sollte immer gelten: Eine Klasse, eine Aufgabe.
Keine großen, monolithischen Gebilde. Einfach kleine aber feine Klassen mit einem einfach zu definierendem Interface. Und so funktionierts: du schaust dir deine Klasse an, und versuchst ihre Aufgabe zu beschreiben. Sobald du in der Beschreibung ein "und" benutzt, tut die Klasse zuviel. so einfach ist das.Beispiel Spielbrett:
gut:
Das Spielbrett verwaltet die Schachfiguren
schlecht:
Das Spielbrett verwaltet die Schachfiguren und erstellt den SpielerDiese Art der Programmierung senkt automatisch die Compilezeit, da die Abhängigkeiten drastisch reduziert werden. Die gute Klasse in unserem Beispiel hat nur die Abhängigkeit zu den Schachfiguren, die schlechte Klasse zudem noch die Abhängigkeit zum Spieler. Ein weiterer Positiver Nebeneffekt ist, dass man nur sehr wenig an den einzelnen Interfaces rumbasteln muss. Erweiterung ist bei dem Prinzip das Mittel der Wahl.
Das zweite Designziel sollte sein(das ist aber die hohe Kunst), dass man als Methode einer Klasse nur das definiert, was wirklich eine Methode sein muss.
Beispiel Kollisionsfunktion:Wenn du 2 Objekte A und B mit der Methode testKollision hast, dann kannst du schonmal folgendes feststellen:
A.testKollision(B); B.testKollision(A);irgendwie ist kein logischer Unterschied zwischen beiden Aufrufen, oder?
Dann gibt es sicherlich auch keinen logischen Unterschied dazu:testKollision(A,B);Und das ist ein sicheres Indiz dafür, dass testKollision keine Methode sein sollte.Das ist allerdings auch nicht das einzige Kriterium. Das wichtigere Kriterium ist: braucht die Methode nur private Daten des Objekts,die bereits nach außen zugänglich sind? Wenn ja, dann spricht wirklich nichts dagegen, dass die Methode zur Funktion wird. Wenn die Methode logisch als Funktion existieren kann, und dabei keine weiteren Daten der Klasse öffentlich gemacht werden müssen, dann sollte die Methode auch eine Funktion sein.
Was bringt das? Das Interface wird dadurch viel Stabiler. Man muss nur noch selten Änderungen an der Klasse vornehmen, meistens reicht es, wenn einfach eine weitere Funktion hinzugefügt wird. Das Interface der Klasse kann desweiteren generalisiert werden, sodass das Gesamtdesign stimmiger und einfacher zu überschauen ist.
-
Dravere schrieb:
*sich jetzt gerade an den Löffel in Matrix erinnert*

Nicht denken... Wissen. :p
Dravere schrieb:
[...] Es ist also nicht die Aufgabe der Spielfigur, ihren Zustand zu ändern.
Okay, das war wirklich ein gutes Beispiel. Jetzt wird es mir auch relativ klar.
Dravere schrieb:
Zeichnen musst du die Dinger sowieso und ob du nun vorher noch 3 Zahlen überschreibst oder nicht, fällt meistens nicht so ins Gewicht.
Okay. Animationen würde ich wahrscheinlich mit Frames lösen, d.h. wenn eine Animation ausgelöst wird, wird eine int-Variable auf 0 gesetzt. In jedem weiteren Frame wird sie inkrementiert und so wird je nach der vergangenen "Zeit" ein anderes Bild dargestellt. Da müsste man eventuell länger berechnen, wenn man keine synchronen Animationen will. Aber auch das (Division/Modulo) sollte kein Problem darstellen.
Dravere schrieb:
Nehmen wir am besten wieder das Spielbrett. Du kannst die Spielfigur nehmen und neben das Brett legen. Bzw. am Anfang nimmst du sie aus der Schachtel und setzt sie auf das Brett drauf. Es gibt auch die Möglichkeit, dass man eine Spielfigur überhaupt nicht nimmt.
Zu dem Schachspiel passt das Beispiel ja schön und gut, aber in meinem Spiel hat eine Spielfigur eigentlich nichts ausserhalb des Spiels verloren. Sorry, wenn ich so stur bin

Dravere schrieb:
Die Sache ist halt, wenn du die Spielfigur fest ins Spiel einbindest, dann verbarrikadierst du dir viele Möglichkeiten.
Das kann schon sein, aber momentan kann ich mir nicht vorstellen, welche Möglichkeiten? Naja, vielleicht ist hier auch wieder ein Zuerst-auf-die-Schnauze-Fallen-bevor-man-es-kapiert-Effekt angebracht

Dravere schrieb:
Nexus schrieb:
Gibt es überhaupt ein Wort für Variablen, deren Speicherbereich statisch angefordert wurde (unabhängig ob im Heap oder Stack)?
Ehm ... Membervariable?
Membervariablen würde ich eher die innerhalb einer Klasse deklarierten Variablen nennen...
Dravere schrieb:
Hmmm, ich weiss jetzt nicht, ob wir uns aneinander vorbeireden, aber um es mal zu verdeutlichen, was ich meinte: [...]
Danke für das Beispiel. Ja, in etwa so dachte ich es mir (natürlich nicht mit Light und Heavy Binding). Aber das Light Binding wird ja benötigt, wenn man einen ganzen Spieler an eine Klasse übergeben will (sodass die Klasse nun machen kann, was sie will). Daran hab ich gar nie gedacht, aber genau so wenig an Heavy Binding, da ich ja immer von festen Besitzen ausgegangen bin...
Dravere schrieb:
Freak? Nerd? Geek? Oder sonst was? Vielleicht ... dafür herrsche ich über die WELT!!! MUAHAHAHAHAHA ... *auf den Bildschirm schaut*
segfault in function world_domination at location 0x0badc0de
Ehm ... soon
Irgendwie haben wir ja alle etwas gemeinsam... Der Versuch einer Masterklasse ist schon das erste Anzeichen bei mir

BTW: Zentrum der Schweiz ist gut, ich wohne im Kanton Luzern (in Kriens)

Dravere schrieb:
Somit hat die Figur kein "geh irgendwohin", sondern eine art "setze neuer Standort". Und die Figur wird damit nichts anfangen, sondern diesen neuen Ort einfach nur intern abspeichern. Und wenn das nächste Mal eine Bewegung erfolgt, weiss die Regel, durch abrufen der Funktion
get_location, wo die Figur aktuell steht.Streng gesehen sind dann auch Methoden der Figur wie z.B.
Move()oder ähnliches zur Vereinfachung verboten? D.h. gerade mal Getter und Setter? Oder gleich ein Struct verwenden?@ otze: Okay, dann verwende ich bei dir keine Quotes :p
Hey, vielen Dank für die ausführliche Beschreibung, jetzt wird mir einiges viel klarer in der OOP! Ich hatte bisher immer nur so eine schwammige Vorstellung - vom Konzept "eine Klasse - eine Aufgabe" hab ich auch schon gehört, aber ich weite meine Aufgaben meistens zu stark aus...
-
...
ausserdem möchte ich noch eine buchempfehlung aussprechen.
vielleicht ist das buch ein wenig zu massiv, aber es befasst sich sich
grad mit der thematik des physikalischen designs (compiletime coupling, ...).large scale c++ design (john lakos) http://www.amazon.de/Large-Scale-Software-Addison-Wesley-Professional-Computing/dp/0201633620/ref=sr_1_1?ie=UTF8&s=books-intl-de&qid=1219008234&sr=8-1
ich lese es gerade (bin grad irgendwo in der mitte).
es ist in einigen wenigen sachen outdated (bzw nicht state of the art), aber es vermittelt irrsinnig viel knowhow
-
Nexus schrieb:
Zu dem Schachspiel passt das Beispiel ja schön und gut, aber in meinem Spiel hat eine Spielfigur eigentlich nichts ausserhalb des Spiels verloren. Sorry, wenn ich so stur bin

Nexus schrieb:
Das kann schon sein, aber momentan kann ich mir nicht vorstellen, welche Möglichkeiten?
Ich habe bisher immer sehr allgemein probiert zu reden. Da ich dein Spiel nicht kenne, kann ich dir auch nicht direkt irgendwelche Möglichkeiten für dein Spiel aufzählen. Du bleibst einfach viel flexibler.
Aber ich denke es ist, wie du selbst sagt:
Nexus schrieb:
Naja, vielleicht ist hier auch wieder ein Zuerst-auf-die-Schnauze-Fallen-bevor-man-es-kapiert-Effekt angebracht

Nexus schrieb:
Dravere schrieb:
Nexus schrieb:
Gibt es überhaupt ein Wort für Variablen, deren Speicherbereich statisch angefordert wurde (unabhängig ob im Heap oder Stack)?
Ehm ... Membervariable?
Membervariablen würde ich eher die innerhalb einer Klasse deklarierten Variablen nennen...
Und auf welche Variablen trifft deine Beschreibung sonst zu?
Static Variablen kommen schliesslich weder auf den Heap noch Stack, sondern in den Global-Speicher.Nexus schrieb:
Aber das Light Binding wird ja benötigt, wenn man einen ganzen Spieler an eine Klasse übergeben will (sodass die Klasse nun machen kann, was sie will).
Genau, und so sollte es meiner Meinung nach mit dem Spieler geschehen

Nexus schrieb:
BTW: Zentrum der Schweiz ist gut, ich wohne im Kanton Luzern (in Kriens)

Habt ihr zufällig eine UBS-Filiale dort? Dann wickeln nämlich meine Eltern einen Teil ihrer Finanzen dort ab. Mein Vater ist im Kanton LU eingebürgert

Nexus schrieb:
Streng gesehen sind dann auch Methoden der Figur wie z.B.
Move()oder ähnliches zur Vereinfachung verboten?Haargenau! Die Vereinfachung findet dadurch statt, dass du genügend Klassen baust, also deine Aufgaben aufteilst.
Nexus schrieb:
D.h. gerade mal Getter und Setter?
Am besten wäre es wohl.
Nexus schrieb:
Oder gleich ein Struct verwenden?
Von einem Struct empfehle ich ab. Ein Struct hat alle Membervariablen normalerweise als public. Mit den Getter/Setter hast du im ersten Moment zwar auch nichts anderes und der Compiler wird dir alles dermassen optimieren, dass es auch nichts anderes sein wird, aber die Getter und Setter haben einen riesigen Vorteil beim Debuggen!
Du kannst genau nachprüfen, WANN eine Variable abgerufen oder verändert wird. Entweder durch das setzen eines Breakpoints oder durch das dazufügen von irgendwelchen Log-Möglichkeiten.@otze,
Super Beitrag! Das hat nochmals einiges auf den Punkt gebracht. Vor allem die Definition mit dem "und" finde ich extrem gut. Das kann man wirklich vollständig so anwenden.Grüssli
-
Nexus schrieb:
[Streng gesehen sind dann auch Methoden der Figur wie z.B.
Move()oder ähnliches zur Vereinfachung verboten? D.h. gerade mal Getter und Setter? Oder gleich ein Struct verwenden?ein sehr gutes beispiel für eine MVC architektur in einem spiel wäre der sourcecode von ut2004 (unreal script, sehr c/java ähnlich, leicht lesbar, gibt gute editoren die die objekthierarchie darstellen können [wotgreal])
da ist es in etwa so aufgebaut:
es gibt eine Scene (kann irgendeine form von strkutur sein, im einfachsten fall eine Liste, oder ein Grid), in dieser scene gibt es Aktoren.
Aktoren sind grob gesagt alle objekte die in einer Scene plaziert werden können (position + dimension im raum).jetzt mal einige leafnodes aufgezählt (alles aktoren):
spieler, waffen, fahrzeuge, factories (weapon, spawnpoints), pathnodes, triggers, sounds, kameras (views). (nicht aber controller, und diverse helfer klassen)die interaktion mit dem spieler ist so aufgebaut:
die basis klasse eines spielers ist der Pawn (der Pawn ist ein Aktor welcher eine grafische Darstellung referenziert (mesh, sprite, was auch immer) )dann gibt es das Controller interface klasse + die Ableitungen davon.
ein Pawn wird durch entweder einen PlayerController / BotController (frei erfunden: NetController, MakroController/ DemoController) modifiziert.Der PlayerController ist im prinzip nur ein Leichter wrapper der das Input modell aufs Controller Interface biegt, und die position und zustand des Pawns modifiziert.
Der BotController modifiziert den Pawn durch irgendwelche Ausgeklügelten AI berechnungen.
Der NetController (frei erfunden) würde den Pawn durch Server Daten bewegen.
ein MacroController / DemoController (frei erfunden) könnte zb eine aufgenommene demo mit den bewegungen eines spieles wiedergeben.
der Pawn sollte auch keine kollisionserkennungsfunkion beinhalten.
für sowas ist die Scene zuständig.
die kollisionserkennung sollte aber auf der ebene des controllers aufgerufen werden, da erst der controller die notwendigen subsysteme den richtigen kontext setzen kann.fürs rendern ist auch nicht die Scene zuständig, sondern der Aktuelle Viewport.
wäre zb so: (vorweg möchte ich mich für meinen hühnerkrallenähnlichen schreibstil entschuldigen :-), weiters behandelt es nicht das problem mit der compilezeit!)
class Scene : public Singleton<Scene> { public: ... struct CollisionStats //collision locations, safe position / orientation { ... }; ... CollisionStats TestCollisions(Actor const& actor) const; //test against world CollisionStats TestCollisions(Actor const& actor1, Actor const& actor2) const; // hittest two actors enum { MAX_ACTORS = 2 } Actor* actors[MAX_ACTORS]; void Think(double frameTimeMS) { foreach (actor in actors) actor->Think(frameTimeMS); } Camera& GetCamera() {... } }; class Actor { ... protected: Vector3d location; virtual ~Actor(); public: Vector3d get_Location() const; void set_Location(Vector3d location); virtual void Think(double frameTimeMS) = 0; }; class Pawn: public Actor { Mesh& m_Mesh; public: Pawn(Mesh& mesh) : m_Mesh(mesh) { } Mesh& get_Mesh() const; void Move(Vector3d diff) { location += diff; } virtual void Think(double frameTimeMS) { m_Mesh.Animate(frameTimeMS); ... } }; class Controller { public: virtual void Think(double frameTimeMS) = 0; ... protected: Pawn& m_Pawn; Controller(Pawn& pawn) : m_Pawn(pawn) { } virtual ~Controller(); }; class PlayerController: public Controller { PlayerController(PlayerController& other); public: PlayerController(Pawn& pawn) : Controller(pawn) { } virtual void Think(double frameTimeMS) { Vector3d diff; float MoveSpeed = PlayerSettings::MoveSpeed(); diff.x = InputSystem::IsKeyDown(VK_LEFT) * -1.0 * MoveSpeed * frameTimeMS; diff.x = InputSystem::IsKeyDown(VK_RIGHT) * MoveSpeed * frameTimeMS; diff.z = InputSystem::IsKeyDown(VK_UP) * -1.0 * MoveSpeed * frameTimeMS; diff.z = InputSystem::IsKeyDown(VK_UP) * MoveSpeed * frameTimeMS; Scene::CollisionStats stats = Scene::Instance().TestCollision(m_Pawn); if (stats.HasCollision()) { m_Pawn.set_Location(stats.safeLocation); m_Pawn.set_Orientation(stats.safeOrientation); } else m_Pawn.Move(diff); } }; class Camera { ... }; class Viewport { //frustrum definitions void Draw(Scene const& scene) { foreach (actor in scene.Actors()) { if (IsVisible(actor)) Graphics::DrawActor(actor); } } bool IsVisible(Actor const& actor) { //test actor AABB against Frustrum, cull not visible actors } }; int main() { ResourceManager& resMgr = ResourceManager::Instance(); Pawn player(resMgr.GetMesh("someMesh")); Camera camera(10,10,10, //position 10, 10, -10, //look at 0, 1, 0); //topside orientation Scene &scene = Scene::Instance(); scene.LoadGeometry("someMap"); scene.AddActor(player); scene.SetCamera(camera); PlayerController controller(player); ViewPort viewport(scene.GetCamera()); ... //game loop bool bGameRunning = true; double frameTimeMS = HighResTimer::GetTimeDiff(); do { controller.Think(frameTimeMS); scene.Think(frameTimeMS); viewport.Draw(scene); frameTimeMS = HighResTimer::GetTimeDiff(); } while(bGameRunning) }
-
@ inpimp
Danke für deinen ausführlichen Beitrag. Ich fürchte nur, momentan übersteigt das meine Erfahrungen. Ich muss auch zuerst mal ein grundlegendes Design gut hinkriegen, bevor ich alles auf MVC setze und komplett neu beginne...Dravere schrieb:
Und auf welche Variablen trifft deine Beschreibung sonst zu?
Static Variablen kommen schliesslich weder auf den Heap noch Stack, sondern in den Global-Speicher.Naja, ist nun auch egal, wir wissen ja, was wir meinen

Hab mich nur gefragt, obs dafür extra ein Wort gäbe, aber scheinbar nicht...Dravere schrieb:
Habt ihr zufällig eine UBS-Filiale dort? Dann wickeln nämlich meine Eltern einen Teil ihrer Finanzen dort ab. Mein Vater ist im Kanton LU eingebürgert

Ja, die UBS gibts ja fast überall... Kennst du Kriens (hast du überhaupt schon davon gehört)?

-
Nexus schrieb:
@ inpimp
Danke für deinen ausführlichen Beitrag. Ich fürchte nur, momentan übersteigt das meine Erfahrungen. Ich muss auch zuerst mal ein grundlegendes Design gut hinkriegen, bevor ich alles auf MVC setze und komplett neu beginne...also von dem thread her finde ich das du das ziemlich schnell drauf haben solltest

einfach die fühler nach neuen informationsquellen ausstrecken und einsaugen
und wg dem physikalischem design solltest du dir wirklich "large scale c++ development" durchlesen / anlesen.
-
Okay, ich werd mal schauen, was sich machen lässt
