Skip to main content
gamedev.net
🔒 Locked

Decoupling classes

Started by besx Apr 4, 2009 at 9:03 PM 3 replies 2k views
Original Post
besx
besx
Hello, I just ran into a (for me) big problem regarding the dependencies between the classes in my game. I am not quite sure how to design my code in a way that I do not have to pass references and pointers over and over again. For now it looks like this: I have a Application class that contains all the subsystem-classes (window, graphics, audio, input, resource manager, scene manager...). But for my taste they depend on each other to much. E.g. the scene manager needs to know about the graphic-subsystem for drawing, the audio subsystem for playing sounds, the resource manager for loading resources and so on. The resource manager needs to know about the graphics subsytem because I need the IDirect3DDevice instanceto load textures. Then the entities in the current scene (that gets handled by the scene manager) needs some access to the graphics subsystem to draw themselves or get drawn, to the audio system to play der sounds ect.). For now my "solution" is to pass around references for all those systems to eather methods or the constructors of the classes they are needed in. Then it looks like this: In the Application-class' main loop, this method get's called:

myScreenManager->Present(myRenderWindow->GetGraphics(), myAudio);
In the scene manager then it looks like this:

void ScreenManager::Present(const shared_ptr<Graphics>& graphics, const shared_ptr<Audio> &audio)
{
	myScreens.back()->Present(graphics, audio);
}
Then again the entities, that are contained by the Screen class want to load their resources so they need access to the ResourceManager again... (and so on) I really do not like this because everytime I need to access another part of my engine/game I need to update method's declaration and definition and pass them through a lot of classes until it reaches the point where I acutally need it. And I do not like singletons at all, so I don't want to use them if no one of you will convincw me to do so =) Maybe you have some suggestions or further reading for me on this topic. So, thanks in advance for that, I hope it is quite understandable what I mean ;-) Greetings from Germany Benjamin
Zipster
Zipster
What you really want to make sure is that your implementations are decoupled, so that a particular implementation of your resource manager doesn't depend on a particular implementation of your graphics subsystem. However, it's perfect fine for "the thing that handles resources" (the resource manager) to know about "the thing that loads textures" (the graphics subsystem), so long as they're hidden behind interfaces and abstractions. You keep the logical dependence, but remove the "physical" (or implementation) dependence. That way, you can stub in or swap out any piece of the puzzle at any time. Maybe one day you won't need the graphics subsystem to load textures, and you can swap in a custom texture loader that implements the same interface. If you designed your code right then it will be easy. Maybe one day you decide to license your graphics library but don't anyone to have your audio library. Even though "the thing that renders" may depend on "the thing that plays sounds", since it's all hidden behind an abstract interface you can just supply a stub audio library implementation that doesn't do anything with the graphics library implementation. Or, your licensee can hook in their own audio system and have access to all the features of your graphics library without depending on your particular audio library.

The point to take away from all this, is that you should think in terms of what services your subsystems provide, as opposed to how they provide it. The resource manager doesn't really need graphics, what it needs is something to load its textures, which could be just about anything.
ApochPiQ
ApochPiQ
I would adjust things roughly as so:

void MainGameLoop(){  while(we_are_not_done)  {    GraphicsSystem.GenerateOperations(graphics_ops);    AudioSystem.GenerateOperations(audio_ops);    GraphicsRenderer.ExecuteOperations(graphics_ops);    AudioRenderer.ExecuteOperations(audio_ops);    // etc.  }}



Essentially this is taking advantage of the command pattern to decouple your systems. The graphics engine just queues up a list of things it wants to render (say, sprites) and the renderer actually does the job of drawing them, without caring how the list of operations is generated.

Similarly, audio operations are built up for the current frame, and then the list of audio requests is fired off to the audio render logic.

You can trivially introduce these types of interfaces as wrappers for your own implementations of the logic, or for middleware, or whatever. Basically, the goal is that your code should be talking about what it does, not how it does it.
Darg
Darg
Speaking from a C# perspective (which quite possibly cannot be implemented in C++) I tend to make quite widespread use of static classes or variables in these situations.

For example my GameEngine class has a static pointer for the device and content managers. Any class within the same package or with a pointer to that package can therefore just call GameEngine.device etc.

An example of a static class would be my configuration within the gameengine package. Upon engine initialisation the Config static class is told to load a configuration from a serialized xml file. If it cannot find the file then it uses default values. To access any of these values then all I need to do is again call Config.res.X for example which returns the desired width.

You can overuse static classes though, I originally used a static class for my Camera which I'm now regretting, I plan to change that into a standard object so I can have several of them. On a side note I also recommend keeping any code within a camera class to precisely what the camera needs. Any input should be done elsewhere.

Basically anything that you'll only ever need one of can be done via static classes. My Terrain class is static along with my Water class (this just contains the water height and the vertices of the water plane).

For things like your audio/physics etc a static class could work quite well, as I said though make sure not to overuse it though.
Portfolio & Blog:http://scgamedev.tumblr.com/
JustChris
JustChris
For my graphics subsystem, I have a SceneManager class for keeping track of all renderable entities such as textures, lights and 3d meshes. The class does not really need to know how these entities are used in the rendering pipeline. I had the same problem as you, in that the early builds of the class passed the IDirect3DDevice object during every single frame, and the object has to 'trickle down' to the entities themselves so that it can access the commands to set a texture, draw geometry, etc.

Does every one of your entities take the IDirect3DDevice object to do rendering work? If so, I would remove this dependency. Instead of having an entity depend directly on a subsystem, have the subsystem class use the entity as a parameter.

To solve this, some kind of "loader" and "renderer" is needed. I wrote loader and renderer objects that specialize in handling a certain type of entity, so these would have a reference to IDirect3DDevice. Now instead of having an arbitrary number of entity objects using this directly, you only have two types of objects for each entity type accessing the device.

As you might expect, loaders typically do something once for setup, and renderers work on each frame. I haven't gotten yet to incorporating audio in my program but I plan to do something similar.

// In my case, GraphicsDisplay would probably be like your Graphics class. // It sets up the Direct3D rendering process so it has a method to return the IDirect3DDevice objectvoid SceneManager::init(GraphicsDisplay &Display) {    // expanding the setup into more functions isn't necessary, but this is    // just for clarity    setupLoaders(Display);    setupRenderers(Display);}void SceneManager::setupLoaders(GraphicsDisplay &Display) {    // these loaders reside in your class    pTextureLoader = new TextureLoader(Display->D3Ddevice());    pVertexLoader = new VertexLoader(Display->D3Device());    // etc...}void SceneManager::setupRenderers(GraphicsDisplay &Display) {    // similar to setupLoaders}void SceneManager::render() {    // note how there are no arguments here!    // the Renderers already took access to IDirect3DDevice    // back in the init() method    // these renderer methods would use that device in some way     YourTextureRenderer->setSomething(YourTexture);    YourVertexRenderer->drawPrimitives(YourVertexBuffer);}


All it's doing here is passing a reference to the graphics subsystem just once, during setup, so the other functions do their work. You might call SceneManager::init() before the main loop, for instance. Renderers and Loaders get setup in the constructors, and then keep hold of the D3D device reference until they get deleted.

All that's needed besides this is to forward declare classes for your graphics subsystem and any other subsystems you might have. SceneManager may include objects for them in its functions, but you won't ever need to use subsystem functions directly because its Loaders and Renderers take care of that.
Electronic Meteor - My experiences with XNA and game development

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.