As a small exercise I am trying to write a very small, simple game engine that just handles entities (moving, basic AI etc.)
As such, I am trying to think about how a game handles the updates for all of the entities, and I am getting a little bit confused (Probably because I am going about it in the wrong way)
So I decided to post this question here to show you my current way of thinking about it, and to see if anyone can suggest to me a better way of doing it.
Currently, I have a CEngine class which take pointers to other classes that it needs (For example a CWindow class, CEntityManager class etc.)
I have a game loop which in pseudo code would go like this (Within the CEngine class)
while(isRunning) {
Window->clear_screen();
EntityManager->draw();
Window->flip_screen();
// Cap FPS
}
My CEntityManager class looked like this:
enum {
PLAYER,
ENEMY,
ALLY
};
class CEntityManager {
public:
void create_entity(int entityType); // PLAYER, ENEMY, ALLY etc.
void delete_entity(int entityID);
private:
std::vector<CEntity*> entityVector;
std::vector<CEntity*> entityVectorIter;
};
And my CEntity class looked like this:
class CEntity() {
public:
virtual void draw() = 0;
void set_id(int nextEntityID);
int get_id();
int get_type();
private:
static nextEntityID;
int entityID;
int entityType;
};
After that, I would create classes for example, for an enemy, and give it a sprite sheet, its own functions etc.
For example:
class CEnemy : public CEntity {
public:
void draw(); // Implement draw();
void do_ai_stuff();
};
class CPlayer : public CEntity {
public:
void draw(); // Implement draw();
void handle_input();
};
All of this worked fine for just drawing sprites to the screen.
But then I came to the problem of using functions which exist in one entity, but not in another.
In the above pseudo code example, do_ai_stuff(); and handle_input();
As you can see from my game loop, there is a call to EntityManager->draw(); This just iterated through the entityVector and called the draw(); function for each entity - Which worked fine seeing as all entities have a draw(); function.
But then I thought, what if it is a player entity that needs to handle input? How does that work?
I haven't tried but I assume that I can't just loop through as I did with the draw() function, because entities like enemies won't have a handle_input() function.
I could use an if statement to check the entityType, like so:
for(entityVectorIter = entityVector.begin(); entityVectorIter != entityVector.end(); entityVectorIter++) {
if((*entityVectorIter)->get_type() == PLAYER) {
(*entityVectorIter)->handle_input();
}
}
But I don't know how people normally go about writing this stuff so I'm not sure of the best way to do it.
I wrote a lot here and I didn't ask any concrete questions, so I will clarify what I am looking for here:
You're getting pretty close to the way most games actually do it (although performance expert curmudgeon Mike Acton often gripes about that).
Typically you'd see something like this
class CEntity {
public:
virtual void draw() {}; // default implementations do nothing
virtual void update() {} ;
virtual void handleinput( const inputdata &input ) {};
}
class CEnemy : public CEntity {
public:
virtual void draw(); // implemented...
virtual void update() { do_ai_stuff(); }
// use the default null impl of handleinput because enemies don't care...
}
class CPlayer : public CEntity {
public:
virtual void draw();
virtual void update();
virtual void handleinput( const inputdata &input) {}; // handle input here
}
and then the entity manager goes through and calls update(), handleinput(), and draw() on each entity in the world.
Of course, having a whole lot of these functions, most of which do nothing when you call them, can get pretty wasteful, especially for virtual functions. So I've seen some other approaches too.
One is to store eg the input data in a global (or as a member of a global interface, or a singleton, etc). Then override the update() function of enemies so they do_ai_stuff(). and the update() of the players so that it does the input handling by polling the global.
Another is to use some variation on the Listener pattern, so that everything that cares about input inherits from a common listener class, and you register all those listeners with an InputManager. Then the inputmanager calls each listener in turn each frame:
class CInputManager
{
AddListener( IInputListener *pListener );
RemoveListener( IInputListener *pListener );
vector<IInputListener *>m_listeners;
void PerFrame( inputdata *input )
{
for ( i = 0 ; i < m_listeners.count() ; ++i )
{
m_listeners[i]->handleinput(input);
}
}
};
CInputManager g_InputManager; // or a singleton, etc
class IInputListener
{
virtual void handleinput( inputdata *input ) = 0;
IInputListener() { g_InputManager.AddListener(this); }
~IInputListener() { g_InputManager.RemoveListener(this); }
}
class CPlayer : public IInputListener
{
virtual void handleinput( inputdata *input ); // implement this..
}
And there are other, more complicated ways of going about it. But all of those work and I've seen each of them in something that actually shipped and sold.