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?"
... 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
If you are initiating a save by pressing save, you may as well save what your variables are at NOW.
You don't need to log changes as they occur, like a journalled fs does, unless you wanted to eliminate the need to press save, and have everything save automagically. This may be impractical, depending on the number of variables and states you are saving in your log.
i hate pansy republicans
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
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.
You mean object prevalence like in Prevayler? See also here for a general presentation (intro here: Object Prevalence in C++).
Trusted Computing FAQ | Free Dawit Isaak!
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
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
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.
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.
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.
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.
Use these passcodes. Then we won't have to memorize another 120 meaningless numbers.
Personally, I have never seen a functional or procedural technique which could not easily be translated into OO. I've seen lots of OO features that can't be translated into functional or procedural, if only because the lack of language support makes using them that way awkward. So in my (admittedly limited) experience, OO is objectively better. I wouldn't make that as a general claim, of course, but I will make a general claim that encapsulating something like global state as an object is superior to using it as a bunch of global variables. Even making a single global struct would be better.
Interesting you should mention that. Bungie used to be a big fan of this technique, which they used in all of their Marathon and Myth games. The game basically just recorded the player's actions, and saved this record into the file when you hit save. Restore was a matter of loading the level and then replaying the save file until the end. How they managed not to break save files every time they released a new version, I will never know.
One big advantage of this approach is that you can use the exact same code to create game films. Create a file the exact same way, but display the results when you play it back, and presto! Bungie did this for Marathon and Myth as well.
The big disadvantage is that you have to be absolutely sure that the game will play back exactly the same way every time. As far as I know, the Aleph One open-source version of the Marathon engine has never, and probably will never, read normal Marathon saved games or films because of subtle differences in how the engines operate.
Mod down posts with a "Free Mac Mini/iPod" sig, they're spam!
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.
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
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.
Yes. Many games these days are written as if they were a pure simulation. You have your initial state and you change state by applying inputs (user commands & time progression). It should be completely deterministic, so you can have two copies of the game that start with the same state, apply the same inputs, and arrive at the same final state.
This may seem heavyweight, but, hey, processors are fast these days. Many games use scripting languages -- the "I need to write this in C using only global variables" approach of yesteryear is no longer necessary. Additionally, you get some great benefits. Chief among them is replayability and save&restore, but you can also get a relatively simple network protocol (mostly just send inputs, which are small), maintainable code, easy dead reckoning, and the ability to apply other optimizations on top of the model (e.g. lag thrusters). Also, an important insight is that you don't necessarily have to display your simulation state _exactly_. This way you can have reliable code to track the simulation and reap the benefits of such a model, and you can also use a bunch of hacks on top of the simulation to make the user experience look/feel good. That's a good way to isolate the hacks that go into any game.
Jon
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