Ejemplo de diseño orientado a datos

8

Parece que no puedo encontrar una buena explicación del diseño orientado a datos para un juego de zombis genérico (es solo un ejemplo, un ejemplo bastante común).

¿Podría dar un ejemplo del diseño orientado a datos sobre la creación de una clase genérica de zombies? ¿Es bueno lo siguiente?

Lista de zombis clase:

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
}

Ejemplo:

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
}

O crearía un contenedor World genérico que contenga todas las acciones de bite(zombieId, playerId)to moveTo(playerId, vector)a createPlayer()to shoot(playerId, vector)to face(radians)/face(vector); y contiene:

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

ser un buen ejemplo?

¿Cuál es la forma correcta de organizar un juego con DOD?

Zapato
fuente

Respuestas:

11

No existe un "juego con DOD". Firstable, esa palabra de moda es un poco confusa, porque cada sistema está diseñado orientado a datos. Cada programa trabaja en un conjunto de datos y realiza ciertas transformaciones. Imposible hacerlo sin orientar el diseño hacia los datos. Por lo tanto, no es mutuamente excluyente con el diseño "normal", pero agrega restricciones en el diseño de la memoria y la forma en que se accede a la memoria respectivamente.

La idea detrás de DOD es empacar y agrupar datos que pertenecen a una funcionalidad más cerca en un bloque de memoria continuo, para tener menos errores de caché, deshacerse de funciones virtuales y vtables, paralelización más fácil, sin accesos aleatorios de memoria (o mínimos) y escribir código para procesadores altamente optimizados como las SPU de Cell en la PS3 con sus recursos de memoria limitados, optimizando el acceso a la memoria y los DMA desde y hacia su memoria principal.

Esto no significa simplemente cambiar todo de "Array-of-Structures" (AoS) a "Structure of Arrays" (SoA) como se muestra en los ejemplos aquí. También puede significar mezclar ambos e intercalar y empaquetar datos que pertenecen a una funcionalidad estrechamente juntos, como por ejemplo "posición" y "velocidad" para evitar saltar en la memoria para la integración de la posición.

Sin embargo, los sistemas DOD puros son muy difíciles de implementar, ya que cada acceso de puntero es una violación de ese concepto puro , ya que ya no accede a un bloque de memoria continuo, sino que realiza accesos aleatorios de memoria al desreferenciar un puntero. Esto es particularmente importante para escribir código para la SPU cuando se mueve, por ejemplo, un sistema de partículas desde la CPU a la SPU, pero en el desarrollo normal del juego diario no es importante. Es una forma de optimizar la subfuncionalidad, no de escribir juegos con ella (todavía, como explica el artículo de Noels).

Mike Acton de Insomniac Games tiene mucho material relacionado con este tema, puedes encontrar algunas de sus cosas aquí , así como los artículos de Noel , ambos muy recomendados.

Maik Semder
fuente
Una cosa que me gustaría agregar a esta respuesta: DOD no es TODO sobre los sistemas SoA. Si bien las estructuras SoA tienden a funcionar mejor para DOD, no siempre se ajustan al concepto real de DOD. El concepto detrás de DOD es simplemente la idea de que está diseñando el código alrededor de los datos, no al revés, que es el método habitual.
Gurgadurgen
0

También he estado buscando un buen ejemplo de esto, pero con recursos limitados en la red y nadie que me diga cómo hacerlo correctamente, lo hice con la siguiente implementación. (Puede que no sea el mejor, pero sigue la idea básica)

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);

Entonces, la implementación es más o menos así: tiene una clase de objeto base, que contiene todos los datos comunes. Cuando se construye su clase de automóvil, usted especifica qué cantidad de datos desea agrupar y, por lo tanto, tiene suficiente memoria para todos los objetos.

desde allí puede agregar identificadores o lo que sea necesario para su implementación. pero intenté esto en un juego más simple, y funcionó bastante bien.

Tampoco está tan lejos de su diseño, y, francamente, no conozco una manera más eficiente de hacer esto.

Tordin
fuente
Algunos problemas de DOD: 1. Perder escala, seguro. Los cálculos con respecto a la posición y la rotación prácticamente no se ven afectados por la escala, por lo que prácticamente nunca se usarán y solo ocuparán espacio en la caché. 2. También perdería rotación y la reemplazaría con velocidad. Un automóvil está destinado a moverse en línea recta, pero su velocidad determinará en última instancia su dirección. El conductor presiona el pétalo de gas, pero la física mueve el automóvil. 3. No herede de las clases los datos si no planea usarlos en casi todos los cálculos, juntos. 4. Incluso en OOP, los autos no se actualizan entre sí. Utiliza funciones gratuitas.
Gurgadurgen
Este es más un ejemplo, no una guía definitiva. Por supuesto, debe elegir el mejor ajuste para su propia implementación. (como se dice)
Tordin
Su ejemplo es un ejemplo de abstracción OOP estándar, y aprovecha poco o nada las estrategias de DoD. DoD es sobre los datos, no sobre el modelo. El hecho de que incluso tenga un objeto de "automóvil" es un claro indicio de que este no es un gran ejemplo de DoD. Un automóvil es bastante específico, y DoD tiende a hacer composición de objetos basada en la existencia y polimorfismo, en lugar de herencia. Entonces, por ejemplo, es posible que tenga un objeto que contenga la información que se requiere para una transformación específica, y cree una matriz de esos objetos, en lugar de un objeto con información para múltiples transformaciones.
Gurgadurgen