Is there a better way to do this? (class design)

Started by
5 comments, last by Ravyne 15 years, 9 months ago
I have quite a few classes in my game that really only have one instance, which is global. Just to name a few examples: my rendering engine class, my input manager class, my application class, etc. So, any time I need to get input data from anywhere in my code, I just make a call like this: g_Input.IsKeyPressed( KEY_ID ); I recall some arguments on here about singletons, and some argue that they're no better than global data, and should not be used. My scheme is probably even worse, since my classes aren't true singletons. One could instantiate another Input object if desired, it's just that since I'm the sole programmer on this hobby project, I know not to do that. The funny thing is that I actually ported my input class from another project I did way back. Except, on that project, it wasn't a class. It was just some global data and some global functions. When I was porting it over to my current project, I was changing it to a class, and I remember thinking, "LOL, what the hell am I doing? This is so irrational." Thing is, I just like having it wrapped up in a class. I can't help but think that, if a real game programmer were to see it, they'd laugh.
Advertisement
I think wrapping the input functionality in a class does make sense, but declaring a global instance of that class doesn't.

By taking that global functionality and wrapping it in a class, you stopped it from being global and forced yourself to think where this functionality is actually needed. By creating a global instance of the class, however, you threw that away.

Ask yourself whether every class really needs to poll for input, render graphics, etc. If you answer yes, you should rethink your design to minimize coupling between classes.

Better yet, describe the relationships between your classes (or some key classes, if there are too many), i.e., which class uses which other classes and in what way. Then we might be able to give you more concrete feedback.

Also, this article has more to say on the subject (refer mostly to the points about global access).
Quote:Original post by CDProp
When I was porting it over to my current project, I was changing it to a class, and I remember thinking, "LOL, what the hell am I doing? This is so irrational."

Thing is, I just like having it wrapped up in a class. I can't help but think that, if a real game programmer were to see it, they'd laugh.


Ultimately, programming has a lot more to do with what makes sense than what you "just like". Or perhaps more accurately, as you improve as a programmer, your tastes change correspondingly.

What are you hoping to gain by using a class for the functionality? If it's only an organizational thing, you might consider using (assuming C++ or C#) a namespace instead.
Well, right now I have a "ControlCam" class that is basically just a camera class that has some mouse-look and mouse-movement functionality built-in so that I can move around the game world and view it from different angles. It's update function uses the global Input object in order to decide how to update its orientation matrix. Aside from that, I call g_Input.Update() in WinMain(), and I think that's the only other instance where it's used at this point in time. I imagine in the future, I'll have to use it in other classes that require access to input.

I have a GameWindow class that basically just registers a WinAPI window class, creates a window, and shows it. I don't have a global GameWindow object, though. Instead, I have an App class that has a single GameWindow member. And yes, I have a single global instance of App, so whenever I need access to the GameWindow for whatever reason, I call g_App.GetGameWindow().

I also have a RenderingEngine class, which holds all of the RenderingLists. It also holds the DirectX Device pointer. So, I init my graphics in the RenderingEngine class, and when I do that, I sometimes need access to the HWND of my game window, so from the RenderingEngine::Init() method, I have to call g_App.GetGameWindow()->GetWindowHandle().

And sometimes, in the RenderingLists methods, I need the device pointer, so I'll call g_RenderingEngine.GetDevice().

Also, when setting up the input, I need both the HWND and the HINSTANCE so I end up calling g_App.GetGameWindow()->GetWindowHandle() and g_App->GetAppInstance() from there too.

I hope that made sense and gives an idea of the sort of interplay between the classes.

Quote:Ultimately, programming has a lot more to do with what makes sense than what you "just like". Or perhaps more accurately, as you improve as a programmer, your tastes change correspondingly.

What are you hoping to gain by using a class for the functionality? If it's only an organizational thing, you might consider using (assuming C++ or C#) a namespace instead.


That's good advice. Yeah, it's purely an organizational thing. Global data just looks so naked to me. If I can cut a whole bunch of global data and methods down to one object, I feel like I've accomplished something even though I haven't.

Like I said, I was just laughing at myself as I was converting the input stuff to a class because I felt like it was a compulsion...as if I was washing my hands for the 100th time or something, knowing it was pointless, but feeling good anyway.

Edit: OOps, sorry for the double-post. I meant to edit this into my previous post.
Quote:Original post by CDProp
Well, right now I have a "ControlCam" class that is basically just a camera class that has some mouse-look and mouse-movement functionality built-in so that I can move around the game world and view it from different angles. It's update function uses the global Input object in order to decide how to update its orientation matrix.


You can poll the input somewhere else, and pass it to ControlCam. Then ControlCam would no longer need access to the input object.

Quote:I have a GameWindow class that basically just registers a WinAPI window class, creates a window, and shows it. I don't have a global GameWindow object, though. Instead, I have an App class that has a single GameWindow member. And yes, I have a single global instance of App, so whenever I need access to the GameWindow for whatever reason, I call g_App.GetGameWindow().


What reason might that be?

Quote:I also have a RenderingEngine class, which holds all of the RenderingLists. It also holds the DirectX Device pointer. So, I init my graphics in the RenderingEngine class, and when I do that, I sometimes need access to the HWND of my game window, so from the RenderingEngine::Init() method, I have to call g_App.GetGameWindow()->GetWindowHandle().


Again, have higher-level code pass the window handle to RenderingEngine.

Quote:And sometimes, in the RenderingLists methods, I need the device pointer, so I'll call g_RenderingEngine.GetDevice().


Yet again, you can pass the device pointer as a parameter to whatever functions need it.

Quote:Also, when setting up the input, I need both the HWND and the HINSTANCE so I end up calling g_App.GetGameWindow()->GetWindowHandle() and g_App->GetAppInstance() from there too.


You guessed it - pass the HWND and HINSTANCE as parameters.

Quote:I was just laughing at myself as I was converting the input stuff to a class because I felt like it was a compulsion


Like I said, I think this makes sense because it forces you to think where this functionality is actually needed (assuming you don't bypass this by creating a global variable), but I'd be interested to hear what other people think about this.
Its actually far preferable that this be a global, rather than a singleton. Granted, neither are good, but you've at least chosen the lesser of two evils.

What you want to do to resolve this problem is to limit the scope in which these classes can be seen. Taking your input class as an example, there's no reason that the sound system, resource loading or graphic engine needs to be able to see this global instance.

Instead, migrate the input class down into your game's class hierarchy (I'm talking composition here, not inheritence) to the common ancestor of everyone who needs it. This is where you instantiate your input class -- as a side note, if it seems like an odd location for it, its probably a hint that your overall structure is poorly designed.

Continuing on, any children of these classes (again, composition) which need access itself (or must pass through access to one of its descendants) should be passed a reference or pointer to the class instance via its constructor, keeping for itself a copy of the reference and/or passing it through as necessary.

While it sounds like a lot of work, it is actually very minimal in a well-designed system. It also has a habit of exposing design flaws, which is a very positive side-effect.

As an example, my last game contained a main class which represented the game application itself. This class took in its constructor an HINSTANCE parameter which is, in turn, passed to the Graphics Cache, who passes it to the Image Loader, which needs the hInstance in order to retrieve resources from the executable file. Another example comes from my GameState classes (which encapsulate distinct states of my game -- menu, pause screen, gameplay, etc) which are all passed references to the gamestate manager, the renderer and the graphics cache (all of which are owned by the game application class).

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

This topic is closed to new replies.

Advertisement