I decided to write this post because a few people were surprised when I told them I make video games in C (and not in C++). What I am told is that since a video game usually requires an object oriented design, using C++ is more suitable to the task. I disagree with this common opinion: writing object oriented code in C is not only possible but, I might even argue, easier than in C++.
Before I enter into the matter, I need to say that I am not against C++. It does indeed have a few interesting features that C does not have. I decide not use C++ in my games because, from my experience, the risk of messing up the code with no way to go back once you realized it outweigh the relatively small convenience you get from it. Yet, some people manage to use C++ in a successful way, and I think that is usually because they enforce strict guideline about what features can be used.
So let's go back to video games. Usually the basic design of a game is to have a common class for all the game objects and then for each type of object (player, enemies, doors, decor, etc.) we create a sub-class. Each sub class reimplements a few methods like "iter", "render". We maintain a list of all the game objects and the engine is responsible for looping over them and calling "iter" and "render".
Of course at first glance C++ seems to be the perfect choice to implement this kind of design. We could have something like:
class Object {
public:
virtual void iter(float dt);
virtual void render();
private:
Vec pos;
// ...
};
class Player : public Object {
// ....
};
class EnemyA : public Object {
// ....
};
Then we could implement Player::iter to update the player position when we press a key, and EnemyA::iter to move the enemy the way we want it.
(Note that in practice it is not efficient to have such a "render" method, because the nature of modern GPU makes it better to batch the rendering of all the entities of a game, but this is not really important for my argument).
In C, there are several ways of implementing the same design, all making use of function pointers.
Since C++ user are rarely exposed to function pointer, lets quickly remind the somehow awkward syntax to define them:
static void my_func(int x) {printf("hello %d\n", x);}
// Here the variable func_ptr is pointing the the function my_func.
void (*func_ptr)(int x) = my_func;
So here is how we could do Object oriented in C: Each base object we create keep pointers to all the implementations of its methods, we then need to create a special constructor function for each type of objects, equivalent to the C++ constructor:
struct Object {
void (*iter)(struct Object *this, float dt);
void (*render)(struct Object *this);
Vec pos;
};
static void player_iter(struct Object *this, float dt) { ... }
static voif player_render(struct Object *this) { ... }
Object *create_player()
{
struct Object *ret = calloc(1, sizeof(*ret));
ret->iter = player_iter;
ret->render = player_render;
return ret;
}
Calling a method of an object is simple, we just need to remember to pass the "this" argument as first parameter, since C won't do it for you.
Object *object = create_player();
object->iter(object, dt);
Of course we are wasting a lot of memory by storing those function pointer for each object. If this is an issue, we can use instead a single pointer to a "v-table" struct that we share among objects of the same type. But for the sake of simplicity I prefer to skip this for the moment. Also this structure offers the interesting possibility to dynamically change the methods of an active object. This can be used in game to simulate state changes. Let say your enemy has two possible states: patrolling, and attacking. If we were to use C++ it would be complicated to implement this using the language classes features. Here we could simply change the "iter" pointer of the object at any time during the game to make it behave differently:
void enemy_patrolling(Object *this, float dt)
{
...
if (player_in_sight)
this->iter = enemy_attacking;
}
void enemy_attacking(Object *this, float dt)
{
...
if (end_attack)
this->iter = enemy_patrolling;
}
Object *create_enemy()
{
struct Object *ret = calloc(1, sizeof(*ret));
ret->iter = enemy_patrolling; // Initial state.
...
return ret;
}
One point I didn't cover is how to create specific attribute to the sub classes. I will talk about this in a next post.