16
\$\begingroup\$

I'm making a 2D street fighter-like game that is not tile based. Usually people recommend that entities be given to a renderer that render them, not them render themselves, but it seems the inverse is better,

Why is one better over the other?

Thanks

\$\endgroup\$
2
  • \$\begingroup\$ Why do you think the inverse is better? \$\endgroup\$
    – Martin
    Commented Jun 11, 2011 at 4:05
  • 1
    \$\begingroup\$ @Martin because the object will hint which bitmap to use anyways, so why not just do object->render(); \$\endgroup\$
    – jmasterx
    Commented Jun 11, 2011 at 4:08

6 Answers 6

13
\$\begingroup\$

An couple of considerations :

  • as you mentionned, each sprite would have to "hint" about which bitmap to use, but if the entity has to render itself. What would that 'hint' be ? If it is a reference to a different bitmap, sprite sheet, etc... for each sprite, then you might end up using more memory than necessary, or having troubles managing that memory. An advantage of a separate renderer is that you have only one class responsible for any asset management. That said, in a SF2-like fighting game, you might only have two sprites ;)

  • as mentionned elsewhere, whenever you want to change your graphical API, you have to change the code for all your sprites.

  • rendering is rarely done whithout a reference to some graphical context. So either there is a global variable that represent this concept, or each sprite has an interface with render(GraphicalContext ctx). This mixes the graphical API and the logic of your game (which some people will find unelegant), and might cause compilation issues.

  • I personnaly find that separating the rendering from the individual entities is an interesting first step in the direction of viewing your game as a system that does not necessarily need graphics at all. What I mean is that when you put rendering out of the way, you realize lots of the gameplay happens in a "non-graphic world" where the coordinates of the entities, their internal states, etc... is what matters. This opens the door to automated testing, more decoupled system, etc...

All in all, I tend to prefer systems where rendering is done by a seperate class. That does not mean your sprites can not have some attributes that are "graphically related" (animation name, animation frame, height x width, sprite id etc... ), if that makes the renderer easier to write or more efficient.

And I don't know if that would apply to 3D (where the notion of meshes, and the coordinates variable you would use would maybe be tied to your 3D API ; whereas x,y,h,w is pretty much independant of any 2D API).

Hoping this helps.

\$\endgroup\$
11
\$\begingroup\$

You want the rendering system to be in control of what gets drawn when. If instead the sprites are in control of the rendering you loose on lots of efficiency gains and flexibility. I'm of the opinion that having the render system in control results in cleaner code.

Some advantages of centralized rendering:

  • z-ordering:
    If the game objects themselves are responsible for rendering you'll have to make sure you call them in the correct order. Otherwise background objects may be drawn over foreground objects.
    With the render system in control, it can choose to sort all render objects, detect overloaps at render-time and just render those, or just forego ordering all together. The point is that decision can be made easily now.
  • batching:
    The other obvious advantage of allowing the render system to be in control is batching. Here again the render system has to option to batch sprite renders the share a texture. It can use triangle slicing to render everything with one call. It may be able to cache some render calculations. Or it could just render each sprite in turn with none of that fancy stuff. (Note: it is possible batch when each object renders itself, but the problem is less efficient and more complex).

The way I implement this in my games it to have game objects register the sprites they want drawn with the render system. When the object no longer wants the object drawn it unregisters the sprite, or marks it inactive.

All that said. If it's easier to have your game objects render themselves by all means do it that way. It's much more important to make progress and get something / anything drawn than it is to have a perfect architecture.

\$\endgroup\$
1
  • \$\begingroup\$ About z-ordering if objects draw themselves cant a system decide about the order to call their drawing method? I mean centralized vs non centralized seem to make no difference about z-ordering. \$\endgroup\$
    – GorillaApe
    Commented Mar 12, 2015 at 13:56
3
\$\begingroup\$

Disclaimer: your question doesn't give much detail so I'm responding with a general principle. Please excuse me if I misunderstood your use or 'render'.

I generally use an external object to render various actors in a scene as a way of encapsulating scene level properties and methods outside of the individual 'actor objects.' Objects in the scene should only contain internal methods and properties; they should only know about what they themselves are and what they do. Presumably, they will be influenced by other objects in the game as well as user input. This will effect how/whether they are rendered on the screen. A 'director object' can, for example, translate the 'w' keypress to jump, then tell the actor object .jump(). Such director level logic can also tell actors to enter or exit the scene entirely.

Cheers, David

\$\endgroup\$
3
  • \$\begingroup\$ But in that sense couldn't the director just say acton->setVisible(false); ? \$\endgroup\$
    – jmasterx
    Commented Jun 11, 2011 at 4:10
  • \$\begingroup\$ Even in the setVisible(false) case, it's an external entity that does the rendering by checking the actor's visible variable and rendering it only if it's true. \$\endgroup\$
    – Nav
    Commented Jun 11, 2011 at 9:10
  • \$\begingroup\$ Just making an actor invisible does not remove it from the scene. It must also stop participating in collisions etc. \$\endgroup\$
    – finnw
    Commented Jun 30, 2011 at 17:58
3
\$\begingroup\$

What if someday you want to port your game to a different resolution (ie iPhone and friends). Thus, a global property about rendering changes, how do you easily update your code?

\$\endgroup\$
3
\$\begingroup\$

What I used was an observer-based design. When I created an instance of a class I wanted to render, then a pointer to it was stored in the central Renderer class. When you call RenderFrame(), then the renderer already has all the existing objects it needs to render and accessed their properties to do it. The classes themselves had no idea that they were going to be rendered at all. This API was nice and clean and easy to use.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ +1 Interesting. I've used this approach for sound while using the Visitor Pattern for graphics. I thought this made more sense for sound because while the graphics and AI are running on the same clock, the audio mixer is running on another so an event model is easier. Also its not critical if a movement event (that causes the pan/reverb of an audio channel to change) arrives a few milliseconds late but it is critical if a sprite is drawn in the wrong state. \$\endgroup\$
    – finnw
    Commented Jun 30, 2011 at 17:54
2
\$\begingroup\$

In general it's always about how easy it is to maintain and expand your code. Tomorrow you figure out that you don't like the graphics API you're using currently, and want to switch. Will you now have to go through all of your objects classes and change everything, or do you still just need to change your code in one central point of the project?

It depends on what your objects are really doing when you call render(). As long as they just wrap method calls around your graphics engine, it's completely fine, since logics <-> graphics distinction will still be given.

For example, if your render() methods are basically convenience-methods and look something like this:

void MyClass::render(const Graphics &g)
{
    g.draw(this);
}

or

void MyClass::render()
{
   mySprite->render();
}

or

void MyClass::render()
{
    mySprite->UseShader(thatshader);
    mySprite->render();
}

or close to that, I don't think it's a problem whatsoever.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .