Object-Oriented 'Save Game' Techniques? 229
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?"
Its always best... (Score:2, Interesting)
They're called singletons now (Score:5, Interesting)
Re:They're called singletons now (Score:2, Insightful)
Both ways of working have the same result in the end. They both make fixed references to a fixed place in the memory (the singleton's instance is also a static variable accessible through a static method).
What benefit is there to get from storing prefs inside a singleton instance instead of making them accessible through static methods or variables? Other than cleaner code and applying the OO paradigm I can't think of any. Are there?
Re:They're called singletons now (Score:3, Insightful)
Re:They're called singletons now (Score:2)
Singletons or Class variables.. all the same. (Score:5, Interesting)
Re:Singletons or Class variables.. all the same. (Score:2)
Two possible arguments against so doing are:
legibility might suffer, if the variable declaration is distant from its use
resource management might be an issue, if you're instantiating "a lot" of stuff before you get around to using it.
I think singletons have their appropriate uses, and any idiom can be perverted.
Re:Singletons or Class variables.. all the same. (Score:2, Informative)
For those of you who haven't seen this before, check out Effective C++ by Scott Myers (or Meyers; I don't have it in front of me)
Re:Singletons or Class variables.. all the same. (Score:3, Informative)
The code is managed by an XML file (or some external configuration); you get the effect of singletons, but the extensibility to swap out which implementation of the interface/base-class you use in which environm
Re:They're called singletons now (Score:2, Interesting)
Uhhh.. (Score:3, Interesting)
Re:Uhhh.. (Score:2, Informative)
Momento... (Score:2)
Also... (Score:2)
Nobody wants their game state written all over their body.
(Well, some hardcore gamers might actually get off on that.)
Compressed variable dump (Score:2, Insightful)
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.
use state machines and context objects (Score:3, Interesting)
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
Re:use state machines and context objects (Score:2)
They get unwieldly fairly fast - as soon as you start hitting any complexity.
Yes. That, and the fact that the trouble with Globals, is that anything can use it, and modules (be it procedures, functions, methods, objects, and whatever the else the word of the day is) access variables that aren't declared, making your code an annoyance to use elsewhere -- instead of copying a single module, you now have to copy the mod
Serialize the objects in question... (Score:4, Interesting)
From the guide [sun.com]:
And serialization has been available from java 1.1 at least...
Re:Serialize the objects in question... (Score:2, Insightful)
IMHO, it's a design issue rather than a language issue, if you need to keep track of independent/unrelated variables generously spread all over your code, well... you get what you deserve.
A bit of refactoring seems to be needed, after which the OP can choose from many sound solutions, such as serialising an object to disk.
Re:Serialize the objects in question... (Score:2, Interesting)
Of course you have to be careful that you don't implent serializable where you don't want it. And you have to have some relinking code to relink evertyhing with the GUI, but it isn't that hard.
Re:Serialize the objects in question... (Score:2)
First, Java's serialization format is pretty opaque. That makes debugging, testing, and schema migration a pain in the ass. A better choice is XML via XStream [codehaus.org]. If you have a good object model, its output XML is pretty readable. And you can improve that by adding custom adapters for particular objects. And for the XML-is-too-verbose crowd, let me suggest putting a GZIPOutputStream in the chain.
You generally do have something like GameMap or
Re:Serialize the objects in question... (Score:2)
You can change serialization behaviour with various means to only store exactly that what needs to be stored.
From javadoc of java.io.Serializable [sun.com] :
Re:Serialize the objects in question... (Score:2)
Then he should have one. Encapsulate the (global) game state into an object. OP says he uses global vars for this stuff. That's fine for simple cases but it rapidly gets unweildy as complexity grows. If it helps, think of the GameState object as a namespace, and think about how that can reduce the chances of mistakes and errors. If you embrace the OO desi
Re:Serialize the objects in question... (Score:3, Informative)
Second of all, serialization in Java is not as simple as just saying 'implements Serializable'... In some cases it can be this simple, but if you are doing anything with controlled object construction, such as singletons or the typesafe enum pattern, or if your objects hold onto things that don't make sense to be persisted (such as network connections, open files, etc), then you are going to have to do some special things.
Take a look at the ReadResolve and W
Actually... (Score:3, Funny)
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
That's not how you implement singleton (Score:3, Informative)
This lets you avoid new/delete calls, which in your example can create a memory leak if an exception is thrown or some fatal error occ
Re:That's not how you implement singleton (Score:2)
Re:That's not how you implement singleton (Score:2)
> virtual memory? The application crashes with a fatal error
> -- as a result the memory is reclaimed by the VM.
Well, yes, you are right. It wouldn't really be a memory leak; I just think of it as such. There would be a memory leak if you tried to use the same code in a non-terminating function and something threw an exception. You'd have to catch it and delete the pointer manually.
But the real problem still remains: if you let the VM re
eh? mygame.savemethod()?? (Score:5, Informative)
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
Re:eh? mygame.savemethod()?? (Score:2, Interesting)
Re:eh? mygame.savemethod()?? (Score:2, Interesting)
Save Game method abstraction (Score:2)
Single instance (Score:5, Insightful)
Re:Single instance (Score:4, Funny)
Yes my am.
easy (Score:5, Funny)
there are no rules !
Re:easy (Score:3, Insightful)
There are rules, written or not
Object-based approach... (Score:3, Informative)
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 [boost.org], 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.
Passcodes (Score:5, Funny)
Use these passcodes: (Score:3, Funny)
whatever works for you (Score:3, Insightful)
Re:whatever works for you (Score:4, Insightful)
not a "student" question (Score:2)
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 savi
Re:not a "student" question (Score:2)
"Serialize" (Score:5, Interesting)
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.)
Re:"Serialize" (Score:2, Interesting)
But seriously, when everything serializes, stuff like game file versions or encrypting the stream on the way through get a lot easier. What happens with the block'o'memory save method when you have to insert one damned int in the middle? Toss all those old saved games?
Re:"Serialize" (Score:3, Funny)
Oooh, aren't we fancy Mister "toss all those old saved games". The proper answer according to the programmers of many of the games I've played is to do nothing, blindly load the now badly formated chunk of memory, and then crash the game trying to use it. The user will eventually figure out the problem, so make sure to wipe out their preferences in one of the crashes - to give em something to remember you by.
Re:"Serialize" (Score:2)
Configuration Object (Score:4, Insightful)
-s
Re:Configuration Object (Score:2)
Re:Configuration Object (Score:2)
In short, now I can have two, three, four copies of my softare running, each with a different config file, ea
Re:Configuration Object (Score:2)
Re:Configuration Object (Score:2)
Beware of Memory Dumps. (Score:5, Informative)
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
Re:Beware of Memory Dumps. (Score:2, Insightful)
A couple of rules of thumb I keep in mind when designing a save-game system:
- Maintain backwards compatibility when possible. Assign unique IDs to all values stored in the file, so you're not relying so much on order or assuming the existance of something. Also, testers get pissed when their 8-hour saved game no longer works with each and every new build.
- Add a version number, so that on the
In this case... (Score:3, Insightful)
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.
Serialization (Score:5, Informative)
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.
Re:Serialization (Score:2)
Isn't the PSO hack for gamecube based on this flaw? And the other one for the XBox (Forgot what game).. Without lazy programmers, it would have taken far longer to crack these machines!
Re:Serialization (Score:2)
When you serialize, usually the start of the stream has a version number. When you deserialize, you can decide based on the version of the stream you're reading, how to deserialize it.
So if you add a new field to an object, when you're deserializing it, if the data you're reading is from a version before that field was added, use a default va
Are you serious? (Score:5, Insightful)
Re:Are you serious? (Score:2)
Yes, global variables are bad (Score:2, Insightful)
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 knowin
Re:Yes, global variables are bad (Score:2)
It's back to the old adage "if you only have a hammer, every problem looks like a nail". So expanding your "toolbox" of languages might help you keep a clearer idea of how to solve the problems you face.
Prou
Do what Infocom did: dump everything to a file (Score:2, Interesting)
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
Re:Do what Infocom did: dump everything to a file (Score:2)
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.
boost.serialization (Score:3, Informative)
How can this fail, let me count the ways... (Score:5, Interesting)
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:
Persistence discussion on LtU (Score:3, Interesting)
You may find a recent discussion [lambda-the-ultimate.org] on Lambda the Ultimate [lambda-the-ultimate.org] relevant to your question.
Antiquated (Score:3, Insightful)
Re:Antiquated (Score:2)
By definition, calling something antiquted is calling it inferior. In the context of software development, it's reasonable to call something antiquted if it's been superceded by a better technology or method - punch card programming is antiquated. Don't turn your brain off on buzzwords. There's a good reason people preach against global variables. It's not just dogmatic.
Reverse justification? (Score:3, Insightful)
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?
Your instructor doesn't want checkpointing, either (Score:2)
--dave
Your instructor is insane (Score:2)
What's my solution? Easy. Freedom from religion. I think of this IT obsession with think
Re:Your instructor is insane (Score:3, Informative)
Re:Your instructor is insane (Score:2)
(Let's just answer that first random accusation by pointing out that I'm a Squeak user and I make my living programming in Objective-C.)
Moving on, consider the case of a global state flag. Logic would suggest that since it's a global value, anyway, we might as well just make it a global variable.
Now some folks would say that this is a big no-no, and that we need to get rid of global variables,
Re:Your instructor is insane (Score:2)
Game Programming != Other Kinds of Programming (Score:3, Insightful)
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.
That is not a good counter-example (Score:2)
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 point
Trivial (Score:2)
This isn't an OO question (Score:2)
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.
Old Dogs and all (Score:2, Insightful)
Id does it quick and dirty. (Score:2, Informative)
Basically, keep all your game state in a large array of entities:
Not "under any circumstances" eh? (Score:4, Interesting)
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.
Crime and Punishment (Score:3, Insightful)
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.
Yeah, just like not using "goto" (Score:2, Insightful)
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 s
on game globals (Score:2)
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];
publi
Re:on game globals (Score:2)
Make Your Own Namespace (Score:2)
XML-like Tree serialize... (Score:2, Informative)
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 yo
no need for global variables here (Score:2)
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.getProperty1(..);
foo.setProperty2(..);
OO isn't for games (Score:2)
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.
Huh? (Score:2)
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
}
Re:Huh? (Score:2)
Re:how about.. (Score:3, Informative)
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.
Re:how about.. (Score:5, Informative)
Re:how about.. (Score:3, Informative)
One big advantage of this approach is that you can use the exact same c
Re:how about.. (Score:3, Interesting)
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 varia
Re:how about.. (Score:2)
Re:Global C++ objects not very portable (Score:2)
Re:Ignore the books (Score:2)
Re:Ignore the books (Score:2)
That statement is simple to counter. Let's say you suddenly realize that you need an additional global variable - you need to make a minimum of three changes: first to add the variable, second to add it to the things to save to a file, and third to ad it to the things needing to be loaded.
At the very least, it should be encapsulated within a struct (even one at global scope) so that only one change needs to be made. At worst