Painter-Class and Level-Structures

Started by
5 comments, last by Juliean 7 years, 2 months ago

Hello dear forum : )

Using C++, but possible design patterns are probably not limited to the language, therefore no "C++"-tag,

I have a level-class with a vector of vectors (containing entity-game-objects) as structure for my level because the level is predictable; grid-based.

Each game-object owns a graphics-component, containing all required information by my rendered API.

Of course my level-class shall not be in charge to draw entities, therefore I would like to implement a painter-class.

However I'm not sure what would be the good-practice-way to tell my painter-class what to draw.

Additionally, there are also GUI-widgets that needs to be rendered. As they have no grid-based appearance, they just lie within a vector of widgets and show their coordinates.

Simple: Just pass a reference of the 2d-vector to the paint-class. This the easier route and should probably preferred over more complex code.

While this is sufficient for my needs, should a usual painter-class even have the necessity to know the level-format?

Alternative: Letting the painter-class have a vector of pointers to each graphics-component within an entity.

This removes one dependency of knowing the level-structure and leaves the dependency of knowing how the graphics-component works.

Removing the component-dependency: Implementing a wrapper-class containing texture-reference, coordinates and a link to the original graphics-component.

Of course, I would also require a second vector for GUI-widgets.

I thought about using a queue over a vector. But I'm not sure if the painter-class should dequeue/remove an item or if the level-(un)loader-class should trigger a flag to do this.

So thought I will save quite a few iterations if the painter-class does not need to remove elements once done.

Are there better ways to do this? How would you implement a painter-class?

Thanks for taking your time to read through my thread : )!

Advertisement
The renderer/painter and level should definitely not know anything about each other.

The "easy" approach here - since you mentioned a component already - is to add the component to a scene graph. This might even be "implicit" in that your component factory might itself be part of the scene graph.

The renderer then walks said graph, using whatever it needs to efficiently cull objects (e.g., a bounding volume hierarchy, which is dead simple to write and quite effective).

class SceneManager {
  vector<Node*> _nodes;

public:
  void registerComponents(Registry& registry) {
    registry.addComponent("Graphics", [this]{
      audo component = make_unique<GraphicsComponent>();
      _nodes.push_back(component.get());
      return component;
    });
  }

  void draw(Camera const& camera) {
    for (Node* node : _nodes) {
      if (camera.frustrumContains(node))
        render(node);
    }
  }
};

class GraphicsComponent : public Component, public Node {
  TransformComponent* _transform = nullptr;
  AABB _bounds;

public:
  // virtual functions from Component used for core entity functionality
  void initialize(Entity& entity) override {
     _transform = entity.getComponent<TransformComponent>();
  }

  // virtual functions from Node that graphics needs to do rendering
  Vector3d getPosition() const override { return _transform->getPosition(); }
  AABB getBoundingBox() const override { return _bounds; }
};

All incomplete and a little icky, but that's the general idea. The renderer works with its own data types and the components just serve to glue the systems together and ferry data around.

(p.s. anytime I see "vector of vectors" I get really uneasy; there's almost never a case when that's an appropriate data structure.)

Sean Middleditch – Game Systems Engineer – Join my team!

Wow! Thanks : ) I will give it a try!

About vector of vectors:
I thought it might appropiate to simulate a level-grid.
Especially since collision of the mouse to grid-entities can be computed by coordinates. Which enables a vector[y][x]-access structure.
Would you recommend not to do this?
I thought it is quite convenient, maybe I should roll with a customised data structure or look for something prettier?
... 2d vector ... Especially since collision of the mouse to grid-entities can be computed by coordinates. Which enables a vector[y][x]-access structure. Would you recommend not to do this?

A 2D array is generally not best for this type of system.

In the rectangular array, the long single dimensional array is the same as an array of arrays is laid out sequentially. One row, then another row, then another, each accessed individually. You can do this yourself with an array that is size width*height, and then access it either in row-major or column-major by accessing array[x+width*y] or array[y+height*x]. Once you decide the pattern you are storing your data you need to stick with it everywhere. In a classic 2D array that is exactly how array[y][x] worked out mathematically. The compiler would replace [y][x] with [x+width*y] behind your back.

In the container full of containers there is the potential for jagged arrays. That means the first item may be length 10, the second item may be length 45, the third may be length 3, the fourth may be length zero, etc. That takes more effort to work with. Generally you should only use that if you intend to have a jagged array.

Personally I would pass around a data structure that handles a rectangular array.

Perhaps I might start with this:

{

uint_32 width; // number of pixels wide

uint_32 height; // number of pixels tall

uint_32 * data; // pixel data in RGBA format, row major starting in the upper left corner

}

Be aware that graphics work is generally best done on the GPU when possible. It is certainly possible to composite all the data on the CPU and work with all the items in main memory. But if the only thing you are doing is displaying the result it is generally best to use the graphics drivers that will do the work on the video cards using hardware-accelerated methods.

Everything frob said.

A slightly more advanced concern with arrays-of-arrays is that the individual rows won't be allocated in memory together. One row might get allocated around address 0x1000 while the next might be around address 0x5500 and the third is near 0x1100. This makes copying the tile grid around much more expensive/complicated, makes accessing the tile data a little slower, etc.

The easiest setup for a tile grid is what frob described above, though I'd personally still use a vector or unique_ptr<[]> instead of a raw pointer. The fastest approach is to use a fixed "chunk" of tiles (say a 64x64 chunk) and then arrange those together in an octree or spatial hash. That is essential to supporting Really Large Worlds, and also just tends to be a faster way to structure tile data internally (e.g., that's closer to how your GPU stores textures in its memory, using zigzag layout for better cache usage).

That's all fairly advanced stuff to understand and not required for 99% of games, but it's the kind of stuff you should be aware of for when you start working on bigger projects. :)

Sean Middleditch – Game Systems Engineer – Join my team!

Thanks a lot! I will remove my vector of vectors. Just thought [y][x] would be a bit more "human-friendly", especially since I was not really worried about performance.

I was curious on what data structures you would use within the stage/scene/painter-class?

Since I have an edit mode that requires flexible editing (adding, removing and switching layers). At the moment, I did not include x- and y-coordinates in my game-objects, because I know the tile-grid-size and simply load them in order.

I considered these possible solutions: Just a normal vector that allocates max_width*max_height amount of empty slots that get populated - pretty much the same structure as in the level-wrapper-class.

Or start assigning x- and y-coordinates to every object.

The thing is, the renderer needs to know coordinates for GUI-widgets of course. But since I plan on putting them into another instance of a vector, it should be no problem to treat them differently.

What is common practice? Or is there even such? It probably depends quite a lot.

Every layer would have one vector or I consider something like a priority queue.

Thanks a lot! I will remove my vector of vectors. Just thought [y][x] would be a bit more "human-friendly", especially since I was not really worried about performance.

Remember that you can always implement that functionality yourself:


struct TilemapData
{ 
  uint_32 width; // number of pixels wide
  uint_32 height; // number of pixels tall

  uint_32 GetTile(uint_32 x, uint_32 y) const
  {
      return data[y * width + x];
  }

  uint_32 * data; // pixel data in RGBA format, row major starting in the upper left corner
}

Just a minor overhead, data.GetTile(x, y) vs data[x][y] (if even that, I consider [][] harder to type than (,)).

This topic is closed to new replies.

Advertisement