Beispiel für datenorientiertes Design

8

Ich kann anscheinend keine gute Erklärung für das datenorientierte Design eines generischen Zombiespiels finden (es ist nur ein Beispiel, ein ziemlich häufiges Beispiel).

Könnten Sie ein Beispiel für das datenorientierte Design zum Erstellen einer generischen Zombie-Klasse geben? Ist das folgende gut?

Zombielistenklasse:

class ZombieList {
    GLuint vbo; // generic zombie vertex model
    std::vector<color>;    // object default color
    std::vector<texture>;  // objects textures
    std::vector<vector3D>; // objects positions
public:
    unsigned int create(); // return object id
    void move(unsigned int objId, vector3D offset);
    void rotate(unsigned int objId, float angle);
    void setColor(unsigned int objId, color c);
    void setPosition(unsigned int objId, color c);
    void setTexture(unsigned int, unsigned int);
    ...
    void update(Player*); // move towards player, attack if near
}

Beispiel:

Player p;

Zombielist zl;
unsigned int first = zl.create();
zl.setPosition(first, vector3D(50, 50));
zl.setTexture(first, texture("zombie1.png"));
...

while (running) { // main loop
    ...
    zl.update(&p);
    zl.draw(); // draw every zombie
}

Oder würde die Schaffung eines generischen Welt Container, der jede Aktion von enthält bite(zombieId, playerId)bis moveTo(playerId, vector)zu createPlayer()bis shoot(playerId, vector)zu face(radians)/face(vector); und enthält:

std::vector<zombie>
std::vector<player>
...
std::vector<mapchunk>
...
std::vector<vbobufferid> player_run_animation;
...

ein gutes Beispiel sein?

Was ist der richtige Weg, um ein Spiel mit DOD zu organisieren?

Schuh
quelle

Antworten:

11

Es gibt kein "Spiel mit DOD". Erstens ist dieses Modewort etwas unscharf, da jedes System datenorientiert ausgelegt ist. Jedes Programm arbeitet mit einem Datensatz und nimmt bestimmte Transformationen vor. Dies ist nicht möglich, ohne das Design an den Daten auszurichten. Es schließt sich also bei "normalem" Design nicht gegenseitig aus, sondern fügt Einschränkungen im Speicherlayout und in der Art und Weise hinzu, wie auf Speicher zugegriffen wird.

Die Idee hinter DOD besteht darin, Daten, die zu einer Funktionalität gehören, in einem kontinuierlichen Speicherblock näher beieinander zu packen und zu gruppieren, um weniger Cache-Fehler zu vermeiden, virtuelle Funktionen und vtables zu entfernen, die Parallelisierung zu vereinfachen, keine (oder nur minimale) zufälligen Speicherzugriffe und Schreiben von Code für hochoptimierte Prozessoren wie die SPUs der Zelle in der PS3 mit ihren begrenzten Speicherressourcen, Optimieren des Speicherzugriffs und der DMAs zum und vom Hauptspeicher.

Dies bedeutet nicht einfach, alles von "Array-of-Structures" (AoS) in "Structure of Arrays" (SoA) zu ändern, wie in den Beispielen hier gezeigt. Es kann auch bedeuten, beide zu mischen und Daten, die zu einer Funktionalität gehören, eng miteinander zu verschachteln und zu verpacken, wie zum Beispiel "Position" und "Geschwindigkeit", um ein Springen in den Speicher für die Integration der Position zu vermeiden.

Reine DOD-Systeme sind jedoch sehr schwer zu implementieren, da jeder Zeigerzugriff eine Verletzung dieses reinen Konzepts darstellt, da Sie nicht mehr auf einen kontinuierlichen Speicherblock zugreifen, sondern zufällige Speicherzugriffe durch Dereferenzieren eines Zeigers durchführen. Dies ist besonders wichtig, um Code für die SPU zu schreiben, wenn beispielsweise ein Partikelsystem von der CPU auf die SPU verschoben wird. In der normalen täglichen Spieleentwicklung ist dies jedoch nicht wichtig. Dies ist eine Möglichkeit, die Unterfunktionalität zu optimieren und keine Spiele damit zu schreiben (wie im Artikel von Noels erläutert).

Mike Acton von Insomniac Games hat viel interessantes Material zu diesem Thema. Einige seiner Artikel finden Sie hier sowie Noels Artikel , die beide sehr zu empfehlen sind.

Maik Semder
quelle
Eine Sache möchte ich zu dieser Antwort hinzufügen: Bei DOD geht es nicht nur um SoA-Systeme. Während SoA-Strukturen für DOD am besten funktionieren, passen sie nicht immer zum tatsächlichen DOD-Konzept. Das Konzept hinter DOD ist einfach die Idee, dass Sie den Code um die Daten herum entwerfen, nicht umgekehrt, was die übliche Methode ist.
Gurgadurgen
0

Ich habe auch nach einem guten Beispiel dafür gesucht, aber mit begrenzten Ressourcen im Internet und niemandem, der mir sagt, wie man es richtig macht, habe ich es mit der folgenden Implementierung gemacht. (Es ist vielleicht nicht das Beste, aber es folgt der Grundidee)

Object
   //Basic data
   Vector3* Position;
   Vector3* Rotation;
   Vector3* Scale;



Car : Object
    float* acceleration;
    Object* GetObjectData();
    //invoke the updateCars, to update all cars.
    void    UpdateCar() { UpdateCars(Postion, Rotation, Scale);

    //Update all your objects in a big loop.
    void    UpdateCars(vector3* Position, vector3* Rotation, Vector3* scale);

Die Implementierung sieht also mehr oder weniger so aus: Sie haben eine Basisobjektklasse, die alle gängigen Daten enthält. Wenn Ihre Fahrzeugklasse erstellt wird, geben Sie an, welche Datenmenge Sie zusammenfassen möchten, und verfügen daher über genügend Speicher für alle Objekte.

Von dort aus können Sie natürlich Kennungen hinzufügen oder alles, was für Ihre Implementierung erforderlich ist. Aber ich habe es mit einem einfacheren Spiel versucht, und es hat ziemlich gut funktioniert.

Es ist auch nicht so weit von Ihrem Design entfernt, und ehrlich gesagt kenne ich keinen effizienteren Weg, dies zu tun.

Tordin
quelle
Einige DOD-Probleme: 1. Sicher die Skalierung verlieren. Berechnungen in Bezug auf Position und Rotation werden von der Skalierung praktisch immer nicht beeinflusst, sodass sie so gut wie nie verwendet werden und nur Cache-Speicherplatz beanspruchen. 2. Ich würde auch die Rotation verlieren und sie durch Geschwindigkeit ersetzen. Ein Auto soll sich gerade bewegen, aber seine Geschwindigkeit bestimmt letztendlich seine Richtung. Der Fahrer drückt auf das Gasblatt, aber die Physik bewegt das Auto. 3. Erben Sie nicht von Klassen für die Daten, wenn Sie nicht vorhaben, sie in fast allen Berechnungen zusammen zu verwenden. 4. Auch in OOP aktualisieren sich Autos nicht gegenseitig. Verwenden Sie freie Funktionen.
Gurgadurgen
Dies ist eher ein Beispiel, kein ultimativer Leitfaden. Sie müssen natürlich die beste Lösung für Ihre eigene Implementierung auswählen. (wie es heißt)
Tordin
Ihr Beispiel ist ein Beispiel für eine Standard-OOP-Abstraktion und nutzt DoD-Strategien kaum oder gar nicht. Bei DoD geht es um die Daten, nicht um das Modell. Die Tatsache, dass Sie sogar ein "Auto" -Objekt haben, ist ein totes Werbegeschenk dafür, dass dies kein gutes Beispiel für DoD ist. Ein Auto ist ziemlich spezifisch, und DoD tendiert eher zur existenzbasierten Objektzusammensetzung und zum Polymorphismus als zur Vererbung. So können Sie beispielsweise ein Objekt haben, das Informationen enthält, die für eine bestimmte Transformation erforderlich sind, und ein Array dieser Objekte anstelle eines Objekts mit Informationen für mehrere Transformationen erstellen.
Gurgadurgen