Designing my structure of classes - Help!

Started by
14 comments, last by phresnel 14 years, 10 months ago
Hey there! I'm working on my very own simple game, and I've got some problems with sorting out how to put everything in classes. Especially the underlying graphicspart. I was wondering if anyone could help me - here's my chain of thought: 1) At first I thought of putting all graphical functions and variables in a namespace and call it a day. This however, seemed to be impractical, because: - it cluttered up the namespace with functions and vars lying everywhere about - it didn't provide any means for datahiding and encapsulation 2) Thus, I decided to put everything in classes. Not one class, because - and correct me if I'm wrong - that's not how classes are supposed to work. A class is supposed to be a manifestation of one idea, with one task, and nothing more. Right? So I devised e.g. a class for the core stuff, and a class for loading textures. They both work seperately, and well. The core sets up the window, clears the screen, swaps buffer, etc. The textureloader loads in textures and manages the resources. My question is twofold: - Is this the correct way to go? If, up until this point, I've been making terrible design decisions, please say so now. - Where should I put the texture dráwing functions. It'd make sense to put it in the core class, because the core handles basic rendering. This means I'd have to make the texturemanager a member of the coreclass, or sort of register the instance I'm using with a pointer in the core class. It also makes sense to put it in the textureclass, because it ís texture work. Then again, then the core doesn't handle all the rendering on it's own, and loses control. What's the way to go? - Stijn Frishert
Advertisement
From what I understand you have 2 classes now, correct? I don't know what kind of game you are trying to make, but typically the number of classes is a bit higher. You don't need a few hundred yet for a simply game, but if you have only 2 I think you need to look closely at the structure again.

I can't say to much about how the structure should be as I don't know what kind of game you are making, but in for example a 2D game, you could have a Game class that handles the game functions. Then have a RenderEngine that handles the window creation, rendering and keeps a list of textures. Then the Texture class handles texture loading and managing. To draw the textures, you have a Scene class that holds Objects. Each object has a position, size and pointer to the Texture.

Then when rendering (from Game), call scene->render(). The scene will ask the render engine to prepare for rendering (clear scene) and then will loop all objects, optionally checks if it's visible (in case of a scrolling 2D game) and then call texture->draw( position, size ). After rendering, it will tell the render engine it's all drawn, and the render engine shows to result.

For a complex game you will need many many more classes (have a look here for what a large rendering engine has), but if you are new to object oriented programming, don't start with to many directly.
Here's how mine is set up, more or less:

I have a class which handles basic fullscreen window initialization, and from this, I derive specialized windows to support either OpenGL or DirectX rendering -- I don't attempt to encapsulate drawing here, just the parts that must necessarily interact closely with the windowing functionality. The final window classes provide a means to get the output renders from DirectX or OpenGL onto the window, and correctly implemented full-screen/windowed mode switching.

I have other classes which encapsulate the graphics API in a way that makes sense for my games.

I have a class which represents a surface (for rendering) as well as one for textures.

I have a series of free-standing functions (they are not classes) for loading images of various formats into the texture class. These are often called through a function pointer in my AssetCache class (more commonly called a resource manager, although that's a terrible name for what it does.) When I need deferred loading, I have a class which encapsulates a call to the correct loader, and a copy of the filename and loading options.

Finally, I have my AssetCache, which shares out references (boost::shared_ptr) to assets that have already been loaded, or creates a new asset record and loads the file for the first time so that it may be shared with future requests.

throw table_exception("(? ???)? ? ???");

@ MichaBen

Oh, I've got way more classes than two. The problem concerns only these two though, so those are the ones I was talking about. I see you've put the actual drawfunction in the texture itself, but frankly, that doesn't seem like a good idea. Mainly because the user will need to have access to the entire class, which defies encapsulation of the texturedata.

@ Ravyne

I see. Basically, I'm going the right direction. My coreclass handles creating the window and everything related to that. Then, I divide up the (rest of the) graphics into several classes. Now, moving on to the next step, which class(es) handle(s) rendering?

Does one of those classes take care of all the rendering:

VideoCore.RenderTexture("Stone", 135, 120); // Which then gives a call to the class that holds the textures, retrieves the data, and rendersVideoCore.RenderFont("Font", "Hello, world!", 10, 786); // Same story


Or do you give calls to the individual classes?

TextureManager.RenderTexture("Stone", 135, 120);FontManager.RenderFont("Arial", "Hello, world!", 10, 786);


Or, as Michaben suggested:

TextureManager.GetTexture("Stone")->Render(135, 120);FontManager.GetFont("Arial")->Render("Hello, world!", 10, 786);


- Stijn Frishert
Well, a font or a texture would never render themselves, after all, a font and a texture is a shared resource -- which instance of itself would it draw?

In general, you're likely to have a class which represents an entity in the game world, or within the UI of your game, such as an enemy entity or text box. These will have some sort of reference (eg a pointer, reference or handle of some kind) to the font, texture or model data that are needed in order to render it.


There are really two schools of thought from here, either:

1) The entity has a render method, which is called upon in a loop inside your game function, passing in a reference to your renderer -- entity.render(rendercontext);

or

2) The render context has one (or more: rendermodel, rendertext) render function which take in an entity -- rendercontext.render(entity);


Which you choose really depends on your particular opinion on which is more natural -- however, I would strongly recommend against the entity.render() approach if that means including Direct3D or OpenGL code in your entities directly -- if you take that approach, you really should have a level of abstraction between your entities and the underlying API, or you'll really regret it when you decide to switch your project from OpenGL to Direct3D or vice-versa.

In general, however, you want to abide by the single-responsibility principle anyhow -- which states that an entity should simply be an entity, and nothing more. An entity which models that entity and also is responsible for its own rendering has more than one responsibility, and therefore violates that principle.

throw table_exception("(? ???)? ? ???");

Aaah yes. Well, I myself didn't like option 3 either, but MichaBen suggested it, so I figured it was an option too. Anyway, you say it's really just a matter of personal preference when choosing between 1 and 2? There's no fatal drawback to any of the two?

Great, then I've got my question answered. :)

Thanks, Ravyne!
The manager, if you have one at all, gives you a corresponding object, which is then involved in the process of doing something.

E.g.

Screen.drawText("Hello, world!", TextureManager.getFont("Arial"), x, y);
Quote:Original post by Frishert
it cluttered up the namespace with functions and vars lying everywhere about

You can make globals "private" to the compilation unit by declaring them static. That way, you don't clutter up the namespace with implementation details.
static void invisible_helper_function1(){    // ...}static void invisible_helper_function2(){    // ...}void visible_global_function(){    invisible_helper_function1();    if (blabla)    {        invisible_helper_function2();    }    // ...}
Quote:Original post by Frishert
Anyway, you say it's really just a matter of personal preference when choosing between 1 and 2? There's no fatal drawback to any of the two?
Either approach is workable but the rendercontext.render(entity) approach is superior in terms of design quality (Ravyne's already highlighted the Single Responsibility Principle in regards to this), it also has more scope for optimisations later such as batching and minimising GPU resource switching.

Quote:Original post by DevFred
You can make globals "private" to the compilation unit by declaring them static. That way, you don't clutter up the namespace with implementation details.

<snip>

Actually statics at namespace scope are deprecated and replaced with anonymous namespaces:

namespace{    void invisible_helper_function1()    {        // ...    }    void invisible_helper_function2()    {        // ...    }}void visible_global_function(){    invisible_helper_function1();    if (blabla)    {        invisible_helper_function2();    }    // ...}


[Edited by - dmatter on June 20, 2009 8:44:34 PM]
Quote:Original post by Frishert
Or, as Michaben suggested:

TextureManager.GetTexture("Stone")->Render(135, 120);
FontManager.GetFont("Arial")->Render("Hello, world!", 10, 786);


I personally thought you would keep a pointer to the texture resource rather then a name. In case you have the name, you could rather ask the resource manager to render it. Personally I would keep a pointer to the resource directly so you don't need to lookup the name each call. In that case you can call texture->render() directly from the object. Calling manager->render( texture ) is also possible, but then you have to remember 2 pointers.

Keeping all the rendering code in the same class isn't going to work for larger projects, the code become unmanageably large some day. For example, maybe you will start using dynamic textures some day, like a big screen that shows another part of the map. That means in the texture manager, you will have to add an if statement to check if the texture is a dynamic one, and handle it. Or you could make a class DynamicTexture which extends Texture, and have it override the render function. Then you keep the code for the special effect in a separated file and it stays manageable. One special effect inside the main class won't be a trouble, but maybe later you also want a ScrollingTexture, VideoTexture, MirrorTexture, and the code will grow and grow and before you know you have 5000 lines of code and don't know which part does what anymore.

This topic is closed to new replies.

Advertisement