Wednesday, June 14, 2017

architecture - Tactics for moving the render logic out of the GameObject class


When making games you often create the following game object from which all entities inherit:


public class GameObject{
abstract void Update(...);
abstract void Draw(...);
}


So in you update loop you iterate over all the game objects and give them a chance to change state, then in the next draw loop you iterate again over all the game objects and give them a chance to draw themselves.


Although this works fairly well in a simple game with a simple forward renderer it often leads to a few gigantic game objects that need to store their models, multiple textures and worst of all a fat draw method that creates a tight coupling between the game object, the current render strategy and any rendering related classes.


If I were to change the render strategy from forward to deferred I would have to update a lot of game objects. And the game objects I make are not as reusable as they could be. Of course inheritance and/or composition can help me fight code duplication and make it a bit easier to change implementation but it still feels lacking.


A better way, perhaps, would be to remove the Draw method from the GameObject class altogether and create a Renderer class. The GameObject would still need to contain some data about it's visuals, like what model to represent it with and what textures should be painted on the model, but how this is done would be left to the renderer. However there are often a lot of border cases in rendering so although this would remove the tight-coupling from the GameObject to the Renderer, the Renderer would still have to be all knowing about all the game objects which would make it fat, all knowing and tightly-coupled. This would violate quite a few good practices. Maybe Data-Oriented-Design could do the trick. Game objects would certainly be data, but how would the renderer be driven by this? I'm not sure.


So I'm at a loss and can't think of a good solution. I've tried to use the principles of MVC and in the past I had some ideas about how to use that in games, but recently it doesn't seem as applicable as I thought. I would love to know how you all tackle this problem.


Anyway let's recap, I'm interested in how the following design goals can be achieved.



  • No rendering logic in the game object

  • Loose coupling between game objects and render engine


  • No all knowing renderer

  • Preferably runtime switching between render engines


The ideal project setup would be a separate 'game logic' and render logic project that don't need to reference each other.


This thought train all started when I heard John Carmack say on twitter that he has a system so flexible he can swap out render engines at run time and can even tell his system to use both renderers (a software renderer and a hardware-accelerated renderer) at the same time so he can inspect differences. The systems I've programmed so far aren't even near that flexible



Answer



A quick first step to uncoupling:


Game objects reference an identifier of what their visuals are but not the data, let's say something simple like a string. Example: "human_male"


Renderer is responsible for loading and maintaining "human_male" references and passing back to objects a handle to use.


Example in horrible pseudocode:



GameObject( initialization parameters )
me.render_handle = Renderer_Create( parameters.render_string )

- elsewhere
Renderer_Create( string )

new data handle = Resources_Load( string );
return new data handle

- some time later

GameObject( something happens to me parameters )
me.state = something.what_happens
Renderer_ApplyState( me.render_handle, me.state.effect_type )

- some time later
Renderer_Render()
for each renderable thing
for each rendering back end
setup graphics for thing.effect
render it


- finally
GameObject_Destroy()
Renderer_Destroy( me.render_handle )

Sorry for that mess, anyways your conditions are met by that simple change away from pure OOP based on looking at things like real world objects and into OOP based on responsibilities.



  • No rendering logic in the game object (done, all the object knows is a handle so it can apply effects to itself)

  • Loose coupling between game objects and render engine (done, all contact is via an abstract handle, states that can be applied and not what to do with those states)

  • No all knowing renderer (done, only knows about itself)


  • Preferably runtime switching between render engines (this is done at the Renderer_Render() stage, you do have to write both back ends though)


Keywords you can search on to go beyond a simple refactoring of classes would be "entity/component system" and "dependency injection" and potentially "MVC" GUI patterns just to get the old brain gears spinning.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...