Jump to content
  • Advertisement

Guy Thomas

Member
  • Content Count

    8
  • Joined

  • Last visited

Community Reputation

2 Neutral

About Guy Thomas

  • Rank
    Newbie

Personal Information

  • Interests
    Art
    Design
    Programming

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Guy Thomas

    Question about Observer pattern

    Oxyd's answer is the best. This is why it is important to have setters and getters for your class member variables. If the only way the Lists can be modified is through a setter or getter (or, more to the point a 'removeEntity' method) then you can easily wrap other behaviour into the event that an object is erased.
  2. Yes, I do agree that it wasn't perhaps the strongest example, though I would say I was trying to keep it as simple and easy to follow as possible. Thanks for posting your snippet, I think it better explains the point I was trying to make. Without wishing to make excuses I was writing that example in a hurry on a short lunch break and had to cut it short! I also wanted to cover the point that without a virtual destructor a base class pointer will not call the derived destructor and thus you can potentially miss out on important cleanup operations etc. However as dmatter has correctly pointed out, if there is no additional actions performed in the derived destructor, there is no need to have a virtual destructor in the base class. Hey-ho, time is always short, and thank you all for correcting my mistakes
  3. I totally agree and you were right to point it out, but that was just a simple example. There are a few solutions to the draw order issue you've highlighted and depend very much on the depth sorting of the scene. Without digging into the details of octrees and Z-buffering in 3D scenes, it boils down to needing to sort your drawable objects before looping through them. An example of a way of doing this in a 2D scene might be storing a 'draw layer' in an int (or byte, hardly likely to have more than 255 layers.). Then, when a drawable object registers itself with the engine, it's layer is checked, and it is stored in an appropriate array corresponding with it's layer; if one does not exist, it is created there and then. Then, when the call to 'drawAllEntities()' is made, the renderer makes it's way through the layers, looping through each in turn and drawing the contents. In fact this is exactly the method I used recently in an SFML application recently, and it worked very well.
  4. I think it is important that we show an example of good inheritance vs bad inheritance after this comment: dmatter is absolutely right to say that inheritance is quite often misused. But it is also right to say that there are correct and perfectly acceptable ways to use it, indeed there are even times where it is the best thing to use. So, here is an example of good inheritance: class Soldier { private: int health; int ammo; WeaponType* weapon; Image* sprite; Team team; public: Soldier(int h, int a, WeaponType *w, Image* img, Team t) : health(h), ammo(a), weapon(w), sprite(img), team(t) { } public virtual ~Soldier() { delete sprite; delete weapon; } }; class BLU_Missile_Soldier : Soldier { public: BLU_Missile_Soldier() : Soldier(100, 100, new Missile(), new Image("missileSoldier.png"), Team::Blufor) { } }; class RED_Rifleman_Soldier : Soldier { public: RED_Rifleman_Soldier() : Soldier(100, 100, new Rifle(), new Image("RiflemanSoldier.png"), Team::Redfor) { } }; The example above is considered good inheritance because we are only making derivatives of the existing object, but we aren't taking ownership of any of the members of the base class. The reason inheritance can be considered bad at times is because it is all too easy to put member variables (like health, ammo etc.) into the 'protected:' or heaven forbid even the 'public:' sections of your declaration and thus you break the rules of encapsulation. If the class members are private, and you are only instantiating a variation of the object, then it is usually ok to use inheritance. If however you are changing the core functionality of the object then you may want to consider whether you could get away with using interfaces and composition. Finally, another note on inheritance, always make sure that your ~destructor() is virtual, else your derived classes ~destructor() will not be called when a pointer to the base class is used to destroy your object instance! I think it is important that we show an example of good inheritance vs bad inheritance after this comment: dmatter is absolutely right to say that inheritance is quite often misused. But it is also right to say that there are correct and perfectly acceptable ways to use it, indeed there are even times where it is the best thing to use. So, here is an example of good inheritance: class Soldier { private: int health; int ammo; WeaponType* weapon; Image* sprite; Team team; public: Soldier(int h, int a, WeaponType *w, Image* img, Team t) : health(h), ammo(a), weapon(w), sprite(img), team(t) { } public virtual ~Soldier() { delete sprite; delete weapon; } }; class BLU_Missile_Soldier : Soldier { public: BLU_Missile_Soldier() : Soldier(100, 100, new Missile(), new Image("missileSoldier.png"), Team::Blufor) { } }; class RED_Rifleman_Soldier : Soldier { public: RED_Rifleman_Soldier() : Soldier(100, 100, new Rifle(), new Image("RiflemanSoldier.png"), Team::Redfor) { } }; The example above is considered good inheritance because we are only making derivatives of the existing object, but we aren't taking ownership of any of the members of the base class. The reason inheritance can be considered bad at times is because it is all too easy to put member variables (like health, ammo etc.) into the 'protected:' or heaven forbid even the 'public:' sections of your declaration and thus you break the rules of encapsulation. If the class members are private, and you are only instantiating a variation of the object, then it is usually ok to use inheritance. If however you are changing the core functionality of the object then you may want to consider whether you could get away with using interfaces and composition. Finally, another note on inheritance, always make sure that your ~destructor() is virtual, else your derived classes ~destructor() will not be called when a pointer to the base class is used to destroy your object instance!
  5. std::vector is a great storage container and is usually a good choice but don't write off the other STL containers available to you, most notable std::map and std::set. You've mentioned that you're tempted to use ID's for each object, I do this too a lot as it is a great way of looking things up and being able to store many object's ID's in containers that don't know or care the data type of the object (say for example a std::vector<int> full of various object ID's). If you do end up using ID's, have a look at std::map<int,ObjectPointer>, using the object ID as the key value. This way it's really easy to retrieve it and you don't need to write a loop that calls many object's 'getID()' method. If you don't want to use ID's but want to make sure that you can't ever have two elements the same (which you can easily accidentally do with a std::vector) have a look at using std::set. A set works great with pointers as no two can ever be the same and the 4 byte pointer value works great as a key. Though a std::vector is likely to perform slightly faster than a set or a map, I've never experienced any perceivable performance impact after having used them for years (STL containers are very well optimised) and the benefits the structures add to your development time are great.
  6. I use composition a lot too and most of my classes are made up of a few interfaces. Lets look at it this way, consider this: you have many objects that need to be drawn to the screen via a 'draw()' method. You also want to update them every frame to compute their collisions, etc. We will call the classes Car, Boat, Plane, Man, Box. Now, without composition or inheritance you would need to have the following arrays: std::set<Car*> mCars; std::set<Boat*> mBoats; std::set<Plane*> mPlanes; std::set<Man*> mMen; std::set<Box*> mBoxes; // Then to update and draw... for (auto c : mCars) { c->update(); c->draw(); } for (auto c : mBoats) { c->update(); c->draw(); } // Etc.. etc... This is really messy and breaks the DRY principle of 'Don't Repeat Yourself'. Lets look at this problem another way. Although cars and boats and men etc. may have different members and different methods, they all share two common methods, draw() and update(). Each class will implement draw and update differently - for example the car might calculate it's fuel remaining in update() and draw a picture of a car on screen in draw(). Whereas a person might calculate their age in update() and draw a picture of a person in draw(); Because all the classes share these same methods, we can implement an interface that declares these methods, but allows each class to implement it differently. consider this code (I'll just use Car and Man to save space): class iGameEntity { virtual void draw() = 0; virtual void update() = 0; }; class Car : public iGameEntity { virtual void draw() override { mRenderWindowPtr->draw("PictureOfCar.png"); } virtual void update() override { mFuelRemaining -= 1; } }; class Man : public iGameEntity { virtual void draw() override { mRenderWindowPtr->draw("PictureOfMan.png"); } virtual void update() override { mAge += 1; } }; // Then we can store all our entities like this std::set<iGameEntity*> mAllEntities; // Add some cars and men mAllEntities.insert(new Car()); mAllEntities.insert(new Car()); mAllEntities.insert(new Man()); mAllEntities.insert(new Man()); mAllEntities.insert(new Man()); // Then to call the methods we simply do this for (auto e : mAllEntities) { e->update(); e->draw(); } Now, we only need one container, and we only need to loop over it once to call the draw() and update() methods of every game object. But the real beauty is that every subClass of iGameEntity does something different when draw() or update() is called. Hope this helps get your head around OO coding.
  7. So, In order for a program to run for any real duration there must be a loop of some kind, so it's not so much an argument between having many loops or using good OO practice as it is about using good OO practice while having loops. Most game engines have a relatively simple main loop, that calls a handful of member methods that do some very complex things, in a set order. For example the small game I'm working on at the moment calls a physics step every 10 milliseconds (often not exactly, due to the time it takes the draw frames to complete) but calls a logic and draw step every frame computed. Within these loops it's common to see something like a std::set<std::shared_ptr<Entity>> of current game entities being looped over and having their 'update()', 'draw()', or 'onCollision()' methods being called, and here is where the good OO practice comes in. Entity is a base class that almost every other entity derives from, be that characters, projectiles, pickups etc. Because I store pointers to the base class, I can store any derived class within that set and access the virtual methods within the base class. This means I don't need many collections within the world for every different class type. tl;dl : Looping over collections of OO objects is perfectly fine.
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!