Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


How to structure a game in OOP


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
20 replies to this topic

#1 Tispe   Members   -  Reputation: 1039

Like
1Likes
Like

Posted 23 May 2013 - 01:13 AM

Hi, I have rewritten my project several times to adapt to new game structures. I do this to conform to rules such as, no static or global variables, no singleton patterns and the single responsibility principle.

 

The application consist of class objects. I often find myself creating a "super" class to only make one instance of it. Such as a Renderer class. This class handles the window, resolution, fullscreen toggle and draws the scene.

 

Another class is the Asset Manager, one instance is created and it handles loading files and preparing them for later use. It keeps an array of all assets. Since the Asset Manager needs the device to create resources (VB, IB, Textures) in the managed pool, I keep a private pointer to the device in Asset Class and make a copy in the constructor. This cross object dependancy is to conform to the "rules", maybe there is a better solution?

 

The Entity class has one instance, it has a list of (pointers to) Player objects (struct). These player structs contain information such as what Skeleton to use, what Mesh to use, and character variables.

 

When rendering I pass the list of Entities to the renderer which looks up the Asset Manager for the resources and draws them as described in the Player object. 

 

There are also other classes such as the animator and gui but I kept those out to keep it short. Is this a good game structure? What should I pay attention to, and are there any other game structures I should consider?

 

The pseudo-code:

main(){
InputOutputClass IO;
DeviceClass Renderer;
AssetClass Assets(Renderer.GetDevice());
EntityClass Entities;
GuiClass Gui(Renderer.GetDevice());

MainLoop(Renderer, Assets, Entities, IO, Gui);

}

Edited by Tispe, 23 May 2013 - 01:15 AM.


Sponsor:

#2 Steve_Segreto   Crossbones+   -  Reputation: 1551

Like
9Likes
Like

Posted 23 May 2013 - 01:16 AM

I do this to conform to rules such as, no static or global variables, no singleton patterns and the single responsibility principle.

 

Sorry that has got to be the absolute worst reason to rewrite your code. The end user doesn't care about any of those things, only results.

 

Look - forget the "rules" and begin to think only about results. I'm going to be brutally honest with you - nobody else will ever look at your source code for whatever game you're making. If you're lucky they *might* look at the current version of the game as a finished product running on a platform. They won't know or care if you followed the "rules".

 

Anyway you're design is pathologically simple and uses far too much syntactic sugar to do far too little. Please think about the kind of game and features you need and design your classes around that. Are you making a 3d game with many 3d actors? Can they all share the same skeleton in a scene graph class? Can they share keyframe animations, what about textures, etc.

 

Ultimately what should force you to scrap a design and start over is if it doesn't scale in terms of size or speed (or both).


Edited by Steve_Segreto, 23 May 2013 - 01:21 AM.


#3 Buster2000   Members   -  Reputation: 1775

Like
3Likes
Like

Posted 23 May 2013 - 01:30 AM

Sorry that has got to be the absolute worst reason to rewrite your code

 

Totally agree 100%.  The only reason you should rewrite this stuff is if you find it stops you from extending your game or is inflexible in some way.   Refactoring just to avoid other peoples opinions of what is bad software engineering is just a time suck when what you should be concentrating on is getting things done.

Sure some people don't like statics, globels or singletons but that doesn't mean they should be avoided entirely.  Each has its use cases.

 



#4 Felix Ungman   Members   -  Reputation: 1065

Like
0Likes
Like

Posted 23 May 2013 - 01:41 AM

I often find myself creating a "super" class to only make one instance of it. 

 

Premature abstraction is the object-oriented root of all evil.

 

Use a good refactoring tool, go over the code and try to remove all unnecessary abstractions. Then, go over the code again and see if you find code duplications. Those are the candidate points for introducing new abstractions. Repeat this process a few times and you should end up with a much cleaner framework.


openwar  - the real-time tactical war-game platform


#5 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 23 May 2013 - 02:26 AM

But if I don't bundle things into an object, I would have to either keep them global or have massive amounts of arguments to pass around, creating a huge web of dependancies. Is it bad to make a class if only one instance will be made from it? Surely classes are not only for abstraction?



#6 Buster2000   Members   -  Reputation: 1775

Like
0Likes
Like

Posted 23 May 2013 - 04:56 AM

Is this class just a pod?
couldn't you just have a struct with a global instance?



#7 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 23 May 2013 - 05:43 AM

The render class holds everything from device to shaders. It's the glue between my assets and the Graphics API. And in this class I have functions like 'ChangeResolution(MyStruct Params)' and Render(MyClass *Entities).

 

I read that the only difference between struct and class is that members are public in struct by default and private in classes by default. So changing they key word 'class' to 'struct' has no meaning.

 

Bundling stuff in classes or structs is the same, but is it proper to use it in this case?



#8 Felix Ungman   Members   -  Reputation: 1065

Like
1Likes
Like

Posted 23 May 2013 - 06:24 AM

Yes, it's good practice to put your state inside of objects, even if there's only one instance of the class. The question is of course where to put each individual variable. If you're not satisfied with the architecture but you don't know what to do, one approach is to just shuffle the code around and see what happens. Experiment with merging and splitting classes and functions. Look for new names, and try to make them as concrete as possible. Poke around and see if you find any redundant duplications or code smells. It usually takes a few iterations before the architecture settles.


openwar  - the real-time tactical war-game platform


#9 metsfan   Members   -  Reputation: 654

Like
0Likes
Like

Posted 24 May 2013 - 10:52 AM

It sounds to me like you have a lot of questions but few answers.  If you want my opinion, before you start coding, take a step back and draw your application on paper.  Make sure you understand the interactions between the different subsystems, and how you want them to communicate with eachother.  Get a real understanding of how you WANT the system to work, then you can refine your design.  However, if you just jump in and start making a mess, and hope to figure it out as you go along, you're going to run into problems, and just make your code messier.

 

These are some things I like to avoid in my design:

  • Global state.  There's just no need.  Any situation where a Singleton is a solution, there is often a better solution that doesn't use a Singleton.  This is an opinion I formed after using Singletons for a long time, and gradually realizing how evil they are the hard way.  
  • Avoid global event systems.  They are a nightmare to debug.  Prefer a callback/delegate architecture.  Another thing I had to learn the hard way.
  • Make sure your subsystems are compartmentalized.  For instance, there is no reason for a sound object to contain a pointer to a physics object.  Keep your systems separate and join them using a higher level interface.
  • Make your APIs easy to understand, and difficult to use incorrectly.

Good luck.



#10 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 24 May 2013 - 12:11 PM

  • For instance, there is no reason for a sound object to contain a pointer to a physics object.  Keep your systems separate and join them using a higher level interface.

 

Well, this is where it gets tricky for me. The APIs for sound and graphics are different systems. I can encapsulate them in my own classes and simplify the interfaces for my use. But for example there has to be a place in the code where a sound is triggered when the animation passes a point. We are now far down a method in the animation class and we discover that we need to trigger a sound. Where is the pointer to the sound interface when we are in the animation system?

 

Globals?

Singletons?

Coupling by having a pointer to the sound system as a member in the animation class?

 

Having a higher level interface that calls the DoAnimation() method, I need return values in order to call DoSound() afterwards. But I just shuffle the burden up the food chain. Now I have to code a ton of different states that must be passed and returned and bloat the calling code, making it more complex.



#11 metsfan   Members   -  Reputation: 654

Like
1Likes
Like

Posted 24 May 2013 - 07:45 PM

 

  • For instance, there is no reason for a sound object to contain a pointer to a physics object. Keep your systems separate and join them using a higher level interface.

Well, this is where it gets tricky for me. The APIs for sound and graphics are different systems. I can encapsulate them in my own classes and simplify the interfaces for my use. But for example there has to be a place in the code where a sound is triggered when the animation passes a point. We are now far down a method in the animation class and we discover that we need to trigger a sound. Where is the pointer to the sound interface when we are in the animation system?

Globals?
Singletons?
Coupling by having a pointer to the sound system as a member in the animation class?

Having a higher level interface that calls the DoAnimation() method, I need return values in order to call DoSound() afterwards. But I just shuffle the burden up the food chain. Now I have to code a ton of different states that must be passed and returned and bloat the calling code, making it more complex.

The way I handle this is with callbacks. For instance, let's take a Rock object as an example. A Rock object falls to the ground, and when it hits the ground, it should trigger a "thump" sound.

 

typedef std::function<void()> DelegateFunction; 

class Rock {  
  Sound mSound;  
  Animation mFallAnimation;    
  Rock()  
  {     
    DelegateFunction delegate = std::bind(&Rock::FallAnimationEnded, *this);     
    mFallAninmation.addDelegate(delegate);     
    mFallAnimation.Start();  
  }  

  void FallAnimationEnded()  
  {     
    mSound.Play();  
  }
};

class Animation
{   
  std::vector<DelegateFunction> mDelegates;  
  void AddDelegate(DelegateFunction func) { mDelegates.push_back(func); }  

  void Start(); // Begins a timer, or adds it to an animation manager, however you want to do this.  

  void Update() // Called every animation update  
  {    
    if(animationStillRunning) 
    {      
      this->UpdateAnimationStuff();    
    } else {     
      for(int i = 0; i < mDelegates.size(); i++) {         
        mDelegates[i]();      
      }  
    }
  }
};

 

 

So as you can see, we have no coupling between our Sound system or Animation system. The Rock is created, it will attach its FallAnimationEnded member function to the animation's delegate list, and when the animation is done, it will call all registered delegates. Obviously this code is far from complete and I'm not even sure if all my syntax is correct, but the idea should be clear.


Edited by metsfan, 24 May 2013 - 07:48 PM.


#12 Tispe   Members   -  Reputation: 1039

Like
1Likes
Like

Posted 06 June 2013 - 12:10 PM

Gonna bump this, I would like some feedback on a structure I am working on.

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	Dx9Device DeviceInterface;							//Wrapper Interfaces to hardware APIs
	Dx9Audio AudioInterface;
	Network NetworkInterface;
	WinInput InputInterface;

/*
Some code
*/

	Assets AssetsManager;								//Game managers
	Entities EntitiesManager;
	Area AreaManager;
	Audio AudioManager;

	MainLoop(&DeviceInterface, &AudioInterface, &NetworkInterface, &InputInterface, &AssetsManager, &EntitiesManager, &AreaManager, &AudioManager);

	return 0;
}

 

void MainLoop(Dx9Device* pDeviceInterface, Dx9Audio* pAudioInterface, Network* pNetworkInterface, WinInput* pInputInterface, Assets* pAssetsManager, Entities* pEntitiesManager, Area* pAreaManager, Audio* pAudioManager)
{
	ToLogicData Data;
	GameData Gamestate;

	while(TickMessages())
	{

		GameInput(pNetworkInterface, pInputInterface, &Data);													//Get Input and output pData
		GameLogic(pAudioManager, pAreaManager, pEntitiesManager, pAssetsManager, &Data, &Gamestate);			//Get pData, update and output game state
		GameOutput(pDeviceInterface, pAudioInterface, pNetworkInterface, &Gamestate);							//Render game state, play audio from state and networking
	}
}

 

Is this a good way for passing interfaces and data around?


Edited by Tispe, 06 June 2013 - 12:13 PM.


#13 L. Spiro   Crossbones+   -  Reputation: 14215

Like
3Likes
Like

Posted 06 June 2013 - 06:46 PM

Whenever you need to pass a ton of parameters it is better to make a structure with those parameters as its members and pass a reference to that structure.

 

 

 

L. Spiro


It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#14 Alpha_ProgDes   Crossbones+   -  Reputation: 4692

Like
1Likes
Like

Posted 06 June 2013 - 08:39 PM


Sorry that has got to be the absolute worst reason to rewrite your code

 
Totally agree 100%.  The only reason you should rewrite this stuff is if you find it stops you from extending your game or is inflexible in some way.   Refactoring just to avoid other peoples opinions of what is bad software engineering is just a time suck when what you should be concentrating on is getting things done.

Sure some people don't like statics, globels or singletons but that doesn't mean they should be avoided entirely.  Each has its use cases.


Let's back up. If he's already finished writing the game and wants to go back and refactor it, then I don't see the harm or the waste of time. Now, if he's hasn't finished writing the game yet, then yes it's more important to finish writing the game then worry about good software engineering. You'll get that with experience. But I wouldn't call the effort a complete waste of time.
Beginner in Game Development? Read here.
 
Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley
 
If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts Posted Image
 
Spoiler

#15 metsfan   Members   -  Reputation: 654

Like
0Likes
Like

Posted 06 June 2013 - 09:24 PM

Whenever you need to pass a ton of parameters it is better to make a structure with those parameters as its members and pass a reference to that structure.

 

 

 

L. Spiro

 

Another option for this particular situation is the Service Locator Pattern (http://en.wikipedia.org/wiki/Service_locator_pattern), though there are some who consider this an anti-pattern. 



#16 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 07 June 2013 - 05:13 AM

Whenever you need to pass a ton of parameters it is better to make a structure with those parameters as its members and pass a reference to that structure.

 

 

 

L. Spiro

 

Why not pass by address, that way you can pass a NULL?



#17 Alpha_ProgDes   Crossbones+   -  Reputation: 4692

Like
0Likes
Like

Posted 07 June 2013 - 06:38 AM

Whenever you need to pass a ton of parameters it is better to make a structure with those parameters as its members and pass a reference to that structure.

 

 

 

L. Spiro

 

Why not pass by address, that way you can pass a NULL?

 

Well if you pass the structure by reference, then you only have to pass one NULL, as opposed to 10 or 15.


Beginner in Game Development? Read here.
 
Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley
 
If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts Posted Image
 
Spoiler

#18 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 07 June 2013 - 06:54 AM

I ment if I place everything in a single structure, why pass that structure as reference instead of address.



#19 Paradigm Shifter   Crossbones+   -  Reputation: 5433

Like
0Likes
Like

Posted 07 June 2013 - 10:42 AM

Because the reference to the struct has replaced a load of required parameters, so not passing one would be pointless...


"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

#20 markr   Crossbones+   -  Reputation: 1653

Like
0Likes
Like

Posted 07 June 2013 - 01:30 PM

Really, do the simplest thing that could possibly work.

 

I tend to end up with some big hairy object called "GameManager" or something, which I create as a local in main() or somewhere fairly close, pass by reference between a small number of high-level functions, which then call the more specific things.

 

This "GameManager" tends to be a composition of a number of more specific managers which handle specific aspects.

 

I often seem to have some "ObjectManager" or something which contains lists of various objects which it manages, and deals with calling the tick functions, rendering functions (possibly passing a renderer object by reference).






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS