3
\$\begingroup\$

I am currently rewriting a project I have been working on last year and I’m utterly stuck trying to "correctly" separate the game-logic from its representation. I know why this is beneficial and I sort of get how to build the representation into its own subsystem but if you actually encapsulate all the game-states into a different object or even library, how can you draw the scene?

Example:

Say we are working on the Super Mario game. There would perhaps be some Mario-object that encapsulates the different states Mario can enter (e.g. Small, Big, Fire-shooting, Mounted, …). These states are not purely visual since Mario’s movement-speed or damage-handling depends on this.

However, the way Mario is drawn also depends on this very state. So how would the rendering-layer know which sprite to bring onto the screen?

Pretty much everything the game-logic does causes some sort of animation, sound, or other output. In a way, having the drawing-logic mixed with the internals seems to be the cleanest since they really do belong together.

If you do separate the game-logic from its representation, there needs to be a way to access the data. What’s the best way to do that? I could think of these:

  1. Data-binding by using some Event-system

    Every time some state in the logic-layer changes, it could publish some event to which all other parts of the game can subscribe to. Upon receiving an Event, they can update themselves and load the appropriate animations.

    However, this feels like we’re removing the responsibility of drawing the state onto the screen just to add the responsibility of firing an Event, which might actually be messier: There would be Events for each entity and each state, every spell that can be cast, every changed movement-parameter, plus every started, finished and interrupted attack – the list is huge compared to a function that “just renders the internal state”.

    Also, there is a lot of bookkeeping to be done since each Event needs to manage a list of subscribers, which would probably be an Interface resulting in many indirect calls, which might cause a performance-hit as well.

  2. Access the data from the rendering-layer

    The representation-layer could just query the state, either by simple Getters or a complex Entity-Component-System where the data would be accessed plainly, anyway.

    It still feels wrong to open up the (encapsulated) state like this, but it seems to be cleaner approach. In another post, I saw the use of the visitor-pattern, which sort of does this as well, but then there would be a Draw()-method on the game-object again, which sort of defeats the purpose, doesn't it?

Any input is highly appreciated cause I really want to stop thinking about it and just get into coding again. :D

Thank you so much!

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Both method A and B are feasible.

While an event system is the best way to decouple components, keep in mind that such systems tend to create a lot of overhead. The event handling and dispatching itself creates overhead and you might end up duplicating data in multiple layers of your architecture which takes additional memory and might cause a few bugs due to the additional complexity involved.

A much more efficient way is to simply have the game mechanics directly expose any state required by the rendering system, so the rendering system can operate on the original data. But this creates a tighter coupling between the rendering system and the gamestate.

An in-between solution could be to create another layer between the gamestate and the rendering system: the animation system. This system knows the state of the game mechanics and uses them to determine which spriteID to use for each object in the game on each frame. For example, when mario.hasStar == true it alternates between spriteIDs from the original spritesheet and spriteIDs from the alternative colors spritesheet each frame.

The rendering system then reads the spriteID the animation system came up with, determines the corresponding resource, and renders it.

As David Wheeler once said:

All problems in computer science can be solved by another level of indirection... Except for the problem of too many layers of indirection.

\$\endgroup\$

You must log in to answer this question.

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