After some general pointers on OO game design

Started by
18 comments, last by chairthrower 15 years, 11 months ago
Quote:Original post by chairthrower
The authors of the GoF book, design patterns famously state, “prefer composition over inheritance”. however it never seems to be noted ( at least its difficult to simply reconcile) that this flies in the face of 2 of the 4 most commonly cited attributes of OO design - namely polymorphism, and inheritance ( along with encapsulation and modularity).

AFAIK, the GoF make that statement to discourage the over-use of inheritance when it is not actually required (hence the use of the soft wording "prefer").

Inheritance is used to model the "is a" relationship between two entities (and to enable polymorphism).
If you don't explicitly need an "is a" relationship, then you also don't need polymorphism *in that particular case*, which means that inheritance is the wrong tool *in that particular case*.

For example, lets say we're writing a queue class, which we want to implement by reusing the existing std::vector class. We can do this either by composition or by inheritance.
Both of these approaches work, so they are both "right". However, one uses a model where the "queue is a vector", which is obviously incorrect ("bad") as the queue does not contain all of the functionality which a vector has.
//Using inheritance: "bad and right"template<class T>class MyQueue : private std::vector<T>{public:  void push( const T& in )  {    push_back(o);  }  bool pop( T& out )  {    if( empty() )      return false;    out = front();    pop_front();    return true;  }};//Using composition: "good and right"template<class T>class MyQueue{public:  void push( const T& in )  {    data.push_back(o);  }  bool pop( T& out )  {    if( data.empty() )      return false;    out = data.front();    data.pop_front();    return true;  }private:  std::vector<T> data;};


Quote:Maybe the gof book is merely an extremely well disguised and covert attack on OO theory - i dont know. i guess i feel that with basic contradictions like this, it is really hard to make generalisations about what might be considered good oo design or how to go about learning what would be called 'best practice'.

It's not a contradiction, just generalised advice. It's only contradictory if you misinterpret it to mean that you shouldn't use inheritance ever (instead of meaning that you should only use inheritance when it is actually useful).
Advertisement
Quote:Original post by chairthrower
I have a suspicion that the whole OO analyis and design thing is presently in a really confused state and that there is something of a lack of real critical analysis going on.


That and it's a relatively novel arena within an already relatively novel field.

Quote:
the authors of the GoF book, design patterns famously state, “prefer composition over inheritance”. however it never seems to be noted ( at least its difficult to simply reconcile) that this flies in the face of 2 of the 4 most commonly cited attributes of OO design - namely polymorphism, and inheritance ( along with encapsulation and modularity).


Except the 5 prime elements of object oriented programming (Single Responsibility, Liskov Substitution, Open-Closed principle, Interface Segregation, Dependency Inversion) don't explicitly rely upon inheritance.

Indeed, deep inheritance trees will naturally tend towards violating Interface Segregation (as the base interface will grow to encompass anything the inheritors might do), or the Liskov Substitution principle (you do type-checks on base classes to see if this is a specific derivation).

Quote:
for what its worth, I agree that polymorphism and inheritance (eg factoring base behavours into objects and then deriving) is a really tight coupling...


See Dependency Inversion link. Variable behaviors are best abstracted into a strategy or similar pattern. In general though, if you find things tightly coupled then you're not abstracting enough.

If you have two things that know about each other, but shouldn't then you need a 3rd object that knows of both so that they may freely be ignorant of each other.

Quote:
The design patterns books and the threads on compenent based development here on gamedev always challenge my understanding and help me to think about problems in different ways. also i can totally relate to the above post - right now i am involved in a db project which involves a lot of table/data normalisation and i catch myself wishing that i had a better grasp on relational theory - since there do appear to be interesting implications for general programming.


Indeed. I found some of the database normalization tidbits wonderfully applicable to OO design. It's key though to keep them a bit separate for the most part. Data normalization is for data. OOP is (or at least should be) more than just bundles of data.

Woah, I can see this thread rapidly spiralling off-topic into a general discussion about inheritance and OO design. I realise my original question was quite vague and wide-ranging, but I'm after some advice, specifically, on a decent structure of what should do what for my game. I sort of made a start on a render manager class. Am I heading in the right direction?

/* i've only included the members that show the general design of my renderingsystem*///CBall.h:class CBall{public:	CBall(float X, float Y, float rotation, BALLTYPES type, CRenderManager* renderer);/*constructs object and adds it to the render manager's list of ballsto draw*/	~CBall(); //destroys and removes from list of balls to draw		friend CRenderManager; /*so the render manager can access its members toknow where to draw it*/protected://there are more data members, these are the ones related to rendering	D3DXVECTOR2     mPosition;	float		mRotation;	D3DXVECTOR3	mCenter;	RECT		mSourceRect;	CRenderManager* mRenderer; /*pointer to the renderer. this is needed sothat the ball can (in its destructor) remove itself from the list of objects tobe drawn*/	int		mRenderID; /*where it sits in the render manager'svector of balls*/};//CRenderManager.h:class CRenderManager{public:	CRenderManager(); /*creates a D3DXSprite interface and loads alltextures*/	int AddBallPointer(CBall* ball); /*adds a ball to the list of things todraw*/	void DeleteBallPointer(int element); //the opposite	void Render(); //duh	private:	//spriting interface	ID3DXSprite* mSprite;	//texture objects	IDirect3DTexture9* mTexBalls;        //more to come when I have more stuff to draw	//lists of objects to draw	vector &lt;CBall*&gt; mpBalls; /*note that this is a list of pointers toCBalls. So the balls themselves aren't members of the render manager. The ballsand manager exist on the same scope level, hence the manager being a friend ofCBall.*/};//CRenderManager.cppint CRenderManager::AddBallPointer(CBall* pball){        /*CBall calls this from its constructor, using 'this' as the argument.So a pointer to the ball is added to the list, and the ball is then told itsindex within the vector as the return value, so it can delete itself later on*/	mpBalls.push_back(pball);	return mpBalls.size() - 1;}void CRenderManager::DeleteBallPointer(int element){        /*Cball calls this from its destructor, passing the number obtained fromAddBallPointer as the parameter. only the pointer to the ball is deleted, nottha ball itself. */	mpBalls.erase(mpBalls.begin() + element);}/*CRenderManager::Render is fairly self explanatory. It just iterates over thevector, mpBalls, obtaining each ball's properties to use as arguments to D3DXfunctions.*/

Things will become more generalised than this once I have more objects. Ie- I won't have AddXPointer() and DeleteXPointer() for every type of game object. This is just a prototype.

cheers,
metal

[Edited by - metalmidget on May 13, 2008 5:43:48 AM]
Perhaps something like the following may help;

class Game: // does initialisation of other classes, and holds game loop code   HAS:   class Input // deals with all the input devices you need for the project (keyboard, etc.)   class Render // deals with setting up the screen, and the rendering   class Physics // three guesses :)   class Scenegraph // holds the data for the scene   class Resource // all resources are given keys to refer to them by

the class that everything revolves around here would be the scenegraph, where each node could be given a pointer to a physics "body" and render "model", which would be classes used by (but not inherited from) the Physics and Render classes.

Notice I said that the nodes have pointers to the body and model. This keeps the different parts of the game separate, but allow you to deal with each game object (whether it is a car in a racing game, or an monster in an RPG) in a single location.
Quote:Original post by webwraith
Perhaps something like the following may help;

class Game: // does initialisation of other classes, and holds game loop code   HAS:   class Input // deals with all the input devices you need for the project (keyboard, etc.)   class Render // deals with setting up the screen, and the rendering   class Physics // three guesses :)   class Scenegraph // holds the data for the scene   class Resource // all resources are given keys to refer to them by

the class that everything revolves around here would be the scenegraph, where each node could be given a pointer to a physics "body" and render "model", which would be classes used by (but not inherited from) the Physics and Render classes.

Notice I said that the nodes have pointers to the body and model. This keeps the different parts of the game separate, but allow you to deal with each game object (whether it is a car in a racing game, or an monster in an RPG) in a single location.

(I'm assuming that a node is some sort of thing in my game)
So would that mean that for every physical object (node) in my game (as in, each ball, spring, etc), there will be 3 objects in code? The first in the scenegraph, which has pointers to the other 2 which are in the physics and render classes?
Won't there be some overlap of data there? For instance, the objects' size and position are important to Physics for collision, and Render for graphics. Do they both maintan their own set of data for the object? Or is all the data for the object stored in classes contained within SceneGraph, and that data is merely checked and operated on by Physics, and used by Render?


Oh and what do you mean by a resource? Sounds, textures, etc? What about level data loaded from a file- is that a resource?

How would this structure work across the life of the app? Would it be something like:
Game would call functions of Scenegraph to create game objects. The constructors of these objects would then call functions in Render and Physics which create the corresponding objects in those classes.
<Something else, see questions later>
When the objects are no longer needed, a function in Scenegraph is called to destroy the objects (called perhaps from Game or Physics). The destructor of these objects would then call destroy functions in Render and Physics to delete the corresponding objects.
I'm not sure what goes in the middle though, in terms of the game loop. Does the game loop call a function in Scenegraph which somehow calls the update() and draw() functions in Physics and Render? Does the game loop call all 3 of those functions?

Sorry- I know I'm asking a ton of questions at once, but I'm determined to get this right!

cheers,
metal
A resource is anything you would load from a file, or anything that isn't hard-coded data.

Nodes are specific classes that the scenegraph uses to store relationships between your game objects.

Lets say you're creating a small, 2D ship game. The game is played on a single screen.

Each ship could move about on the board anywhere there wasn't another ship.

Your resources would be something like;
@ ship image - this holds all the images of the ship in the different directions.
@ background image - this is an image of the sea
@ ship info - if you have different ships with different abilities, you'd need something like this for each one
@ config info - holds info like the screen resolution and colour depth, etc.

You load these in an Init()-style function in the Game class. Items like the config file may also be required by the Render class, so initialise your other classes here as well.

During your game loop, your player creates a new ship. You add a new "ship" node to the scenegraph, with its parent set to the root of the graph (there's no need to do otherwise just yet, I'll give some examples later),create a new renderable object with the previously loaded image and tell the new node about it, create a new physics "body" and tell the new node about it, and set the nodes starting position to whatever. The Physics class needs to know about the node, as does the Render class.

During the Update() function, input data is retrieved, the Physics class updates, both of which could change the position data in the node. Note that the node itself hides the Render class from having to know about the Physics class.

Then, during the Draw() function, the Render class has a list of objects to draw, it can get their specific positions from the Nodes they belong to, and can be stored in a render-effective way (by lumping everything that needs a certain shader together, so they all get drawn at the same time, and so on).

Then, once the game is over, you would just call each of the classes Cleanup() functions, which would destroy their lists of objects.


Doing things like this gives all the different parts of the program a universal way to communicate(the nodes in the scenegraph class for actual game objects, and the resources for the data that stays the same between games), and they can store their own information in a decent way for whatever job they need to do.

Examples for giving nodes parents, would be (and remember, this is just an example!) having a glowing symbol that rotates around your characters head to show their status in an RPG.

At least that's just something I came up with, but it sounds like you have a fair idea of what I meant anyway.

As you can see, updating the parts in my example is done by calling an Update() command in the Physics class, and reading in input.

Technically there would be three objects in code, as you would need to store the info for the ship (or ball as per your example) in the correct system (speed and mass in Physics, the image or model in Render), and the node would bring the different data together.
...
Brilliant.
That's exactly the kind of stuff I needed to know. I'll need to read it over a few times to let it sink in properly, but I think I get the basic gist of it.
I'm just a little unsure of exactly what you mean by a node? How is it different to a game object (eg ship, ball, bullet etc), and what do you mean by its parent?

Thanks a lot,
metal

EDIT: If you're still online and reply straight away, I probably won't read it until morning. I'm aware it's only early afternoon in the UK, so it'll probably be mid-late evening for you until I get a chance to have a look.
basically, in the examples I've given a "node" is just what you called an "entity" in your original post. The only possible difference between the two was that in your case, you were probably thinking about keeping all the data for each game object together, while I split it up, and provided an object that kept a reference to the data so it didn't have to be lumped around all over the place.Besides that, I just call your entity a node, due to the shape of the scenegraph being a tree container. Well, in my case, it's technically a map, but anyway... :P
Quote:Original post by chairthrower
The design patterns books and the threads on compenent based development here on gamedev always challenge my understanding and help me to think about problems in different ways...


Are there any links on other component based development threads?

I love these kind of mind-enhancing discussions.

Quote:Are there any links on other component based development threads?


If you search on combinations of 'entity', 'component' 'subsystem' there are quite a few threads. I found these two threads to have a lot of great ideas.

Sneftel's Outboard component-based entity system architecture - although i think i might have read somewhere else that he has since refined the approach presented here

and this post, Entity System question by Aeroz and the following discussion

This topic is closed to new replies.

Advertisement