Advice needed on some OO design decisions (long)

Started by
9 comments, last by swiftcoder 18 years, 7 months ago
(this post might get VERY long, so sorry in advance) For the past few months I've been working on the foundations for my latest game project - a kind of hybrid space sim/strategy game. Specifically, I've been building up a very simple 3d engine to use with it and a basic GUI "system". I have a long way to go with both, but I've realized it's about time that I start thinking about how all of this is going to fit together. This is probably the most ambitious project I've worked on, however, and I've never been very good at working out OO design concepts so I'm looking for a little advice. In the past, I've always used some sort of top-level CGame-style (singleton) class that controls a whole bunch of other classes. I know this is bad design and I know from experience that it gets unwieldly and difficult to maintain. The problem is that I'm not sure of the 'proper' way to do things. Right now, these are the 'big' objects I'm planning on implementing. I'm just looking for a little advice on whether I'm going in the right direction or if there's something I should change; I'm certainly early enough in the project to adjust my thinking. (To put things in context: The game is fairly open-ended in nature. Even though the game is broken up into levels, the entirety of the game world is persistent between them. Game flow is mostly non-linear. The player is tossed out into the world at the beginning of a level and is free to move around in it. A lot of the NPC actors in the game world are purely incidental and will just be acting based on their own internal AI scripting. Triggers for scripted story events will occur based on the player's position, time since the start of the level, etc.) Renderer - All the methods for actually getting things drawn on the screen will be in here. It'll probably encapsulate my 3d engine. I was thinking of just throwing my engine right into the game and having the other objects work with it directly, but it seemed more practical to do things like this. Like I said, the engine is very rudimentary and it's almost guaranteed to change later. I figure this way I can change anything I need to and the only code I'll have to alter will be inside the renderer. GUI - GUI elements are built outside of the game and loaded into the game by the GUI object. My plan is for everything - from the in-game interface to the main menu - to be built this way. GUI objects will load whatever interface file is required and ask the renderer to draw it. Input will probably be controlled from in here too. The GUI pretty much determines everything the player sees. GameStates - The 'Intro', the 'Main Menu', the 'In-level Mode', the 'At Home mode', etc. are all GameStates. Most of the meat of the game is in these. GameStateController - The controller switches between different game states depending on player input or if the game just started or whatever. For instance, when the game starts up it'll give 'control' to the intro state. When that finishes, it'll be time for the main menu game state to begin and so on. Multimedia - Pretty much the same deal as the renderer, except for sounds, music, and movies. As far as 'top-level' objects go, that's about it for the moment. This obviously doesn't include objects to represent actual entities in the game world since I don't think I'll have as much of a problem with that. All that said, these are my questions/concerns that I'm looking for some help and advice with: 1) Should the Renderer class just be part of the GUI? As far as I can tell, the GUI will be the only object that ever makes use of the Renderer, since it controls everything the player sees. On the other hand, I like the idea of keeping it separate. Same question goes for the Multimedia object since its purpose is the same. 2) Should a separate object control player input? I figured it made more sense to make this part of the GUI object, but I also couldn't think of a way to separate it. If I did, would the GUI object just constantly query the input object or would the input object constantly send along messages to the GUI? They seem so completely linked that I'm having trouble seeing how I could implement them separately. 3) Pretty much everything that happens in the game is going to happen either in the GameStates or in other objects that they control. This seems a little daunting to me and not much different from having one mega-class that controls everything, but I'm not sure how else to do things. Right now, for instance, I plan on the 'In-level' state containing a World object that contains (or is responsible for loading/generating) ALL of the world data. The objects controlled by the World object would all contain methods for updating themselves during the game loop and the 'In-level' state object would then pass along relevant information to the GUI so it can be updated each frame. Something about all of this seems horribly wrong to me, but my brain has locked itself on this path and I can't think of an alternative. 4) Do I need the Controller? It seems like the individual GameState objects could take care of themselves. For instance, the 'In-level' GameState should know that when it receives an ExitToMenu message from the GUI it's time to transfer control to the 'Main Menu' State, and when that State receives a ResumeGame message it should know that it's time to transfer control back to the 'In-level' State. So, yeah, I think this post has gotten long enough. I'd really appreciate any advice, since this project is sort of an exercise in OO design for me. I've never made a serious effort to 'get it right' before, so I'm more than willing to completely scrap everything I have in favor of a better design. Edit- And thanks in advance to anyone who actually reads all of that. :) [Edited by - Paradoxish on September 8, 2005 1:06:11 AM]
Advertisement
Well, my advice would be to do it the way you feel comfortable doing it. Don't try to do things in an OO way just to make it OO.

I would keep building the game until I reached a point where I started to get confused about how to implement something. Then I would sit back, work out how the game was currently structured, and think about how it could be improved to solve my problem. If you never reach that stage, then you never needed OO.
This forum is more for the creative aspects of Game Design than the technical areas... this post is probably better suited to one Game Programming.

As for your questions:

1) I suppose it really depends on the complexity of these two modules, but I would be inclined to say no. Two seperate, smallish classes with well specced out functionality and decent interfaces are always going to be be easier to maintain and expand than a single monolithic class with a very wide range of responsibilities.

2) They can be kept seperate, and doing so is probably a good idea for purposes of reusability and extensibility blah blah blah. That said, nine times out of ten 'reusable' code never gets reused anyway, so you're probably better off getting it done the way that seems natural now and refactoring later if you need to.

3) You could look into using some sort of scenegraph. Each state object could manage a scenegraph containing actors and/or gui elements, and pass that on to the renderer/gui object for processing as necessary.

4) As an alternative, it might make sense to keep the gamestates themselves in some sort of tree like structure, which would literally reflect the hierarchy of gamestates in code.
I ran into the same problem time and time again. Every time i started my project from scratch, improved the design, and started coding until i reached the point of spaghetti-code again. But in this way, i got a bit further with every try. And more importantly, i was learning to design better.

In general, i would say to divide a system in subsystems, that only have a single class as an interface to that whole subsystem (possibly with dll's). The subsystems should know as few as possible of each other. Then the Application can connect those subsystems to make them communicate.

I picked up a book on design patterns some years ago, and this helped me a great deal. What design patterns teaches is how to decouple classes. in that way you can just change the implementation of one subsystem, without affecting the rest of the program. The trick is when you have two classes that need to know about each other, use an interface for doing the communication.

What i did for player input is using a messaging system. It's generally better to send a message to a receiver than having the receiver poll the input-object all the time. Now i call Update() on the input function every game-loop. When it detects changes in input, it sends a message to every object that registered for receiving messages from the input-object.

How i implemented this was by making an interface called IMessageHandler. This interface has a pure virtual function called HandleMessage().
I also have a class called Messenger, that has functionality built in to Register and unregister listeners(Messagehandlers) and send messages to them.

You can elaborate on this in different ways. A message queue, delayed messages, a global MessageRouter, a notify function to control when the message gets sent, whatever suits your needs. Also note that an object can be both a IMessageHandler and a Messenger.

This concept can of course be used for many other things than just user input and it does great work in decoupling the different systems.
I wrote a multi-threaded server to control modelrailroads. It heavily relies on this messaging system and is very easy to maintain, reuse, and expand. Mainly because of the messaging system.

just some hints for your input-system, by no means a complete answer.
Jurgen.
First off, I am no expert, and the following is opinion. I can't guarantee my advice is good, or te resultant code will be better than yours. Further, this seems like a good enough setup. It should allow you to at least get something working, long enough to see what it's limitations are, and perhaps design around those in the next try [as what Jurgen describes is pretty common I think]


1- I'd think of it the other way. The gui parts are just specifics of the renderer.

2- To a degree. The actual input handling you probably want to have seperate, so you can work on it or change as needed, but the actual bindings that say what input does what is [imo] best bound with each GUI object.

3- Meh. I've never been a big fan of such wholy abstract 'gamestate' stacks. To me, you only really ever have one game state for most games. What you do have are various menu states, input bindings, rendering methods... Those can be handled by the renderer usually.

4- Depends on the game. Personally, I think the direct control is fine, if fairly difficult for others to see/maintain/follow.

Just a few thoughts, but really you just want to stick with whatever works, untill it no longer works, otherwise you will get bogged-down in the details (like me).

I don't really like the way you have split things up, so I will present an alternative layout:

Renderer:
Abstracts the basic rendering tasks, creating and destroying the window, drawing graphic primitives to screen (text, lines, polygons, etc.), and managing transforms (esp. if the game is 3D).

GUI:
Manages a hierarchy of GuiElements (windows, buttons, textboxes, viewports into the actual game world, etc.), and the individual GuiElements call the Renderer to do the actual drawing.

Scene:
Holds a hierarchy of the game objects (ships, planets, missiles, particle effects, etc.), which each call the Renderer to do there own drawing.

Input:
Polls the OS for input, then sends them as messages to each object that has register to recieve them (could be game states, gui elements, game objects).

Game States:
The individual game states are not managed, but rather manage each other, i.e. in your main() funtion you create and run a LoadingScreenState, which does all the loading, then creates and runs a MainMenuState, which in turn creates and runs an InGameState, etc.
Each state calls in order, Input.Update(), Scene.Update(), GUI.Update(), etc.


Of course this system has to keep expanding, eventually you will nead an AudioRenderer, to deal with the low level audio hardware, a PhysicsManager, an AIManager, etc.

Anyway, I have gone on for long enough, hopefully it gives some food for thought,

SwiftCoder

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

One thing I've found to be useful in my own experience is to remove logic from your entities. Entities should just be data, the logic that controls them can be implemented in 'Command Classes' that take a reference to the entity they're supposed to be working from. As soon as you start to build logic into entities, they become bound to certain systems and lose their reusability. Following this logic, you could also implement rendering in a similar fashion. Building rendering instructions into a system ties it directly to that renderer; unless you plan on using this render interface in all of your projects this quickly becomes messy. Any changes to your render interface mean that you need to start hunting down through all of your code to locate the render calls and change them. Messy.

1) Your GUI shouldn't care how it's rendered. It should keep enough state information to describe it's appearance, but shouldn't have any specific calls to a rendering sub system. Render it by creating a GUIRenderer class that takes your GUI's appearance and turns it into render calls for your renderer.

2) Have your GUI system take input from an input control relay. This relay could then be filled by the player's input system, a network device or even a saved stream of commands.

3) Again, separate logical updates into specific command modules that interact with the objects they're supposed to control. Having a huge if/then/else/if/then state logic will do you no favours.

4) You probably do need some form of controller to handle the transition between states, yes.
Quote:Original post by evolutional
1) Your GUI shouldn't care how it's rendered. It should keep enough state information to describe it's appearance, but shouldn't have any specific calls to a rendering sub system. Render it by creating a GUIRenderer class that takes your GUI's appearance and turns it into render calls for your renderer.
Very good point, scratch my recomendation in my last post.

Quote:2) Have your GUI system take input from an input control relay. This relay could then be filled by the player's input system, a network device or even a saved stream of commands.

That is an brilliant idea, and not just for games; in fact, if the mouse pointer is considered part of the GUI, it makes full tutorial-style playback of GUI actions trivial (and then if this is extended to the rest of the game's input: full replay support with no additional code).
Thanks evolutional.

Quote:4) You probably do need some form of controller to handle the transition between states, yes.

Just my opinion, but most of this type of system I have seen, seems to over complicate the issue.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by Argus2
Well, my advice would be to do it the way you feel comfortable doing it. Don't try to do things in an OO way just to make it OO.


Well, for what it's worth, I'm not really planning things this way just for the sake of making it object-oriented. I've always seen the advantages of designing this way, my biggest problem is that most of my attempts end up unmanageable before long. I can't count how many projects I've abandoned in the past because I've felt like maintaining the code was too big of a pain. I'm hoping better planning at the start of this particular project will allow me to avoid that this time, or at least make it easier for me to revise things later on.

Quote:Original post by evolutional
2) Have your GUI system take input from an input control relay. This relay could then be filled by the player's input system, a network device or even a saved stream of commands.


This is a great idea and not at all something I would have thought to do, especially not at first. Like swiftcoder pointed out, this'll probably work great for a tutorial system, something I was trying to decide how to implement.

I didn't feel like making (another) huge post to respond to everyone, but I've read all of the posts in this thread and I'll definitely be reworking a lot of my plan based on these ideas. I really appreciate all of the advice.
Quote:Original post by swiftcoder
Quote:4) You probably do need some form of controller to handle the transition between states, yes.

Just my opinion, but most of this type of system I have seen, seems to over complicate the issue.


True, but embedding state change logic into the states themselves is likely to be a bad idea (TM). I think this would be a good area for me to look at next; we want a system that can switch between states without the need for a monolithic controller and/or embedded state change logic. An interesting task!

This topic is closed to new replies.

Advertisement