Basic game structure is a topic that many people have trouble with, yet it somehow gets overlooked. In this lesson, I will show you exactly how to set up and structure code for commercial games.
We're going to use
Leadwerks 3 in this lesson because it makes C++ game development faster, but the ideas here are applicable to any programming environment.
Direct vs. Component Programming
Although the component-based script system Leadwerks supports is a convenient way to get simple demos running quickly with Leadwerks, it can be limiting when you try to make a full game. Fortunately Leadwerks supports
direct programming, in both C++ and Lua. This gives a lot more power and control than component-based systems. To really take advantage of this power, we need to understand some basic principles on how to set up and structure our game.
Class Structure
We start with a base class for all objects in our game. We'll call this the
Node class, and derive it from the
Leadwerks::Object class. The
Node class is not an entity, but it has an entity as a member. Think of a
Node as your own game object that is associated with an entity.
For this lesson we'll create an imaginary class called
Foo derived from the
Node class. The
Foo class could represent an enemy, an NPC, a bullet, a grenade, or anything else. We can use the same structure for all of these things. The
Foo class has one function called
Update. This is where all our game code that updates that single instance of this class would go. This code could control the trajectory of a bullet, the AI of an enemy, or anything else. The point is all the code that controls that object is compartmentalized into this function, and it gets called over and over again, for each instance of the class.
In order to keep track of each instance of the
Foo class, let's use a standard C++ list. This is listed in the header file as a static member, so that each instance of the class can access this list:
static std::list list;
Each instance of the
Foo class will also have a list iterator so we can remove it from the list when it's deleted:
std::list::iterator it;
In the
Foo() constructor, the object will add itself to the list of all objects in this class:
Foo::Foo()
{
list.push_front(this);
it = list.begin();
}
And in the destructor, we will use the iterator to remove the object from the list:
Foo::~Foo()
{
list.erase(it);
}
WARNING Removing the iterator from a list can cause a crash if this object is deleted while your code is iteratoring through a list. To avoid this problem, you can add the object to be deleted to a queue of objects to be deleted in your main application loop. However, this is beyond the scope of this lesson, which is only meant to convey a simple program structure.
Why do we need a list of all the instances of our
Foo class? Well, this means we can now
iterate through each one, at any point in our program. This is very powerful because it means we can create new instances of the
Foo class at any time, and our game will adjust to keep them all running, without hard-coding a lot of specific behavior. Iterating through the list is done with the following code:
//First we declare an iterator so we can cycle through our loop
std::list::iterator it;
//Loop
for (it = Foo::List.begin(); it!=Foo::List.end(); it++)
{
//The Foo object is gotten with (*it)
(*it)->Update();
//Alternatively, you could declare a Foo* variable and set it to this value
//Foo* foo = *it;
//foo->Update();
}
This code should go somewhere in your main game loop. You'll end up with a loop like that for each class your game uses, if it's a class that needs to be continuously updated. (Some types of objects can just sit there until something happens to make them react. For those situations, I recommend using a collision callback or other means to activate them.) So your main loop will look something like this now:
bool App::Loop()
{
std::list::iterator it;
for (it = Foo::List.begin(); it!=Foo::List.end(); it++)
{
(*it)->Update();
}
world->Update();
world->Render();
context->Sync();
}
We're going to do one more thing to make our code a little cleaner. Because we'll probably end up with a dozen or more classes by the time our game code is done, we can take that loop and put it into a static function:
void Foo::UpdateEach()
{
std::list::iterator it;
for (it = List.begin(); it!=List.end(); it++)
{
(*it)->Update();
}
}
The Main Loop
Our main game loop becomes a little easier to manage now:
bool App::Loop()
{
Foo::UpdateEach();
world->Update();
world->Render();
context->Sync();
}
By the time our game is done, the main loop will look something like this:
bool App::Loop()
{
Enemy::UpdateEach();
Player::UpdateEach();
Projectile::UpdateEach();
world->Update();
Explosion::UpdateEach();
world->Render();
context->Sync();
}
You might wonder why I didn't just create a list in the
Node class, and have an
Update function there. After all, any class derived from the
Node class could override that function, and a single loop could be used to update all game objects. There's two reasons we don't do that.
First, not all of our game objects need an
Update function to be called each frame. Iterating through hundreds or thousands of unnecessary objects would hurt our performance for no good reason. We
can put an
Update function in a base
Enemy class, however, and have both goblins and trolls use the same
Update loop, when
Enemy::UpdateEach() is called.
Second, we want to control the order and time at which each class is updated. Some classes work best when they are updated at the beginning of the loop. Some work best when they are updated between the call to
World::Update() and
World::Render(). It's different for each class, depending on what you make them do, and we want to leave room for ourselves to experiment and not get locked into a design that can't be easily changed when needed. We could try working around this by setting a priority for each class, so objects are updated in a specified order, but I wouldn't do this. In my opinion, this is the point where your structure is done and you should think about structuring the classes for your game, and filling in their code.
So what does the
Foo:Update() function do that's so important, anyways?
Foo::Update() presently does nothing, but it does everything.
This is where your game code goes. We can use this structure for AI, bullets, rockets, explosions, enemies, tanks, planes, ninjas, pirates, robots, or giant enemy crabs that shoot laser beams out of their eyes. In fact we can also use the same structure for those laser beam objects the crab is emitting!
Conclusion
The main point of this is to show how to graduate from writing simple single-file demos, and to start thinking in terms of classes. Your game code should be written in such a way that it doesn't matter how many objects there are of each class, when they get created, or when they get destroyed. Each class
Update() function is written from the point of view of that single object, looking out at the world around it. This simple concept can be used to make just about any type of game, from first-person shooters to MMOs.
The image for this article was provided by
oppenheimer.
We also provide a system of hooks. I recommend making use of the Collision and UpdatePhysics hooks, when appropriate. There is an UpdateWorld hook is available, and does pretty much the exact same thing we set up here. You could use this to control the Update() function for all objects, but you may find you need control over the time and order in which objects are updated, for the reasons described in this article.