Ok, so maybe I'm not meant to be a game designer but I want definitely to be a game programmer. I like doing all that low level code and I've got an enormous help from tutorials all over the internet and from the "Game Programming Gems Vol 3" book. I hope you find this tutorial useful, and if not then at least I hope you like the save system I wrote for you.
If you've been following my blog you are now aware that I have been developing a game engine for quite some time. I bet heavily on automation and allowing the programmer know as little of internal workings as he wishes. My current iteration of my engine has been influenced heavily by the Source engine structure. But enough about this, if you're here it means you got interested in the a tutorial on how to save entities.
Now, before we begin I'll be mostly talking theory, if you want you can download the entire source code as well as a sample program showing it works in a link I'll provide at the end of this page. This tutorial won't explain on how to implement the saving system I'm describing, but it will tell you a lot of theory to build your own based on Shells or use the code I wrote. If you don't find this tutorial that much helpful since you would like to know the implementation then look at the code in the linked file at the bottom of this post, not only it has everything setup so you can use my code in your project right away, but it has a very good readme file that explains everything from how it works to what every separate thing does, to how to integrate it with existing systems.
Part 1: the start
So you've just created a good entity system or are about to and you think to yourself "gee, if I only had implemented the ability to save entities to a file or a buffer!". Many times beginner programmers let the saving system to the end. Although it's not a bad thing since it's one of the last thing you need you have to prepare a fertile ground for saving. One of the mistakes people do is keeping references to other entities in game as pointers in your object. This practice leads to many problems when you start to make the saving system so here's a word of advice: Don't do it. If you want having references to other entities like to an owner or the nearest enemy keep them as handles. A Handle is a combination of an ID and a index, most probably you'll either keep your entities on a static sized array so you don't have to reallocate the entire collection every time you add or remove an entity. A handle is basically an ID, it works great if it's just the index of the object on the entity array since you're gonna need that anyway. By passing the handle/ID to the entityManager he should be able to give you a pointer to the entity you want or NULL if no such entity exist (the best thing about handles is that you don't have to worry that if you erase your entity if it leaves dangling pointers on other entities). Having all entities inheriting from one common ancestor is also usually a requirement of loading systems.
Part2: the Theory
In an ideal saving system you would be able to just call LoadEntities(char* buf) and the entire system would just do everything you expect it to do. Luckily it is possible, unfortunately you have to implement all that code yourself, or use someone else's code.
Let's start at where we began: we have a collection of objects that don't have any saving implemented and you want to save them. What a bummer! you have to write so much code for every single variable just to save it! You're lucky you've got me, and I'll teach you how to reduce your workload using Shells and Macros (yes, remember my last rant about them? well you'll see them in full action this time!).
I'll just quickly state that by using macros instead of rewriting/copying and replacing about a hundred lines of code for every class you'll only write two short lines, and then one very short line of code for every variable you want to save.
What is a shell?
The theory behind shells is the following: you create a small object specialized for every entity you want to save in your game that holds which variables will be saved and loaded. It'll work as our intermediary between the entity to save and the buffer: [entity]<--->[shell]<--->[buffer] The Ideal would be to be able to write something like this:
FooShell {
- SaveVars
- x[float]
- y[float]
- name[string]
...and then just call the shell's saveObject(object,buffer) and loadObject(object,Buffer). To achieve this we must create a system that:
- allows passing of a variable as a parameter
- allows passing the type of the variable
  v-object start
[x][x][x][x][y][y][y][y][z][z][z][z]
where each [ ] represents a byte. to save y we need to get it's offset from the start of the object. for that we must handle some nasty pointer calculations. in theory you grab the address of y and then subtract from it the address of the object which is also the object start. In our case the offset of y would be 4 and it's size would be sizeof(float) which is also 4. Would you look at that! We already have a format for our variable storage entry in c++ it would look like this:
struct varEntry { unsigned int size; unsigned int offset };our Shell class would look like the following
struct Shell { virtual void fillEntityFromBuffer(baseClass* ent,char* buffer); virtual void fillBufferFromEntity(baseClass* ent,char* buffer); virtual unsigned int getMinBufferSize(); static varEntry* variableList(); };The member function variableList should return an array of statically allocated varEntry-s holding information about the offset and size of every variable to be saved/loaded for the specific entity that Shell was made for, to add simplicity that array should end with a special entry, you could chose an varEntry with size0 and offset 0 or size=0xffffffff and offset=0xffffffff (you can get this by casting a -1 to unsigned integer).
3. How the system works
In a Shell based saving system to save all the entities you'd do either one of these things: every instance of every class would need to have a member function that gives us the shell assigned to it. This works great for saving but problems appear when you are loading entities, and don't know which constructor to use. My favorite mode of overcoming this problem would be to write a singleton factory class for every entity that could not only spawn the entity instance we want, but that could also give us a singleton shell object specific to that entity. On the save system side you'd have one shell for every entity in game and one base shell corresponding to the one entity/class that is the common ancestor from which every other are descendant. An advantage of such system is that if combined with Macros you'll end up writing a little bit more code on the implementation but it'll save you literally thousands of lines of code throughout the project.
The workflow of this method is separated into two phases:-Implementation
-Use
The Implementation phase is the most difficult and takes the longest to complete as you have to write a generic loading code for variables, however thanks to this initial stretch the Use phase, which is when you're actually assigning saving capabilities to every entity you want/need will take a lot less time and a lot less code.
- So the things you'll need are:
- One common ancestor to all entities in game
- A Shell class, that's gonna be rewritten a lot (or not at all with macros), built for saving and loading the variables from a buffer.
- A variable storage entry with a specific format.
if done right with macros you'll end up with having something like this after every entity you want to save:
START_STORAGE_DESC(FooEnt,FooParentEnt,BaseEnt) STORE_VAR(x,TYPE_FLOAT) STORE_VAR(y,TYPE_FLOAT) STORE_VAR(name,TYPE_CSTRING) END_STORAGE_DESC()So that would be all! I hope you liked this tutorial/article/whatever and found it useful, even if not, you can still download my code here: [Shell Based Entity Save System]
Nice tutorial!:) Maybe you should also note that such "shell" can be also used for serializing the properties when sending over the internet in multiplayer modes, so you get 2 in 1.
ReplyDeleteUsing macros for registering the properties isn't very nice, but even the Source Engine is using them, so I guess that there isn't much space for innovations.
Here's the link to their property registering system, if anyone is interested:
http://developer.valvesoftware.com/wiki/Networking_Entities
Cheers! :)