8
\$\begingroup\$

I'm putting together a simple 2d tile-based game. I'm finding myself spinning circles on some design decisions, and I think I'm in danger of over-engineering. After all, the game is simple enough that I had a working prototype inside of four hours with fewer than ten classes, it just wasn't scalable or flexible enough for a polished game.

My question is about how to structure flow of control between game entity objects and their rendering objects. Should each renderer have a reference to their entity or vice-versa? Or both? Should the entity be in control of calling the render() method, or be completely oblivious?

I know there are several valid approaches here, but I'm kind of feeling decision paralysis. What are the pros and cons of each approach?

\$\endgroup\$
3

2 Answers 2

2
\$\begingroup\$

Assuming your "game entity" is a thing full of all sorts of game-specific bits (like health or a set of weapons or whatever), I would prefer an approach where the game entities know nothing about the renderer, as well as the other way around. Instead, you have a higher level bit of code in your primary loop which knows about both subsystems collect together all the game entities and create a bunch of render objects (or reuse cached ones, etc) based on the properties of the game objects. You then submit this list of render objects to the renderer itself. At its purest form this might look like:

while(!finishedPlayingTheGame) {
  GatherInput();

  // In practice this may do culling based on the collision world or whatnot
  // to remove objects that shouldn't be seen according to the game logic.
  List<GameObject> gameObjects = UpdateGameObjects();

  List<RenderObject> renderObjects = CreateRenderObjectDescriptions(gameObjects);
  renderer.Enqueue(renderObjects)
  renderer.Draw()
}

An advantage to this approach is a much higher isolation between the game and render logic; the disadvantage is the extra post-processing of the game objects into render descriptions.

In practice, you can alleviate the algorithmic overhead of that second processing pass by pushing the creation/configuration of a render object for a given game object into the loop that iterates the game objects and updates them. This typically doesn't introduce that much coupling between the logic and render subsystems.

If you are taking a component-focused approach that supports extensible aggregation of behavior into game objects, you can have the renderer publish a component that can be attached to each game object that configures its render object. This can allow for more extensible customization (via, for example, scripts -- which is probably overkill for this project).

What I think you should not do is directly have game objects a "render myself" method, because I think this creates scalability problems in terms of reconfiguring rendering techniques and styles (it can be much harder to have different classes of object share render configuration code this way). Plus it tightly couples the two subsystems and creates additional responsibilities for the game object interface (interfaces should ideally have one responsibility).

\$\endgroup\$
0
\$\begingroup\$

The lesser your entites know, the best. Do not assume your game "is simple". You may want to modify, improve, update it and then a not-too-bad design will help.

You can use a bus strategy : create an interface Bus. It will be able to register :

  • Id of the image to display;
  • If necessary, size of the image to display;
  • x & y coordinates.

So that would be a very simple function :

void addReference(int idimage, uint width, uint height, int x, int y);

Then, the main loop will pass a reference to a Bus implementation to every entities. So your entites will need to know the interface (you can do it with an event system too, but it's not that different), but not the implementation, that's not too much a price to pay. This design has one quality : you can do many things with it.

1°) Display as it comes : immediately after addReference, display the entity. It's what I'm using for my Roguelike, because I do not need more.

2°) Wait for the full references (so you'll need to create a special object to store only display references - the logic being close to a reference system) and have rules for the display stored in the bus class.

3°) Store the references received, and check if there was any change (you might want to add an entity identity number for this), so you'll only display what changes.

You can have the bus do the rendering, or just be a pipeline for another class. Note that you'll have to implement a camera logic somewhere.

\$\endgroup\$

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