🔒 Locked
Sample OO simple game.
Advertisement
Original Post
Hello all, I look for a simple game written in OO methodology. To improve my understanding of this method I want to write a simple game (like Boulder Dash i.e), but I dont know how to do it in proper way. I want to separate logic and graphic, using interfaces etc. Maybe sample code or UML diagrams help me to understand this concept. Or maybe we discuss problem with this type of games here. best regards, tlucz
Yes, but MVC is general concept. Problems with designing are on every layer. Lets take only Model and Boulder Dash game (its my favorote!) http://en.wikipedia.org/wiki/Boulder_Dash
How should I hold objects on board. I have 3 proposal:
1. Two dimensial array (which represent board) of objects (player, rocks, diamonds fly etc.)
2. Collection of objects. Every object hold its own position on board.
3. Combination of above concept. Static objects(dirt, wall etc..) hold in array, and dynamic (movable) objects in collection.
Every of these approach have some disadvantage. You must remember that in every game turn (update) you must calculate new objects position.
What do you think about these approaches?
best regards
tlucz
How should I hold objects on board. I have 3 proposal:
1. Two dimensial array (which represent board) of objects (player, rocks, diamonds fly etc.)
2. Collection of objects. Every object hold its own position on board.
3. Combination of above concept. Static objects(dirt, wall etc..) hold in array, and dynamic (movable) objects in collection.
Every of these approach have some disadvantage. You must remember that in every game turn (update) you must calculate new objects position.
What do you think about these approaches?
best regards
tlucz
It depends how sophisticated you want things I suppose (like do things jump from one cell to the next when they move or do they glide smoothly).
For me, option 3 sounds the most flexible, followed by option 1. Although I'd be going for smooth glidy movement [smile]
Those choices however aren't really anything to do with OO methodology. If you have a proper OO design then you should be able to encapsulate the underlying implementation by using an interface to abstract the functionality. Then you could freely chop and change between any of those options, provided you leave the interface alone then you won't break anything anywhere else.
Like I said, use the MVC pattern, you'd create a Map class, this is effectively the 'Model' from Model-View-Controller. You decide on a nice and easy to use interface, then you pick one of those 3 options and implement it to abide by that interface. If it does turn out that you chose the wrong option, then no matter, just change it; you won't have to mess around fixing every part of your code because the interface will not have changed.
For me, option 3 sounds the most flexible, followed by option 1. Although I'd be going for smooth glidy movement [smile]
Those choices however aren't really anything to do with OO methodology. If you have a proper OO design then you should be able to encapsulate the underlying implementation by using an interface to abstract the functionality. Then you could freely chop and change between any of those options, provided you leave the interface alone then you won't break anything anywhere else.
Like I said, use the MVC pattern, you'd create a Map class, this is effectively the 'Model' from Model-View-Controller. You decide on a nice and easy to use interface, then you pick one of those 3 options and implement it to abide by that interface. If it does turn out that you chose the wrong option, then no matter, just change it; you won't have to mess around fixing every part of your code because the interface will not have changed.
I think that when I design(implement) Model I needn't decide to how will graphics be displayed (smooth or not). When I prepare Model, I can use different View in future (like char mode, graphic 2D, 3D etc..) It would be really separation. So lets try do this with option 1.
class GameObject { };
class Player : GameObject { };
class Rock : GameObject { } ;
class Diamond : GameObject { } ;
class FireFly : GameObject { } ;
class World {
GameObjects map[40][20];
}
What about GameObjects' properties and interfaces? I think that each GameObject should request its move to World and World checks if Move is allowed then change GameObject position on map and reply to GameObjects. Am I right? Or different concepts are better?
[Edited by - tlucz on February 26, 2008 4:43:07 AM]
class GameObject { };
class Player : GameObject { };
class Rock : GameObject { } ;
class Diamond : GameObject { } ;
class FireFly : GameObject { } ;
class World {
GameObjects map[40][20];
}
What about GameObjects' properties and interfaces? I think that each GameObject should request its move to World and World checks if Move is allowed then change GameObject position on map and reply to GameObjects. Am I right? Or different concepts are better?
[Edited by - tlucz on February 26, 2008 4:43:07 AM]
You may want to think about what you can do with operator overloading, it's especially useful if the right operand isn't defined as const in the overload (that way you can modify it).
Example (in super short pseudo-code)
class refEntity;
class entityInst;
refEntity warlord;
entityInst LordOfDooDoo;
entityInst LordOfThePit;
// in your update code, note I chose the * operator, you could use anything
warlord * LordOfDooDoo;
warlord * LordOfThePit;
The operator overload would update the entity instances in accordance with their reference appropriate reference object. I do this for my general logic updates, AI updates using an AI class that's overloaded for the entity instance, adding/removing items to an entity's inventory, and also use it to update an entity against a packet object.
It really helps to make things fit better into loops and keeps the nitty gritty code with the objects so change propagates. It also helps to fight redundant data. Only real caveat is that you have to make sure every loop approach is using 1 to Many and not Many to 1.
Many to 1 and you have to constantly seek the object to update and what to update it against, 1 to many and you only periodically change what instances are being updated against while you're cycling through the instances.
==================================
Also, invest the time to develop a simple class that wraps a hash map using std string and containing pointers to a "tagProperty" base class which you then create derived classes for float, bool, int, double, string, w/e. You can reuse it all over the place and it using std::string as a key helps keep it versatile in C++ and can adapt to scripting needs. I've added an example of code I use for both the base class and a derived class (BOOL). Take note that the get type function of tagProperty is NOT virtual, for a reason (safe static casting without using static_cast).
Give your wrapper class some overloaded functions so you can do something like:
setProperty(std::string propertyName, float); Overloading for the second parameter. And stuff like getPropertyF(std::string propertyName); to get your data back.
struct tagPropertyType
{
enum List
{
NONE,
BOOL,
INT,
FLOAT,
STRING,
VECTOR,
MAT3x3,
MAT4x4,
PAIR
};
};
class tagProperty
{
protected:
std::string _name();
tagPropertyType::List _type;
public:
int getType() { return _type; }
virtual ~tagProperty() {}
};
class tagBoolProperty : public tagProperty
{
protected:
bool _value;
public:
tagBoolProperty()
{
_value = false;
_type = tagPropertyType::BOOL;
};
tagBoolProperty(bool value)
{
_value = value;
_type = tagPropertyType::BOOL;
};
bool get() { return _value; }
void set(bool value) { _value = value; }
};
class tagPropertySet
{
protected:
stdext::hash_map<std::string, tagProperty*, SDBMHasher> _properties;
stdext::hash_map<std::string, tagProperty*, SDBMHasher>::iterator _iter;
public:
~tagPropertySet();
void clear();
void setProperty(std::string name, std::string value);
void setProperty(std::string name, bool value);
void setProperty(std::string name, int value);
void setProperty(std::string name, float value);
std::string getPropertyS(std::string name);
bool getPropertyB(std::string name);
int getPropertyI(std::string name);
float getPropertyF(std::string name);
=========================================================
You've got to minimize your workload. A few general purpose utility classes and full use of the good parts of object orientation will cut out a huge amount of work and make it easier for you to track down your bugs and issues.
Golden Rule:: Object Oriented Design > Inheritance
Your quick example of classes is not a good start.
GameObject <- spatialObject <- drawnObject <- scenery <- rock
GameObject <- spatialObject <- drawnObject <- item <- diamond
GameObject <- spatialObject <- drawnObject <- entity <- firefly
GameObject <- spatialObject <- drawnObject <- entity <- player
..because, later when you have...
GameObject <- spatialObject <- hiddenObject <- ambientSound <- grasshoppers
..and..
GameObject <- nonspatialObject <- audioObject <- music
You could get yourself into trouble. Your root object should handle nothing more than identification and registration. Do not be afraid to have steps that do nothing impressive on their own.
But, there are a dozen ways to do things, and none is wrong or right (some are better than others though).
Think hard, code lots, and never be afraid to chop something out and start it over, unless your working on rendering code, then anything you do should 'work' after 24 hours of coding time, if not chop it out try something new, when it works make it better.
***By chop it out, I mean "Backup what you've got, then start over in your working copy"
Example (in super short pseudo-code)
class refEntity;
class entityInst;
refEntity warlord;
entityInst LordOfDooDoo;
entityInst LordOfThePit;
// in your update code, note I chose the * operator, you could use anything
warlord * LordOfDooDoo;
warlord * LordOfThePit;
The operator overload would update the entity instances in accordance with their reference appropriate reference object. I do this for my general logic updates, AI updates using an AI class that's overloaded for the entity instance, adding/removing items to an entity's inventory, and also use it to update an entity against a packet object.
It really helps to make things fit better into loops and keeps the nitty gritty code with the objects so change propagates. It also helps to fight redundant data. Only real caveat is that you have to make sure every loop approach is using 1 to Many and not Many to 1.
Many to 1 and you have to constantly seek the object to update and what to update it against, 1 to many and you only periodically change what instances are being updated against while you're cycling through the instances.
==================================
Also, invest the time to develop a simple class that wraps a hash map using std string and containing pointers to a "tagProperty" base class which you then create derived classes for float, bool, int, double, string, w/e. You can reuse it all over the place and it using std::string as a key helps keep it versatile in C++ and can adapt to scripting needs. I've added an example of code I use for both the base class and a derived class (BOOL). Take note that the get type function of tagProperty is NOT virtual, for a reason (safe static casting without using static_cast).
Give your wrapper class some overloaded functions so you can do something like:
setProperty(std::string propertyName, float); Overloading for the second parameter. And stuff like getPropertyF(std::string propertyName); to get your data back.
struct tagPropertyType
{
enum List
{
NONE,
BOOL,
INT,
FLOAT,
STRING,
VECTOR,
MAT3x3,
MAT4x4,
PAIR
};
};
class tagProperty
{
protected:
std::string _name();
tagPropertyType::List _type;
public:
int getType() { return _type; }
virtual ~tagProperty() {}
};
class tagBoolProperty : public tagProperty
{
protected:
bool _value;
public:
tagBoolProperty()
{
_value = false;
_type = tagPropertyType::BOOL;
};
tagBoolProperty(bool value)
{
_value = value;
_type = tagPropertyType::BOOL;
};
bool get() { return _value; }
void set(bool value) { _value = value; }
};
class tagPropertySet
{
protected:
stdext::hash_map<std::string, tagProperty*, SDBMHasher> _properties;
stdext::hash_map<std::string, tagProperty*, SDBMHasher>::iterator _iter;
public:
~tagPropertySet();
void clear();
void setProperty(std::string name, std::string value);
void setProperty(std::string name, bool value);
void setProperty(std::string name, int value);
void setProperty(std::string name, float value);
std::string getPropertyS(std::string name);
bool getPropertyB(std::string name);
int getPropertyI(std::string name);
float getPropertyF(std::string name);
=========================================================
You've got to minimize your workload. A few general purpose utility classes and full use of the good parts of object orientation will cut out a huge amount of work and make it easier for you to track down your bugs and issues.
Golden Rule:: Object Oriented Design > Inheritance
Your quick example of classes is not a good start.
GameObject <- spatialObject <- drawnObject <- scenery <- rock
GameObject <- spatialObject <- drawnObject <- item <- diamond
GameObject <- spatialObject <- drawnObject <- entity <- firefly
GameObject <- spatialObject <- drawnObject <- entity <- player
..because, later when you have...
GameObject <- spatialObject <- hiddenObject <- ambientSound <- grasshoppers
..and..
GameObject <- nonspatialObject <- audioObject <- music
You could get yourself into trouble. Your root object should handle nothing more than identification and registration. Do not be afraid to have steps that do nothing impressive on their own.
But, there are a dozen ways to do things, and none is wrong or right (some are better than others though).
Think hard, code lots, and never be afraid to chop something out and start it over, unless your working on rendering code, then anything you do should 'work' after 24 hours of coding time, if not chop it out try something new, when it works make it better.
***By chop it out, I mean "Backup what you've got, then start over in your working copy"
Quote:
Original post by AcidFaucet
You may want to think about what you can do with operator overloading, it's especially useful if the right operand isn't defined as const in the overload (that way you can modify it).
Actually, this is precisely the kind of usage of operator overloading that most designers would consider bad practice. It's also thoroughly unnecessary.
Now, if you have a EnemyList and an operator to += or << an object of type Enemy into that list, that's a good use of overloading. Using a multiplication operator to define an update operation is unclear and non-idiomatic; it will cause your code to be less readable, not more.
The goal of OO is to condense code and make it easier to understand; this is, arguably, the goal of all good language constructs. If your usage goes counter to this, you are doing something wrong. In this case, overloading an operator does not improve the understandability of the code, and therefore is probably not a good idea.
(As another canonical example, Java's OO model does not condense code, and therefore is clearly wrong. See Smalltalk for how OO should be done. For the record, C++'s OO model isn't much better.)
Thanks for answer.
In my opinion operator overloading is not necessary at design OO game structure. Is only abbreviaton for function invoke. I agree that inheritance improve our design and facilitate changing and developing applications.
Now, I'm thinking about what "world knowlegde" should have every "drawable" game object. Should for example firefly know about its position on the map (x,y coordinate) when we take
class World {
DrawableObjects map[40][20];
}
Should firefly has reference to map, and whole knowledge of map. Or World should pass information about nearest location to firefly every update time to let decide what is its move.
Maybe begin of this concept is not good and I should create e.g.:
GameObject <- spatialObject <- drawnObject <- scenery <- rock
GameObject <- spatialObject <- drawnObject <- item <- diamond
GameObject <- spatialObject <- drawnObject <- entity <- firefly
GameObject <- spatialObject <- drawnObject <- entity <- player
class World {
Scenery map[40][20];
Item item[]; //colection of items on map
Entity entity[]; //colection of entity on map;
}
In this approach is natural that Items and Entities have to have their coordinates.
Which of this two concept is easier to manage, is more general and more suitable to these kinds of games in your opinion.
In my opinion operator overloading is not necessary at design OO game structure. Is only abbreviaton for function invoke. I agree that inheritance improve our design and facilitate changing and developing applications.
Now, I'm thinking about what "world knowlegde" should have every "drawable" game object. Should for example firefly know about its position on the map (x,y coordinate) when we take
class World {
DrawableObjects map[40][20];
}
Should firefly has reference to map, and whole knowledge of map. Or World should pass information about nearest location to firefly every update time to let decide what is its move.
Maybe begin of this concept is not good and I should create e.g.:
GameObject <- spatialObject <- drawnObject <- scenery <- rock
GameObject <- spatialObject <- drawnObject <- item <- diamond
GameObject <- spatialObject <- drawnObject <- entity <- firefly
GameObject <- spatialObject <- drawnObject <- entity <- player
class World {
Scenery map[40][20];
Item item[]; //colection of items on map
Entity entity[]; //colection of entity on map;
}
In this approach is natural that Items and Entities have to have their coordinates.
Which of this two concept is easier to manage, is more general and more suitable to these kinds of games in your opinion.
ON TOPIC:
You've got the right idea and such, you just need to put in the trial and error time to sort out practicalities. Unfortunately, nobody really writes squat about the meat and potatoes of game programming. Just get in there and start coding some settle for using the console window for your display output at the moment. You'll learn so much more trying different things then you will from anyone talking.
On your firefly issue, it's generally easier if you let the firefly access some data structure that contains the map info (whether it's your primary map, or a nav map of sometype).
OFF TOPIC A CHUNK:
I won't say my operator overloading is the best solution, but it works, and it works very well, it's easy to understand for myself and the few others working with the codebase, it's provided every bit of flexibility required. Bad practice can be a good solution.
Key thing is I didn't say USE IT! but only said to think about it and gave some reasons why it worked out great for us.
You've got the right idea and such, you just need to put in the trial and error time to sort out practicalities. Unfortunately, nobody really writes squat about the meat and potatoes of game programming. Just get in there and start coding some settle for using the console window for your display output at the moment. You'll learn so much more trying different things then you will from anyone talking.
On your firefly issue, it's generally easier if you let the firefly access some data structure that contains the map info (whether it's your primary map, or a nav map of sometype).
OFF TOPIC A CHUNK:
Quote:
Actually, this is precisely the kind of usage of operator overloading that most designers would consider bad practice. It's also thoroughly unnecessary.
I won't say my operator overloading is the best solution, but it works, and it works very well, it's easy to understand for myself and the few others working with the codebase, it's provided every bit of flexibility required. Bad practice can be a good solution.
Key thing is I didn't say USE IT! but only said to think about it and gave some reasons why it worked out great for us.
Thanks.
I have one more question. When I make logic for this game, displaying on the console it will be no problem. But when I want to display world with bitmap with animation I must have a place to hold bitmaps and number of currently displaying object's frame. It is possible to completely separate logic and graphic in way that I needn't change my working logic world.
Should I use inheritation or ones of design pattern like decoration?
I have one more question. When I make logic for this game, displaying on the console it will be no problem. But when I want to display world with bitmap with animation I must have a place to hold bitmaps and number of currently displaying object's frame. It is possible to completely separate logic and graphic in way that I needn't change my working logic world.
Should I use inheritation or ones of design pattern like decoration?
yes you can that is the point of MVC that was mentioned previously. If you make good use of it the game won't know how it is displayed and could be text 2d or 3d without changing the logic code.
You're going to struggle to find many UML diagrams for games, because games (especially small games) don't tend to use these things as much. You'd have more luck looking for examples of non-game software design... many books have example projects or you might take a look at some component designs on TopCoder, where you can see the specification, UML someone came up with, and the code written from this design.
In newer languages you can use reflection to enumerate the data within a class to then create the approtiate "views" too it. This makes writting inerfaces to view a classes data a snap. In C++ there isnt a convention for this however people have rolled their own. For a simple game project such as this i would use something like this.
The main points is the way u can create a data binding without having to know anything internally about the class. This allows external systems a "view" into the object without creating a strict depdendcy.
For you console view you can bind the data and output it as raw text using sprintf. For a gui based view u can reinterpt the position of the boulder lets say to be the tile coordinates and use that to place a boulder sprite.
Note also that the bindings are using reference to the data, so once you have them you can at anytime read the true values of the bounded vairables by calling their approriate get funciton. This elminitates the need for constaint repolling of the objects.
Also note the possiblit of name collision between the children data members and the parent data memebers. Some systems prefix the data label with the class name ie "Boulder::mBoulderRadius" to be the full label, I just kept it simple but you should do something like this to prevent name collision.
It compiles but I didn't test it yet. This should get you started, Good Luck!
-ddn
[edit] Replaced code tags with source tags
[Edited by - ApochPiQ on March 5, 2008 2:25:04 PM]
//enumeration to identify raw data blob types to allow you to convert from blob->real typeenum GameTypeIDEnum{ eUnenmeratedType, eDashRawType, //this tells us the data blob is of type struct Dash::Raw};//raw data blob struct holds data to non-primitive game specific data typesstruct RawDataBlob{ RawDataBlob():mGameTypeID(0),mPtr(NULL),mSize(0){} RawDataBlob(int gameTypeID, const char* ptr, int size):mGameTypeID(gameTypeID),mPtr(ptr),mSize(size){} int mGameTypeID; const char* mPtr; int mSize;};//variable data object, can hold variety of data types has somewhat safe interface. Note these bindings//are bound to the variables by reference so this eliminate the need to constantly reenumerate the class once//you have them. This of course poses a problem with scope of the bound data which is left up to the application//to manage.class VarTypeConst{public: enum VarTypeEnum { eIsUnknown, eIsFloat, eIsInt, eIsString, eIsBlob, }; VarTypeConst() : mInt(NULL),mFloat(NULL),mString(NULL){} explicit VarTypeConst(const RawDataBlob blob) : mInt(NULL),mFloat(NULL),mString(NULL){mBlob=blob;} explicit VarTypeConst(const int* var) : mInt(var),mFloat(NULL),mString(NULL){} explicit VarTypeConst(const float* var) : mInt(NULL),mFloat(var),mString(NULL){} explicit VarTypeConst(const std::string* var) : mInt(NULL),mFloat(NULL),mString(var){} VarTypeConst& operator = (const float* var) { Reset(); mFloat = var; return *this; } VarTypeConst& operator = (const int* var) { Reset(); mInt = var; return *this; } VarTypeConst& operator = (const std::string* var) { Reset(); mString = var; return *this; } VarTypeConst& operator = (const RawDataBlob& var) { Reset(); mBlob = var; return *this; } VarTypeEnum GetType() { if (mInt!=NULL) { return eIsInt; } else if (mFloat!=NULL) { return eIsFloat; } else if (mString!=NULL) { return eIsString; } else if(mBlob.mPtr!=NULL && mBlob.mSize!=0) { return eIsBlob; } return eIsUnknown; } float GetFloat() { assert(GetType()==eIsFloat); if (mFloat==NULL) { return 0.0f; } return *mFloat; } int GetInt() { assert(GetType()==eIsInt); if (mInt==NULL) { return 0; } return *mInt; } const std::string& GetString() { assert(GetType()==eIsString); if (mString==NULL) { static std::string nullString; return nullString; } return *mString; } const RawDataBlob& GetBlob() { assert(GetType()==eIsBlob); return mBlob; }private: void Reset(){mFloat=NULL;mInt=NULL;mString=NULL;mBlob.mPtr=NULL;mBlob.mSize=0;} const int* mInt; const float* mFloat; const std::string* mString; RawDataBlob mBlob;};//binding object used to enumerate the data within a game object classstruct DataBinderReadOnly{ std::map<std::string,VarTypeConst> mData;};//base game object class, demonstrates data binding ( a primitive type of reflection ) and proper use//of inheritance chaining for enumeration.class GameObejct{ public: virtual bool EnumerateData(DataBinderReadOnly& bindings) { bindings.mData["mGameObjectID"]=&mGameObjectID; bindings.mData["mName"]=&mName; return true; }private: int mGameObjectID; //test int binding std::string mName; //test string binding};//simple child class demonstrating various data bindingsclass Boulder : public GameObejct{public: virtual bool EnumerateData(DataBinderReadOnly& bindings) { bindings.mData["mBoulderRadius"]=&mBouderRadius; bindings.mData["mBouderMass"]=&mBouderMass; bindings.mData["mPosX"]=&mPosX; bindings.mData["mPosY"]=&mPosY; return GameObejct::EnumerateData(bindings); }private: float mBouderRadius; //test float binding float mBouderMass; float mPosX; float mPosY;};//simple child class demonstrating various data bindingsclass Dash : public GameObejct{ virtual bool EnumerateData(DataBinderReadOnly& bindings) { bindings.mData["mDashSpeed"]=&mDashSpeed; bindings.mData["mRawData"]=RawDataBlob(eDashRawType,(const char*)&mRawData,sizeof(mRawData)); return GameObejct::EnumerateData(bindings); } struct Raw { int mVar1; int mVar2; }; float mDashSpeed; Raw mRawData; //raw data object to test Blob type};The main points is the way u can create a data binding without having to know anything internally about the class. This allows external systems a "view" into the object without creating a strict depdendcy.
For you console view you can bind the data and output it as raw text using sprintf. For a gui based view u can reinterpt the position of the boulder lets say to be the tile coordinates and use that to place a boulder sprite.
Note also that the bindings are using reference to the data, so once you have them you can at anytime read the true values of the bounded vairables by calling their approriate get funciton. This elminitates the need for constaint repolling of the objects.
Also note the possiblit of name collision between the children data members and the parent data memebers. Some systems prefix the data label with the class name ie "Boulder::mBoulderRadius" to be the full label, I just kept it simple but you should do something like this to prevent name collision.
It compiles but I didn't test it yet. This should get you started, Good Luck!
-ddn
[edit] Replaced code tags with source tags
[Edited by - ApochPiQ on March 5, 2008 2:25:04 PM]
I decided to write this simple game in C# and XNA. I have this structures (in abbreviation):
In my main game loop I invoke:
It's OK for me because all IBoardObjects have Draw method. But when I want do same with Update method I must check if IBoardObject is also IUpdatable:
It looks ugly for me : casting, conditional...
Beside I must always rewrite Update method in all IUpdatable objects. I cannot write base Update method due to not multi-inheritance. These same things occur at other interfaces: IDestroyable, IExplodable etc.
Can I write this code in better way. What do you think?
class Board { IBoardObject[,] _mapBoard;}interface IBoardObject{ void Draw(SpriteBatch sb, Camera cam, ref int tilex, ref int tiley);}class BoardObject : IBoardObject { public virtual void Draw(SpriteBatch sb, Camera cam, ref int tilex, ref int tiley) { }}interface IUpdatable { void Update(int x, int y, IBoardObject[,] map);}interface IDestroyable{ void Destroy();}......class FireFly : BoardObject, IUpdatable, IDestroyable { public override void Update(int x, int y, IBoardObject[,] map) { .. } public override void Destroy() { ... } ;}....In my main game loop I invoke:
for (int i = minx; i <= maxx; ++i) { for (int j = miny; j <= maxy; ++j) { _mapBoard[i, j].Draw(sb, cam,ref i,ref j); }}It's OK for me because all IBoardObjects have Draw method. But when I want do same with Update method I must check if IBoardObject is also IUpdatable:
for (int y = Height() - 1; y >= 0; --y){ for (int x = 0; x < Width(); ++x) { IUpdatable up= _mapBoard[x,y] as IUpdatable; if(up!=null) { if(up.UpdateCheck!=UpdateCheck) { up.Update(x,y,_mapBoard); } } }}It looks ugly for me : casting, conditional...
Beside I must always rewrite Update method in all IUpdatable objects. I cannot write base Update method due to not multi-inheritance. These same things occur at other interfaces: IDestroyable, IExplodable etc.
Can I write this code in better way. What do you think?
Why isn't IBoardObject also drawable and updateable? Concepetually C# interface objects are like the C++ pure virtuals, they provide a common interface which helps simplify ur code and provide a runtime defined behavior based upon the object. If an object has no update function it would just define an empty update funciton vs you having to check for that interface everywhere u want to use it from IBoardObject. That way you can write ur code with the specification that all objects have an predefined interfaces, however not all objects implement all interface functions.
Ofcoruse if you have a one off case, using type check is fine, it's better than trying to keep it pure "OO" and a language like C# it's even easier to work with type information.
Good Luck!
-ddn
Ofcoruse if you have a one off case, using type check is fine, it's better than trying to keep it pure "OO" and a language like C# it's even easier to work with type information.
Good Luck!
-ddn
Hello,
IBoardObject is 'Drawable' because all objects are drawn on board. But not all IBoardObjects are 'updatable' because some object are like 'static', no changing during game ('space', 'dirt' 'indestructible wall' for example). I think about using empty update function in these objects, but this isn't elegant approach i my opinion. Maybe using composition instead of interface is good for this game...
IBoardObject is 'Drawable' because all objects are drawn on board. But not all IBoardObjects are 'updatable' because some object are like 'static', no changing during game ('space', 'dirt' 'indestructible wall' for example). I think about using empty update function in these objects, but this isn't elegant approach i my opinion. Maybe using composition instead of interface is good for this game...
There are various ways around the problem if your not in favor of using empty functions. It's really dependent upon the context. If say a large portion of the board objects are updateable and drawable i would use the empty funciton. If say, only a few objects are updateable, store those which are in a seperate list call active list and update them only, this saves u from iterating through empty updateable object set needlessly.
Composition is useful for systems with more dynamic runtime beahvior but gets more complex, so you'll have to build more support structure etc... Unless you really need it, i would advise stick with a direct simple approach to achive ur design. Ultimately composite systems boil down to runtime dynamic classes which have the same issue your facing now as well, how do you handle the presence of interfaces which may or may not exist and how to u handle the permutations of interfaces/functionality.(composition suffers from thos more than static class hiearchiess since they allow for any number of permutations).
The strength of OO is in using inheritance, well defined interfaces and data encapsulation to simplfy programming of complex systems. As your programs grow you'll find that complexity is the biggest problem.
Good Luck!
-ddn
Composition is useful for systems with more dynamic runtime beahvior but gets more complex, so you'll have to build more support structure etc... Unless you really need it, i would advise stick with a direct simple approach to achive ur design. Ultimately composite systems boil down to runtime dynamic classes which have the same issue your facing now as well, how do you handle the presence of interfaces which may or may not exist and how to u handle the permutations of interfaces/functionality.(composition suffers from thos more than static class hiearchiess since they allow for any number of permutations).
The strength of OO is in using inheritance, well defined interfaces and data encapsulation to simplfy programming of complex systems. As your programs grow you'll find that complexity is the biggest problem.
Good Luck!
-ddn
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement