Object-Oriented 'Save Game' Techniques?
GreyArtist asks: "I took a course in C++ a year ago in which the instructor claimed that global (file-scope or inter-file-scope) variables were antiquated and not to be used under any circumstances. I immediately thought of a counter argument that involved the method I use for saving game data. The games (and many of the other programs) I write use not only global variables, but consecutive global variables declared in their own separate module. To save the game (or user settings) to file, I simply save a single large segment of data that contains all the necessary information. How do other coders do it? Would they create a 'MyObject.savemyself()' method for every object in their game? Do they save all the game code along with the data? Either way, it seems like a horrid case of code (or data) bloat. What do you die-hard object-oriented fanatics have to say about this, and what method they would you use for saving games?"
. ..something like a script, that replicates (i.e. replays) the code (like journaling fs when crashed), so that you get up to the most-recent point, which was when you pressed save...?
.
A horse can't be sick, you know, even if he wants to.
... NOT to implement a saveMe() Method in every object i think, cause every object should have only a single responsiblity (SRP: http://c2.com/cgi-bin/wiki?SingleResponsibilityPri nciple)
it would be best to create a SaveData-Object and SaveDataSaver-Object (stupid name, but u know what i mean :)) the saveData object as a value object and a saveDataSaver for the responsibility to save it.
:) You make a singleton called "Prefs" and each module stores its values in a map under its own key (and in GCC, the key could be as easy as __PRETTY_FUNCTION__)
Preferably your language has persistence built in. Obviously if it doesn't, *cough* C++ *cough*, you can't do that. In such situations I believe the Memo GoF pattern would be appropriate.
How we know is more important than what we know.
Just dump the variables to a file, compress/encrypt it (encryption only if you want to give savegame editors a hard time. simple XOR style stuff would do).
Of course, you would want this dump to be parsable so that the game would know what variable was what, what object it was attached to, ect.
Globals are simply a first order abstraction when it comes to storing a program's state.
....)
They get unwieldly fairly fast - as soon as you start hitting any complexity.
Try state machines (see GOF) and lots of singleton classes with 'Context' in the name.
The state machines and context objects can save their own state as the change - and read the state back in as necessary. They can save their state to an in-memory object or straight to a database whether it be a file or otherwise. A 'load' would just work the other way...
Just how I'd approach it.... (it and about anything else non-trivial
From the guide:
And serialization has been available from java 1.1 at least...
"Go to CNN [for a] spell-checked, fact-checked summary" -- CmdrTaco
The proper way to do this is:
;)
int main(int argc, char *argv[])
{
Game *theGame = new Game;
theGame->setup(argc, argv);
int retval = theGame->run();
delete theGame;
return retval;
}
Then, theGame can have private variables that are effectively global variables
--TheOrangeSquid Is it any wonder things seem so awry? We swim in a sea of confusion and don't have to think to survive
Would they create a 'MyObject.savemyself()' method for every object in their game?
.. all other derived/related objects, do their big save?
.. embedded data loggers, for example, getting a warning that the shack is about to flood for winter, need to save their state too ..
.. though i think that some would argue that mmap's to flash RAM are cheating ... ;)
isn't the purpose of 'object oriented programming' that you don't have to think like this? you just call the one big 'Game Object' save method, and
seems a bit wonky to me.
"game saves" is not just a game problem, of course. there are many, many parallels in other types of application
for me, the 'global context save and restore' is a 'built-in' to the design. i'm rather fond of libs and services which provide persistence natively
; -- the corruption of government starts with its secrets. a truly free people keep no secrets. --
This only makes sense if you have a single instance. (ie. Singleton) class. Sounds like your a C user lost in OO land
This is a bit different... but at archspace we use a sort of db cache system. It involves many of the same problems, just at a larger scale. We cache the db into mem, generate values based off the db etc. Without getting into too much detail, every object inherits a Store class. When a db related variable is changed it checks if its already appended for update, if not, it puts itself in a update stack (sometimes it is a better idea to divide the update stack by object type because this is obviously a threaded game). Every 5 mins the updates are executed. The parent reminds me a bit of this since its all divided up. (its a tad more complex though)
just ignore the instructor
there are no rules !
There are places where the networks are not touching,and there are places where they are-Boeing's Lori Gunter
In any case, what you need to do is read a good design/patterns book and see which one looks like a solution or even interesting.
When you're finished with it. Give it to your teacher so they can read it too and maybe, god forbid, learn something.
Ciao
To save the game (or user settings) to file, I simply save a single large segment of data that contains all the necessary information.
"To do foo, I do foo."
Seriously, I don't see how global variables help you with this. It sounds like you learned your own way of programming with a few odd practices, and now reject better design because you think you know better. Try paying more attention with an open mind.
I'm not a CompSci student -- so I don't know the strict definitions of things, but I think this, below, counts as more of an object based approach as opposed to true OOP.
The basic idea is that the thing you're trying to do, ie. have saved game state, ought be a first class thing. So have a global singleton that manages this, and have objects register themselves to that class, then most of the boilerplate can be collected in the global object.
In C++, a better approach would be something like that taken by Boost.Serialization, which provide a template (STL style) framework, so that you can plug in different ways to marshal data as well as different output formats, etc.
Screw save games, just give the user a password to get back to the level. Simple. (Just kidding)
Don't change something just because someone tells you it's not the latest and greatest way of doing things. If saving the way you do works for you, just stick with it. It has a number of potential problems (portability, maintainability, version and architecture dependence), but if those don't bother you right now, there is no need to change until they do.
thats an interesting technique .. and its also interesting to note the different techniques for persistence that /.'ers are describing. seems like there's more than one way to stick around ..
; -- the corruption of government starts with its secrets. a truly free people keep no secrets. --
You simply have to model the the essential game state variables, then create a method for "serializing" them into something that can be thrown out to disk. There is no need at all for these variables to be global, just make sure you pass a "game context" down the call stack to any function which can modify the game state.
The reason why its important to have this abstraction, is that its required in order to make in-game demos, and to have any hope of writing a networked version of your game. It can also let you do strange things like split screen the game and let two people play independent games if you like (a speed contest, for example.)
Just use a configuration object. I would argue NOT to use a singleton. Come the day you need to migrate away from the singleton pattern, you will have a bit of work. Why migrate away? What if you wished to work with two configuration files at once? I know, it's not normal for all applications, but I've had an instance where one was the old config and one was the new. Short version, config object, no singleton.
-s
-
ping -f 255.255.255.255 # if only
If you use a memory dump save (as it sounds to me) you will eventually notice several things:
:)
1) The files aren't easily loaded between versions of the software.
2) The files aren't platform independent.
3) The files are very fragile, and very dependent on compiler options.
This is one of the complaints about Word document files - they can contain memory dumps.
However, for simple ease of implementation, nothing beats getting a pointer and writing a block of memory to disk.
Jason
It must be noted that you loose some portability when creating global objects. Specifically, when you have objects in global space, the constructor must be called by some init code. This does not automatically get called in every environment... especially when the global object is located in a shared object.
Unfortunately, this problem also exists when you include static objects in classes... since these objects exist in global space as well.
It's been said a number of times already and I wholeheartedly agree; serialize.
In short; make every class which should save it's data inherit two methods from a custom class.
One of the methods should write the data for that particular object in the format in which it should be exported (text, xml, binary). The other method should be able to read that data again.
Most likely you'll need to use some sort of structured fileformat as it would be simplest if the file format reflects the tree structure of objects in your game, but such things will explain themselves if you try to implement it.
When all is done you should be able to call the either method at the trunk of your tree to parse or write all underlying objects automagically, then just save the returned data to file and you're done.
Slashdot social media options: AIM, ICQ, Yahoo, Jabber and Mobile Text. Why no MySpace?
In the case you mentioned, with each module having global save state, what you might prefer to do would be to create a GameState base class, mostly virtual, with static methods for registering into a list of modules, and for iterating that list to actually save that data.
In each module specific subclass, you implement the necessary storage, interfaces for the module, and the virtuals for actually performing the save or load.
With proper helper functions, you can save yourself some code, and avaid any namespace issues. Plus, you'll have a framework that will be easily reusable for the next game you write, rather than having to write it all from scratch again.
It's called serialization, and most OO frameworks support it in some way or another.
Usually it's a way for an object to render itself to a stream, and reconstitute itself from a stream.
That way you can save the objects to disk, or send them over the network, or whatever else you need to do with them.
Every object serializes itself, and all of it's immediate children. Once every object does this, you can save the whole tree of objects with one call.
You save settings/games by essentially dumping data straight from memory to disk? How large projects have you implmented this way? How do you figure out the right parts to write/read? How you ensure that the program memory (data segment, stack and heap) will all be in a consistent state after a load?
If the road to hell is paved with good intentions, where does the road paved with evil intentions lead to?
Now why are global variables evil? Because they hide information flow. With global variables any function might change them. For instance:
How do you know that some_func does not change global_var (possibly to zero)? You do not know that without knowing the complete inner behaiour of some_func.If the global variables do not change (only initialised once) then there is no big problems, because then there is no information flow hidden, they are merely some kind of constants. Example:
When you are sure of something, you probably are wrong (search for "Unskilled and Unaware of It").
Infocom's approach to the save-game feature was simple and effective: dump
the entire game -- variables, code, objects, constant data, everything -- from
memory to a big fat binary file. This is not the most efficient save-game
mechanism in terms of savegame filesize, and if your game application is quite
large (as most are today) the save and restore process could take several
seconds (so, you'll want a progress bar), but it has a couple of advantages:
1. It's easy to get right, easy to debug, easy to test.
2. It doesn't matter what kind of objects or data structures your code
uses; you can use objects, lexical closures, continuations, whatever,
it won't matter: it all gets thrown in the binary file, so it all
comes back out when you restore.
The virtual machine that Infocom developed, called the z-machine, is still
in use to this day in the hobbyist interactive-fiction community, and they're
still using this method for saving and restoring games, even though the code
for the games is written in a different language now (a language called
Inform which was developed by a hobbyist for this purpose). All the game
code has to do to effect a save or restore is to issue an opcode, and the
VM does the rest.
Cut that out, or I will ship you to Norilsk in a box.
Boost has boost.serialization which takes care of such things as pointers. Check it out.
// ville
I don't code with objects you insensitive clod!
70e808a22cb027cde4a6abddf6435d55
Declare various global variables.
Save game state into them.
On Save Game, write block of memory out.
How can this fail, let me count the ways:
And that's just what I can come up with before my morning coffee.
Look, I disagree with your instructer about "global variables are NEVER needed" - what, then are stdout/stderr/stdin/cout/cin/cerr, if not global variables?
However, global variables are like salt - a little may be needed, but too much will raise your blood pressure.
Again, this is before my morning coffee, but here's a couple of techniques that are better:
www.eFax.com are spammers
You may find a recent discussion on Lambda the Ultimate relevant to your question.
The second you hear that word in the context of writing software stop listening to the person who spoke it. Just because a technique is old does not make it inferior.
There\'s no place like ~
how can I save all of these parameters and their values so that I can later restore the state?
Restating it as a use case you get,
Player resumes game at the point they stopped last time.
This simple change in thought process lets you see that the saved game is no difference in essence to a word processor document, a spreadsheet document or a text file.
You've identified that a secondary user goal in addition to playing of the game is in the saving and restoring of the state.
The approach you've taken in using global vars is decent enough, but wrap them up in a class to make things easier to manage. If you think of each variable as a global you're limiting future expansion options - say your game supported multiple players over a network and they each want have their own state stored, your global method would need heavy modification to allow that change.
Several people have called for a singleton that the rest of your objects talk to, this option, as I understand the term singleton, seems a good way to convert and future proof your existing code.
Btw - what game is this? Can I download it? Is it GPL?
While I'd argue that global variables are usually a bad idea, I don't see a reason agaist file-scope variables. The rationale goes like this: In OOP you use Singletons when you only need a single variable of a given type. You do it, because while the Singleton gives you global access to the single variable, it still acts as an encapsulation method. This eliminates one of the problems of global variables: unpredicatable modifications, by allowing the Singleton to define what is allowed and what is not. Class variables can do the same, since only the method of the class can touch the variables (provided they are private). But file-scope (static) variables have the same property; the only thing different is that access is restricted not by a class, but by the compilation unit. You still get the same encapsulation. Another question is whether Singletons should be avoided too. Most of the time I like writing code in such a way, that you can run two (separated) instances of the program within the same process by simply create two objects of the main application class. Unfortunately Singletons are often needed to cope with libraries and APIs that assume there's only client within a process. IMHO Singletons (and class variables and methods) are to OOP what IO-monads are to functional programming: they are "hacks" that try to work around the limitations of a given programming model, and trying to minimize the damage caused.
Software should be free as in speech, but if we also get some free beer, all the better.
--dave
davecb@spamcop.net
I don't know what other ways there are to do it - I frequently come across situations where I'm working on something and I am faced with a choice between implementing a feature using procedural techniques with about an hour's worth of effort and some nice easy code, or implementing it using OOP techniques over the course of a day using a ridiculous, bloated, and confusing pile of scaffolding that really isn't needed.
What's my solution? Easy. Freedom from religion. I think of this IT obsession with thinking some technique/language/library/OS/whatever is objectively superior to all others makes about as much sense as the bridge-building community deciding that xxx style suspension bridge is much stronger than others in long spans, so we should always build suspension bridges, even if all we have to get across is a creek or a small viaduct.
Game programming, and programming in other resource-constrained, performance-critical situations, is quite a different beast than other kinds of programming.
If you're writing some mundane database software for your office, you want to focus on code maintainability / extensibility which are OOP's alleged strong points.
If you're coding a game, though, you have an entirely different set of priorities. Code maintainability / extensibility are still great things to have (as you'll surely be developing this code over a long period of time) but they quite often must take a backseat to performance.
OtakuBooty.com: Smart, funny, sexy nerds.
Who is JOHN G?
What you're doing is bad because:
1. Your program cannot be easily extended to support two or more instances of the game without launching two copies of the application. This may not be too important for games, but for other applications with save-state behavior it surely is. With an object storing the
2. If you simply write out the data, the save binary will not be portable across platforms with different byte orders. You also are limited to the kinds of things you can put there; structured data with pointers will not correctly write out to disk.
3. All the other things wrong with global variables (generally program hygeine issues).
It's simple enough to have a "State" object that you fill in with the same information and write a serialization method for it. If you want, you can even make it static, in which case you would be doing almost exactly what you're doing now, but not polluting the global namespace.
Ideally you would solve this problem using an aspect. This is exactly what aspects and AOP were designed for, a way to abstract functionality that does not make sense any where in traditional OO and that cross cuts a large part of the system. Data loggers are also an example of something that should be solved with an aspect.
game.toString();
I guess I should finish my sentences.
"With an object storing the state, you can simply create two of them to have two instances of the games working at the same time."
This is merely a design / storage question. It has no special case for OO.
You must design a save game file format, and this can be as easy/hard in any language.
Of course, OO like Java has object serialization and XMLSerialization. This is a way of storing data from an object directly into a file in a symmetric way.
This is not a question about OO, this is a fairly dumb question
As game saves are file formats, it makes it a design issue, not related to OO mechanics.
#hostfile 0.0.0.0 primidi.com 0.0.0.0 www.primidi.com 0.0.0.0 radio.weblogs.com
The biggest thing I've seen so far, especially in this thread, is people become used to doing things a certain way and may be unable to quickly adapt to a new paradigm. It is possible to store all the information needed for a game in global variables that can be easily stored, but are available to all and subject to unrelatable effects from bugs and other sources. It is also possible to store all game information in objects, that protect the variables they contain from tampering, accidental or otherwise, but can be difficult to ensure every object is serialized, serialized properly and with a minimun of actual data.
I personally believe that just as game programming can be different from other kinds of programming, so also can different types of games differ in there best implementations. As such, there may not be a single answer, but each game must be designed individually. The key seems to be to ensure that the structure of the game is consistent and understood by the person responsible for designing the save functionality.
This signiture copied from somewhere.
That's much how we did it at HDI - a base class from which every object in the game was derived that had virtual save and load functions. Every object that needed saving would implement the save and load functions.
Although we did from time to time use globals as well, but frequently those ended up in ini files instead of save game files since they were more likely not directly game variables, but rather variables like whether or not to show videos.
Government IS the problem.
The truth is that games break all the rules. Global variables are fine in games and necessary in most cases for performance.
So with that said, write your own routines to dump what you need to disk. Do not just dump memory. It will break when you make a new version of the game that needs to save more information.
Object Oriented is a way to abstract code, and it does not impose or preclude a specific technique for saving configuration or state.
In truth, how you define your objects will play a big part and what you are persisting, and thus, how to go about doing it.
I encourage you not to discount your instructor, but rather, consider your object model, game, and decide what actually needs persisted.
(Compare, for example, persisting a Chess Game vs. A Space Invaders Game, vs. a game like Morrowind.)
Like any paradigm, you can find a really horrid way to persist via O.O., or a really elegant ways to deal with this problem too. It's not the paradigm, but the programmer that determines the elegance of the code.
If you are writing Java, Peter Coad in his book Java Design (ISBN 0-13-911181-6) gives a wonderful way of leveraging objects to make clean and elegant object oriented code, (though it does not deal specifically with persistence.)
Good Luck
Use these passcodes. Then we won't have to memorize another 120 meaningless numbers.
Each class which must be saved inherits class which constructor/destructor registers object to a (global) list of to-be-saved-objects. In addition this class has an abstract read/write to/from file method which writes objects state to a file.
save() method iterates through list of saved objects and calls obj->write(). Load method tries to load() object with static ClassName.read() methods till it read() is succesful.
Pointer's are hard to handle so each class must also have a unique id which must be saved instead of pointers write(pointer->getID()) and pointer = IDService->getPointer(ID). Loading must be done in two steps:
1) load all objects, save IDs to pointer variables.
2) replace all IDs with real pointers.
--
Tomas Ukkonen
Basically, keep all your game state in a large array of entities:(BTW, the scheme above has certain distinct advantages over freely new/delete managed objects, via object pooling)
When it comes to persist, simply rewrite all the internal pointers to other entities as indicies and dump the entire list of entities to disk. Loading from a save file is merely the same operation in reverse: translate offsets into pointers.(or merely stuff the base array pointer into the save file and just retranslate on load)
The tradeoff is a quick save/load feature for less flexibility in your game entity scheme (relatively inflexible for oop, but can be done). Also, you'd need an array for every base type you wish to mantain in a proper graph.
What do you die-hard object-oriented fanatics have to say about this, and what method they would you use for saving games?
I'd say I'm being trolled, and won't do your homework for you either.
Here's the deal: when your professor tells you that you're never ever ever ever ... ever ever ever ... ever ever (etc) supposed to do something, then as the poster who quoted Miss Manners mentioned, you've got a pretty good idea what the answer to the exam question is. Any professor who's been in the real world (usually people who retire into teaching) will tell you that there's exceptions to any rule.
Games, with their relentless demands for resource efficiency, will have you breaking lots of rules. Game saves are one of the first walls a junior game designer hits. They've written this fabulously interesting game, unpolished of course, but it's got real potential. But the saved games are two megs each, and take 15 seconds to write out. There goes your console version. You now have to start cutting all kinds of corners to get those save times and sizes down, and that may mean a sacrifice of architectural purity.
To wrap it up, you probably do not want to blindly serialize all your stateful objects into persistent storage and leave it at that. You can and probably should do that while developing the game (be sure to version your objects while you're at it), but when you need to get efficient, you need to start relentlessly trimming the "serialized" form, and seeing what you can build up, recreate, or even just leave out (e.g. a save game in a RTS probably doesn't need all the scorch marks saved). Then instead of serializing to a stream to persistent storage, you want them to simply notify a "state container" with a reference to themselves (the container can egregiously violate encapsulation -- use inner class adaptors or private inheritance if you're paranoid) and that container can index into a memory segment. Then you just write that segment out to disk. Version the damn thing, so if you patch the game, you're not completely hosed. Keep in mind that you're getting RAM 4K at a time, and writing it to disk in bigger chunks, so don't be too stingy.
Now go do the rest of your homework yourself.
I am no longer wasting my time with slashdot
Your compiler is not obligated to keep the variable ordering. This means that some parts may not be saved.
/hasn't/ happened. It may not affect you (the Programmer) but will affect the Maintainer. She will spend considerable time wondering if a change is safe.
/between/ parts of the program that are not documented, or are very difficult to document.
Also, the data file can be "hacked", and your program can be convinced to take other paths (think security -- this includes arbitary code execution). Defending against this means checks on every data item anyway.
If you have C++ global objects, the function pointers in the objects can be overwritten (accidentally, by changing revisions of software, or maliciously).
Global variables are bad, because they introduce the POSSIBILITY of coupling. Generally, if you can do without them, its better. Because once the possibility of coupling is introduced, it is very difficult to prove that it
Things with global variables tend (I said *tend*) to be non-reentrant. Which makes reuse a pain. It also makes a conversion to threading painful.
Global variables can (accidentally or purposefully) provide communication channels
I tell my students: Rule 1: Global Variables are evil; Rule 2: See Rule 1.
As a Student, it is your responsibility to absorb as much Zen of Programming as you can. Believe your Teacher in this instance.
Ratboy.
Just another "Cubible(sic) Joe" 2 17 3061
And have 4GB game saves for your multimedia DVD-ROM game? That'll get you some interesting reviews.
Plus, that's not actually what Infocom did, for the similar reason that 128K game saves wouldn't have been acceptable back then.
GCHQ Quantum Insert installed. If only our tongues were made of glass, how much more careful we would be when we speak
Pffft, whatever.
There are times when a goto is best. There are times when a global actually is the best and correct answer. There are times when a more object oriented answer is best. There are times when a functional programming solution is best.
We should learn from our experiences but you don't throw the baby out with the bath. Everything has a place. I hate it when (especially teachers) say things like "never use globals." There is a time and place for everything. What they really need to do is show a specific example of when and why you should not use globals.
The ratio of people to cake is too big
I'm not quite sure what you mean by globals exactly, but I'd assume you're speaking about game state changes (big things, like You Got the Red Key or You Finished the Ninja King and Therefore Now Have the Sword of Green Fire).
// memset the whole thing to 0
// returns value from statelist // sets a value in the statelist
I don't know how proper this is, but I accomplish this in a variety of ways depending on the complexity of what I have in mind.
Inside my main game object I always have a gamestate object, which is something like this:
class cGameState
{
long int statelist[MAX_STATELIST_SIZE];
public:
cGameState();
int getState(int which);
void setState(int which);
};
Depending on whether or not speed is an issue I assign enumerated values as state names or I use strings loaded from disk at runtime. When I need a value then, I can talk to the gamestate object. When I need to write it out, I add a gamestate::save(FILE *fp) function. (No, I don't use iostreams. Never liked them.)
When it comes time to save a game I usually give every object that would need to be saved its own saving function, something like: entity->save(FILE *fp). A few people would say that this is bad, but in practice I find it quite a nice way of going about things regardless of object bloat. I then have the main game object step through its lists of objects, telling each to write in turn, bracketed by counts of objects or section headers or tags or the like. So for example, the game state would write, in order:
- Magic indicator number (in my games usually 0xD00D)
- save file version, program version
- save file player name
- gamestate array (sizeof(whatever I chose for the array) * MAX_STATELIST_SIZE)
If I need to save entities:
- entity count
- entities as so assuming an open FILE *fp:
for (i = 0; i numTotalEnts; i++, entities[i].save(fp));
I haven't coded in a while, so the above is rightfully suspect.
- Cloud
When ever I write anything of any substantial size I typically go through a stage of coding libraries. One of the libraries I have found *invaluable* is the "name space." All it is is an object that takes a string and a primitive datatype (int8, int16, int32, int64, string, or byte array) and an name space. This lets you make a tree of values which you call write() on the root value and the whole tree is written to a stream. This lets you push configs to a file or over a TCP stream and read said objects back in SIMPLY! Typically I put a "modules" namespace in the root namespace in which I store namespaces named after all the modules I load. SO, when I load my config file I do... value = myNameSpace.find("module configs"); value = value.find(moduleName); Then I pass that namespace to the module and let it configure itself. This makes global values either totally obsolete or limited to a single global value (depending on how you prefer to organize your project). This also makes configuraton files VERY easy to manipulate. I can't say enough good things about this pattern of value management! MS has used it by introducing the registry which has its problems, but is a very similar idea of hierarchical data storage indexed by names. Hope you find this interesting. :)
Sam
This is the largest load of crap I've heard in a long time, especially directed to someone who's a student.
The guy took a C++ class; he is now writing games (presumably for fun or for a living). So, this is not a "student" question, it's a "how do I get this done in the real world" kind of question.
as opposed to being a reactionary turd who'll refuse to better himself.
Well, at least he isn't someone who automatically follows every new fad. Global variables are still useful and reasonable, as is saving them by saving a chunk of memory. It's a simple, effective, time-tested technique. It has some (known) limitations, but if he can live with those, it may be the right technique for him.
But he wants to know if there's a better way.
And the answer is: there isn't a uniformly "better" way. There are different ways of solving this problem that involve different tradeoffs.
Next, create a utility class that contains functions that can read a file up to a marker and return what was read, and another that can identify a marker and jump to the appropriate class. When you do the save, tell the game to save by storing simple objects in order and then passing the save call to the complex objects in order. Those objects will do the same thing, delimiting their objects by bracketing them. Then when restoring, read to each tag and jump to the appropriate class constructor. In effect, you're crawling the tree of objects that are being used for the game.
The advantage of this method is that you store exactly what you need to store, adding game objects is a breeze and almost append themselves to the game tree, and if you expand your game you can tell the reader to ignore tabs it can't identify and work around them, meaning that your older save-files won't break the newer version.
One other thing to keep in mind is that make sure that no constructor actions take place until everything is loaded, so that you don't have say player objects trying to render on a board that hasn't been constructed yet. Make sure you have both a constructor and an initializer funciton for all of your game objects.
What you really want is the Memento Pattern. Intent: Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. I agree generally with the other posters who warn against binary serialization and memory dumps. Define a format for writing out the memento object(s) that captures the necessary state but is easily parseable and extendable. A couple of formats that you've probably seen are the windows-style INI files and some kind of XML. There are lots of others.
No, what I would do is write a save_game and restore_game method in a universal subclass for all game objects. It will know how to serialized all the objects contained within a game object, and how to read them back out. That way, I only implement the save/restore method once.
The radical sect of Islam would either see you dead or "reverted" to Islam.
I accomplish most of this through the use of State Stacks. A State interface is defined and for each state of the game ("in play", "in menu", "paused") I create a class. States can be pushed onto the stack, popped off the stack, or replace other states on the stack (just a few pops and a push, of course). This works well because when you want to Pause, a PauseState class instance is pushed onto the top of the stack. Events are handled by state objects and the one on the top gets first dibs. It then deligates if the events are to be passed on to states below it or not (a "typing chat message" state, for example, might pass mouse events on to the "in play" state below it).
What does this have to do with save games and globals? Well, saving a game is merely a matter of serializing the "in play" state. As the state object holds all the data pertaining to it, there is no need for nasty globals.
The point of all this is simple: you really don't need to have global data. There is always a way to represent it in an object-oriented fashion. And, of course there are advantages. My layout can easily support interesting things, such as mini-games and the like, without any fuss at all. I can even use the exact same code to write a stand-alone puzzle game, or a mini-game. I'm just saying, it isn't all talk when people say everything should be componentized and globals are bad. There are serious benefits, beyond just academic theory.
Question
http://www.ironfroggy.com/
Encapsulate your data with a proper API (getters and setters and maybe some additional stuff). Maybe throw some factory method in that returns the appropriate 'game' object given e.g. a filename.
foo.getProperty1(..); ...
This way you can adjust which instance is used in the game or change the implementation of the API without touching the game code or changing the way the object is persisted.
Your application code just calls
GameProps foo = GameProps.getInstance("somefilenameorwhatever");
foo.setProperty2(..);
getInstance does stuff like opening files on disk if needed and instantiating the GameProps class. It may keep a private reference to a singleton instance or maybe cache some frequently used instances.
This is a nice, lightweight OO solution. You gain a lot of control over the behaviour without any real price and without breaking encapsulation, unlike your global variable.
Jilles
..then I guess a way to ensure better replication up to that point (across versions), would be to code version-info into the saved files, and also, to take use of 'diff' files, for each patch released. So, if you would load an older save on a new-patched game, you could perhaps 'calibrate' using diff-files, so as to upgrade the deprecated moves, to suit the new patches..
I've no idea why I'm writing this; just seems interesting..
A horse can't be sick, you know, even if he wants to.
I would have made an asbstract factory for saving games and an interface for the game saver. IGameSaver* saver = gameSaverFactory.Create(); saver->Save( gamedata ); Kind of like that.. so that the saving of game data is separated from the data model. This gives me the abillity to save to disk or network or any place i want by just making a new class that implements the IGamesaver interface.
I needed to serialize C structs too (for a network protocol), and writing serialization code for every message was too tedious and error prone. So, I made my own serialization package based on Sun's XDR code.
This is from the README:
You're welcome to it if you like, just send me a reply.OO isn't really for games, or any engineering problems for that matter. It's designed to model business or "real world" concepts.
Thus, if you try and use it for engineering style problems as you want to, you will always run into, shall we say, philosophical problems. Many coders just use OO as a way of allocating memory and grouping related functions anyway.
const int one = 65536; (Silvermoon, Texture.cs)
SJW, n: "Someone I don't like, and by the way I'm a fuckwit" - AC
So... make a single object called GameState, and that object can contain all the fields you want:
/* better yet, use a language where there isn't a discrepency between interface declaration and implementation, and source unit and class... */
struct GameState {
all
your
shit
nicely
contained
here
}
it beats a gazillion global declarations and 'extern blah' everywhere.
If you don't like struct, at least throw a namespace on it:
namespace GameState {
your
fields
here
}
It's 10 PM. Do you know if you're un-American?
The area of RAM in the Infocom VM is actually rather small, which is why this worked.
Nobody wants their game state written all over their body.
(Well, some hardcore gamers might actually get off on that.)
September 2011: Looking for Cocoa/iOS work in Boston area Cocoa Programmer Quincy, MA
- analyse your class structure, even if it changes
- ensure everything you do is transactional
- provide fast querying functionality for you with indexes
- help you improve your memory management by allowing to unload unused objects from RAM
Here are some object database links:
http://www.cetus-links.org/oo_db_systems_1.html
http://cbbrowne.com/info/oodbms.html
http://www.db4o.com/
db4o - open source object database for Java and
IMO, he should never use the global variable main().
"global" is nothing more or less than a particular scope.
"Variables" can be loop-wide, procedure-wide, file-wide, program-wide, system-wide, or even internet-wide.
There are uses for each scope.
It's probably true that you shouldn't use a wider scope if a smaller one will work, but I don't buy any arguement that one should never use a particular scope.
-- Should you believe authority without question?
When all the game data fits in RAM, you can do it like Age Of Empires or Starcraft do it: http://www.prevayler.org/wiki.jsp?topic=MajorCompa niesUsingPrevalence
When you can't fit all game data in RAM, you better resort to an OODBMS such as http://www.db4o.com/
See you, Klaus.
If you work with C++, boost-serialisiation is the best solution, as it also is part of an upcoming standard!
> Plus, that's not actually what Infocom did, for the similar reason that
> 128K game saves wouldn't have been acceptable back then.
Okay, I admit, I oversimplified, because I didn't want to discuss the finer
points of the z-machine memory model. Technically, they only dumped the
bottom portion of memory, the read/write portion, or in some cases the vm
(essentially) binarily diffed the in-memory game against the copy on tape
or disk or cartridge or whatever. But these are implementation details;
the fundamental essense of the approach is what I said in the first place.
Cut that out, or I will ship you to Norilsk in a box.
I've been in the industry 8 years (including 3 published PS2 and Xbox console titles) - and we've used OOP on every single project.
When projects take more than a few months (such as an average 18 month development cycle), reliablity and maintainablity are FAR more important than raw speed. If you nail those, speed tends to automatically follow as your code is clean. Besides, the abstraction that OOP gives you by using classes for your game object heirachy is quite fantastic and you'd almost be silly not to do it.
Yes, speed is important. But OOP alone does not automatically give you a speed handicap. C++ features like templates and heavy use of the STL might give you problems - otherwise, no problem. If your algorithms and code execution path is bad, your game is going to have performance problems even if it doesn't use OOP.
In short: OOP is ideal for games.
Yea that is what they teach in school but is rarely used in the real world. Now the idea of just writing out a block of memory to save anything is very old school.
Why should you avoid it? The biggest reason is what if the file gets corrupt? Odds are your program will crash and burn if you try to load the file. It is also hard to recover the file if it damaged. Not that big of an issue for a game but a killer for other types of apps.
Yes the way to do a save is for each object to have a save method. The format should have many levels of error checking and I would avoid binary storage.
See my blog http://ilovecookes.blogspot.com/ for light hearted technical information.
First, I'd like to thank everyone for all the well-written responses I've received to my question, but...
I think that in some instances the crux of my problem may have been overlooked. The title of this post may have been somewhat misleading (my fault), but I was really more concerned with the avoiding code bloat aspect of my question than the "how to save a game state" part, all while maintaining object-oriented methodology. Some people seem to consider this a silly question, as the object-oriented methodology supposedly allows any technique you want. I beg to differ. When I started out programming many years ago (writing BASIC on an Apple II), I instinctively wrote top-down, procedural, and somewhat structured code that had very few GOTO statements (maybe some when I had mismanaged line numbers). The methodologies I just mentioned do allow for any technique you choose to employ, and can rival the efficiency of any of the spaghetti code that came before. I have never found the same thing to be true for object-oriented methodologies.
Some of my concerns about the techniques mentioned:
The singleton technique seems to be a politically correct label for a global data structure. I don't think my ex-instructor would approve (but seriously, it does seem to be somewhat of an anti-oop construct).
The object serialization technique is basically a sophisticated term for the MyObject.savemyself() that I mentioned at the top of the post. Of course the game data would need to be serialized, rather than letting objects write randomly to the save game file with a brief header describing who had done the writing. My overriding concern with this technique is that it would involve so many function calls for a game that included say, 40,000 objects. My secondary concern would be that depending on the class hierarchy, many different versions of a save game state function would have to be employed. While one post claimed that a universal base class would be able to write the game state out for every derivation, I cannot believe it. Derived classes will always have extra features that affect the game state which they will need to handle themselves (their parents and children can't do it for them).
After spending 15 years trying to clearly understand the object-oriented philosophy (even taking a class at my age), it occurs to me that the object-oriented hype seems to be an attempt at scaling down the operating system philosophy to fit single applications. Objects are "mini-programs" that are supposedly decoupled from the surrounding super-program and protected from dangerous and misbehaving code in other modules (of the same super-program). Funny that after 20 years of procedural programming, I have seldom beheld any of the claimed symptoms of not using object-orientation.
I think the industry went in totally the wrong direction. We should have been fixing our operating systems to more closely mimic the single application philosophy...
But I would still be interested in hearing about a truly efficient, simple, elegant, and nearly code-less object-oriented solution to the save game state problem.
email: aofi7@hotpop.com
But the saved games are two megs each, and take 15 seconds to write out. There goes your console version.
Sure, some developers toss out the possibility of a console port when they discover that they can't get the savegame size anywhere near 128 KB (16 blocks on GCN memory cards), but for others, it's "there goes your console version" long before the first line of code is even written, even on games whose save state would take only 1 KB, because the console makers, who control the bootloader, won't even talk to startup game development firms.
#include "stdio.h"
// my global variables
// get/set functions to access "global vars"
#include "stdlib.h"
typedef struct gamedata_s
{
} gamedata_t;
class GameData
{
public:
GameData()
{
pFile = fopen(fname, "r+b");
memset( &data, 0, sizeof(data) );
}
virtual ~GameData()
{
if (pFile)
fclose(pFile);
}
int seek( size_t game_no )
{
long offset = game_no * sizeof(data);
return fseek(pFile, offset, SEEK_SET);
}
int load()
{
return fread(&data, 1, sizeof(data), pFile);
}
size_t save()
{
return fwrite(&data, 1, sizeof(data), pFile);
}
private:
static const char* fname = "gamedata.bin";
FILE* pFile;
gamedata_t data;
};
All your 5 points are very insightful, but if I may be allowed to add one from experience:
6. Blindly writing a block of memory prevents using _any_ kind of pointer, not just virtual function table pointers. _Any_ kind of a linked list, hash table, dynamically sized array, etc, suddenly becomes taboo.
For example, how's a fixed memory block going to accomodate the inventory in an RPG? Have a fixed buffer for every single object in the game, for example, saying how much of each you've got?
Sounds good until you realize that this prevents any kind of mod, and you can't even make an official expansion pack that doesn't require starting the game from scratch. You can't add a new object, because you don't have space for it in that fixed buffer.
Oops.
How about objects that take modifications? E.g., a rifle that can be added a sniper scope, or a pistol which can take a silencer? Or a SMG that can take _both_? Suddenly you can't just add a linked list that points at the augmentations added, because those don't work well with the save-whole-memory-block technique.
Etc.
A polar bear is a cartesian bear after a coordinate transform.
Go fuck your mother. A good grasp of the language is fucking essential to make it far in the world. The GP was trying to be helpful. It's stupid motherfuckers like you that are flushing our language right down the fucking toilet. Don't be such a fucking shithead. Try to be more fucking professional, asshole.
Keyword being: premature.
Let me tell you a story: I've actually written game engines, in the days of Doom, before accelerated 3D graphics cards were anywhere near common. I've actually had to optimize stuff lots.
You had to calculate and paint every pixel in a 320x200 pixel image. Assuming an overdraw factor as low as 2, that's 128,000 pixels written per frame. You want 30 frames per second? Good, that's some 3,740,000 pixels drawn per second.
So you wrote assembly, counted cycles to optimize by hand, and unrolled loops by hand like there's no tomorrow. Yep, that was a different beast. (It was also fun.)
But here's the other side of the coin: optimizing stuff that's _not_ the bottleneck is just plain old stupid. "Optimizing" it in a way that ties your hands in another area, is double-plus stupid.
And "optimizing" without even measuring is downright lobotomized. _The_ worst code, including worst performing, code I've seen so far was invariably code that someone thought they "optimized" but never measured it. You'd be surprised in how many ways reality can differ from what you think as "the fastest way."
E.g., at one point we thought that getting rid of compression for the resources (ought to speed level loading up. I mean, it's logical enough, right? You save the time to decompress those files, right?
Turns out that it actually loaded faster with the compression in place. The CPU time saved for decompression was actually a spit in the bucket compared to the extra time used by the HDD to read the uncompressed data. Go figure.
So to get back to the topic at hand, I find the claim _very_ suspect that saving a raw block of RAM is needed nowadays.
It has a _ton_ of disadvantages, it's fragile, and for what? Even if we're talking coding for a console and their slow memory cards (in fact _especially_ for consoles) a well written serialization can actually write less data than blindly dumping a huge static block where you pre-allocated the largest arrays you can possibly ever need during the game.
A polar bear is a cartesian bear after a coordinate transform.
I haven't tested this, but it seems very promising by the examples.
http://s11n.net/
Enjoy!
Cantão!
> Blindly writing a block of memory prevents using _any_ kind of pointer
:) I've never had a program that relied on this, but I have occasionally taken advantage of it when dumping data in the debugger.
It is amusing that it will actually work for the developer for quite some time, because often the program is loaded at the same address each time you run it. Then you install some service and suddenly your saved games don't work any more
> how's a fixed memory block going to accomodate the inventory
> in an RPG? Have a fixed buffer for every single object in the
> game, for example, saying how much of each you've got?
Believe it or not, that's exactly what Omega (ancient roguelike game) does; there is a 26-slot pack in a global variable and a 13-slot inventory in a global variable. It also has an Objects and Monsters global static arrays in large header files, which are modified at runtime to indicate which objects are "known" and to make special creatures.
since I've done much in OOP w/ C++ (Mostly do ASP.NET and SQL nowdays)... But can't you physicaly save a instance of an object? I seam to remember you could bit-by-bit save to a stream....
I'm not too hot on C++ but a while back I was working on a similar problem in Java. I wanted platform-independent, non-binary state saving files that could easily be extended without breaking the format and the feature of Java that saved my life was Reflection.
With the Reflection classes, you were able to read out, programatically, a list of the variables, methods/functions etc. within each class in the currently running program.
Properly programmed, and by overloading/extending the Serialization classes, you could look into the internals of the program that was running, view all the variables, choose which ones needed to be saved, serialise them into a nice plain-text format (INI, XML, whatever).
If you designed the format right, you could save the info in such a way that a later version of the program would save the same info, with extra fields, in the same format and not have to worry about whether it was a version 1 or a version 2 savegame.
I don't know if there is an equivalent in C++ (I doubt it with all those pointers and complications) but you only really see the power of a Reflection class when you start playing about with it like this.
First: Professors are probably trying to protect the less intelligent in the class from themselves, by attempting to force them not to use globals (since beginners can tend to over use them, which leads to jumbled, unreadable code).
Second: A lot of programming professors do not know what they are saying. He may honestly believe that there is never a case for global variables, just as many programmers believe there is never a case for goto statements. Globally defined constants are still global. What does he say to that? "That's different?"
Onto your post, the true object oriented method would not be to have a static, global object simply marshalling all of the data around. Yes, it is a way to do it, but the general object oriented way would be to make objects that are necessary to be saved as serializable. Loop through them all, then bam, you're done. Easier said then done, but hey, that's object oriented.
I simply save a single large segment of data that contains all the necessary information
How do you "simply save" such a block of memory? If you are doing a simple memory dump into file, then all sorts of problems can crop-up. For example, you cannot save pointer in this manner because the next run of the same program will not necessarily have the same memory layout (i.e. even if same objects are created they may not be at the same address as the last run).
Other problems may occur... differences in alignment (because of change in compiler options), insertion of more variables in the next version of your program, differences in how compiler stores them (because you changed the compiler or compiler version)...
Basically, even if it works, it will be very difficult to attain any kind of portability (between program versions or platforms) for such save files.
If your requirements include;
1) The ability to save games
2) Save games must be compact in size
3) Saving games must be done efficiently
Then your system architecture should take these requirements into account, and your architecture document should outline a clear and consistent scheme for saving games, and your design documentation for each module should identify which attributes need to be persistant and stored in a save game file, which do not, and which ones might optionally be saved on the PC version where disk space is a non-issue.
You should be able to come up with a fairly accurate estimate of a save-game file's size and the time it will take to create it before you start coding, based on the other requirements of the game. If you fail to meet the required sizes for your target platforms, you know you either have to ditch those platforms, or adjust the requirements of your game.
The method you describe is what as known as "hacking in a feature". This is how junior designers turn otherwise simple requirements into "walls". It is also how you end up spending 6 months writing code, only to re-work all of it because it "didn't work out".
It's frequently the difference between being profitable and being a failure.
read this for an old hands view. Basically you are sacrificing most of what is known about good design practice for a marginal benefit.
The idea that all die-hard OO fanatics would do the same thing is absurd. Yes, making a member method (or more desirably the stream operators) for serialization is desirable. However, one could just as easily use a generic serializer like Boost::Serialize(), or a database, or SQLite, or a traditional set of structs, or a byte stream, or feed something to bzip, or or or or or...
Don't choose technique by dogma. Choose technique to suit the current project's need. As the commercial said, "anything else would be uncivilized."
StoneCypher is Full of BS
First off, learning OO via C++, C# or Java will not give a very good first impression of it because they allow you to break OO rules way too easily. To see real OO in action you should check out Smalltalk via squeak http://www.squeak.org/ (it's free). Smalltalk is designed so it's hard to write anything procedurally and it's very easy to learn (something like 5 reserved words total).
{/Soapbox}
Second off, the benefit of OO is that it is much easier for OTHER coders to learn and modify a properly designed OO piece of code than a procedurally designed one. Trust me. One of my first tasks as a 'professional programmer' was to understand a 30 page main procedurally coded simulator. Not fun. Not at all. The guy who wrote it had no problem finding exactly where code mods had to go but when he was reassigned to another project all that knowledge went with him. When I had to learn a well designed OO program it was MUCH MUCH easier.
Chekoff,(Sorry, I just read the Star Trek story)in summary, the difference between a good coder and a great coder is NOT that their code compiles,works, and that one can write code quicker than the other, its how well the design can be learned and modified by others. In the coding business, single points of failures aka losing contracts/business because your stud coder got hit by a bus, is bad.
-Pablo
CSer
Personally, I've moved away from use of C++ serialization, and prefer to write custom "save" code. It may take longer, but it's easier to debug and to maintain upward compatibility.
well you certainly have a grasp of the language (where "language" == your dick, wanking furiously).
Add twenty devlopers working 60 hours a week. Spawn a few threads in your game. And see how well your global data holds up over the course of several game releases.
You don't have a counter argument for the use of global data. You have in experience.