11
\$\begingroup\$

Disclamer: I know what an entity system pattern is and I'm not using it.

I've read a lot about separating object and rendering. About the fact that the game logic should be independent from the underlying rendering engine. That's all fine and dandy and it makes perfect sense, but it causes a lot of other pains too:

  • need for synchronization between the logic object and the rendering object (the one that keeps the state of the animation, the sprites etc)
  • need to open the logic object to the public in order for the rendering object to read the actual state of the logic object (often leading the logic object to easily transform in a dumb getter and setter object)

This doesn't sound like a good solution to me. On the other hand it's very intuitive to imagine an object as its 3d (or 2d) representation and also very easy to maintain (and possibly much more encapsulated as well).

Is there a way to maintain graphic representation and game logic coupled together (avoiding synchronization issues) but abstract away the rendering engine? Or is there a way to separate game logic and rendering that doesn't cause the above drawbacks?

(possibly with examples, I'm not very good at understanding abstract talks)

\$\endgroup\$
2
  • 1
    \$\begingroup\$ It would also be helpful if you provide an example of what you mean when you say you're not using the entity system pattern, and how you think that relates to whether or not you should separate the concern of rendering from the concern of entity/game logic. \$\endgroup\$ Dec 6, 2013 at 1:37
  • \$\begingroup\$ @michael.bartnett, I'm not separating objects in small reusable components that are handled by systems, the way most implementation of the patterns do. My code is more an attempt to the MVC pattern instead. But it doesn't really matter as the question is not dependent on any code (not even a language). I've put the disclamer because I knew some would have tried to convince me into using the ECS, that seems to cure cancer. And, as you can see, it happened anyway. \$\endgroup\$
    – Shoe
    Dec 6, 2013 at 1:42

3 Answers 3

13
\$\begingroup\$

Suppose you have a scene composed of a world, a player, and a boss. Oh, and this is a third person game, so you also have a camera.

So your scene looks like this:

class Scene {
    World* world
    Player* player
    Enemy* boss
    Camera* camera
}

(At least, that's the basic data. How you contain the data, is up to you.)

You only want to update and render the scene when you are playing the game, not when paused, or in the main menu... so you attach it to the game state!

State* gameState = new State();
gameState->addScene(scene);

Now your game state has a scene. Next, you want to run the logic on the scene, and render the scene. For the logic, you just run an update function.

State::update(double delta) {
    scene->update(delta);
}

That way you can keep all the game logic in the Scene class. And just for the sake of reference, an entity component system might do it like this instead:

State::update(double delta) {
    physicsSystem->applyPhysics(scene);
}

Anyways, you've now managed to update your scene. Now you want to display it! For which we do something similar to the above:

State::render() {
    renderSystem->render(scene);
}

There you go. The renderSystem reads the information from the scene, and displays the appropriate image. Simplified, the method for rendering the scene might look like this:

RenderSystem::renderScene(Scene* scene) {
    Camera* camera = scene->camera;
    lookAt(camera); // Set up the appropriate viewing matrices based on 
                    // the camera location and direction

    renderHeightmap(scene->getWorld()->getHeightMap()); // Just as an example, you might
                                                        // use a height map as your world
                                                        // representation.
    renderModel(scene->getPlayer()->getType()); // getType() will return, for example "orc"
                                                // or "human"

    renderModel(scene->getBoss()->getType());
}

Really simplified, you'd still need to, for example, apply a rotation and translation based on where your player is and where he's looking. (My example is a 3D game, if you go with 2D, it'll be a walk in the park).

I hope this is what you were looking for? As you can hopefully recollect from the above, the render system doesn't care about the game's logic. It only uses the scene's current state to render, i.e. it pulls the necessary information from it, in order to render. And the game logic? It doesn't care what the renderer does. Heck, it doesn't care if it's displayed at all!

And you don't need to attach rendering information to the scene either. It should be enough that the renderer knows it needs to render an orc. You'll have loaded an orc model already, which the renderer then knows to display.

This should satisfy your requirements. The graphic representation and logic are coupled, because they both use the same data. Yet they are separate, because neither relies on the other!

EDIT: And just to answer why one would do it like this? Because it's easier is the simplest reason. You don't need to think about "such and such happened, I should now update the graphics". Instead you make stuff happen, and each frame the game looks at what's currently happening, and interprets it in some way, giving you an on-screen result.

\$\endgroup\$
7
\$\begingroup\$

Your title asks a different question than your body content. In the title, you ask why logic and rendering should be separated, but in the body you ask for implementations of logic/graphics/rendering systems.

The second question has been addressed previously, so I will focus on the first question.

Reasons for separating logic and rendering:

  1. The widely held notion that objects should do one thing
  2. What if you want to go from 2D to 3D? What if you decide to change from one rendering system to another in the middle of the project? You don't want to crawl through all your code and make huge changes in the middle of your game logic.
  3. You would likely have reason to repeat sections of code, which is generally regarded as a bad idea.
  4. You can build systems to control potentially huge swaths of rendering or logic without individually communicating with small pieces.
  5. What if you want to assign a gem to a player but the system is slowed down by how many facets the gem has? If you have abstracted your rendering system well enough you can update it at different rates to account for expensive rendering operations.
  6. It allows you to think about things that really matter to what you are doing. Why wind your brain around matrix transforms and sprite offsets and screen coordinates when all you want to do is implement a double-jump mechanic, draw a card, or equip a sword? You don't want the sprite representing your equipped sword rendering in bright pink just because you wanted to move it from your right hand to your left.

In an OOP setting instantiating new objects has a cost, but in my experience the cost to system resources is a small price to pay for the ability to think about and implement the specific things that I need to get done.

\$\endgroup\$
6
\$\begingroup\$

This answer is only to build intuition of why separating rendering and logic is important, rather than directly suggesting practical examples.

Let's assume we have a big one elephant, nobody in the room can see the whole elephant. maybe everybody even disagrees on what it actually is. Because everyone sees a different part of the elephant and can only deal with that part. But in the end this doesn't change the fact that it is a big one elephant.

The elephant represents the game object with all its details. But nobody actually needs to know everything about the elephant (game object) to be able to do its functionality.

Coupling the game logic and the rendering is actually like making everyone see the whole elephant. If something changed everybody needs to know about it. While in most cases they only need to see the part that they are only interested in. If something changed the person who knows about it, needs only to tell the other person about the result of that change, that is what only is important to him (think of this as communication via messages or interfaces).

enter image description here

The points you mentioned are not draw backs, they are only draw backs if there was more dependencies than there should be in the engine, in other words, systems see parts of the elephant more than they should have. And this means the engine was not "correctly" designed.

You only need synchronization with it's formal definition if you are using a multi-threaded engine where it puts the logic and the rendering in two different threads, and even an engine that needs a lot of synchronization between systems is not particularly greatly designed.

Otherwise, the natural way to deal with such case, is designing the system as input/output. The update does the logic and outputs the result. The rendering is only feed with the results from the update. You don't really need to expose everything. You only expose an Interface that communicates between the two stages. The communication between different parts of the engine should be via abstractions (interfaces) and/or messages. No internal logic or states should be exposed.

Let's take a simple scene graph example to explain the idea.

Updating is usually done via a single loop called the game loop (or possibly via multiple game loops, each running in a separate thread ). Once the loop updated ever game object. It only needs to tell via messaging or interfaces that objects 1 and 2 where updated and feed it with the final transform.

The rendering system only takes the final transform and doesn't know what actually changed about the object (for example specific collision happened etc). Now in order to render that object it only needs the ID of that object and the final transform. After that the renderer will feed the rendering api with the mesh and the final transform without knowing anything else.

\$\endgroup\$

You must log in to answer this question.

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