Slashdot Mirror


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?"

58 of 229 comments (clear)

  1. Its always best... by boeserjavamann · · Score: 2, Interesting

    ... 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.

  2. They're called singletons now by mrami · · Score: 5, Interesting

    :) 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__)

    1. Re:They're called singletons now by Mariani · · 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?

    2. Re:They're called singletons now by etedronai · · Score: 2, Interesting

      A singleton is really just a way to get around the way that constructors work in OO - namely that they always generate an object before any of the user code is even invoked. This does not allow for intelligent instantiation of objects and object reuse. The way that you are talking about using it is as a way to simulate global state - which is really no better than just using a global variable in the first place.

    3. Re:They're called singletons now by arkanes · · Score: 2, Insightful

      All the general advantages of OO. Encapsulation - all of your access to the global data goes through one object and in one manner. This makes extending behavior (for example, adding journaling) much simpler as well as drastically reducing the chance of errors. The object provides a namespace wrapping around the variables to prevent pollution of the global namespace. And don't underestimate the benefit of cleaner code, which is easier to maintain and easier to fix.

  3. Uhhh.. by QuantumG · · Score: 2, Interesting

    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.
    1. Re:Uhhh.. by Anonymous Coward · · Score: 2, Informative

      You mean the memento pattern, right?

  4. Compressed variable dump by Anonymous Coward · · Score: 2, Insightful

    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.

  5. use state machines and context objects by jamsho · · Score: 3, Interesting

    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 ....)

  6. Serialize the objects in question... by stoborrobots · · Score: 3, Interesting
    Just implement java.io.Serializable

    From the guide:
    Object Serialization supports the encoding of objects, and the objects reachable from them, into a stream of bytes; and it supports the complementary reconstruction of the object graph from the stream. Serialization is used for lightweight persistence ...


    And serialization has been available from java 1.1 at least...
    1. Re:Serialize the objects in question... by Anonymous Coward · · Score: 2, Insightful

      Unfortunately, the OP does not have one single object that they can serialize, and they surely don't want to serialise the whole content of the memory...

      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.

    2. Re:Serialize the objects in question... by yasth · · Score: 2, Interesting
      You generally do have something like GameMap or MobileObjectsHashTable though. The cool thing about serializable is that it ripples through all serializable things. so you could litteraly do
      ObjectOutputStream oos = new ObjectOutputStream(file);
      oos.writeObject("GameMa p");
      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.
      --
      I'd do something interesting, but my server can't handle a slashdotting.
    3. Re:Serialize the objects in question... by bokmann · · Score: 2, Informative

      First of all, the guy is talking C++, not Java.

      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 WriteReplace methods, the Externalizable interface, and for Gods sake, Read the relevant parts of the book 'Effective Java' by Josh Bloch before doing anything with serialization.

  7. Actually... by orangesquid · · Score: 2, Funny

    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
  8. Re:how about.. by danielrose · · Score: 2, Informative

    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
  9. eh? mygame.savemethod()?? by torpor · · Score: 5, Informative

    Would they create a 'MyObject.savemyself()' method for every object in their game?

    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 .. all other derived/related objects, do their big save?

    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 .. embedded data loggers, for example, getting a warning that the shack is about to flood for winter, need to save their state too ..

    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 .. though i think that some would argue that mmap's to flash RAM are cheating ... ;)

    --
    ; -- the corruption of government starts with its secrets. a truly free people keep no secrets. --
  10. Single instance by Bluelive · · Score: 5, Insightful

    This only makes sense if you have a single instance. (ie. Singleton) class. Sounds like your a C user lost in OO land

    1. Re:Single instance by Anonymous Coward · · Score: 4, Funny
      Sounds like your a C user lost in OO land

      Yes my am.

  11. Re:eh? mygame.savemethod()?? by zbyte64 · · Score: 2, Interesting

    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)

  12. easy by DrSkwid · · Score: 5, Funny

    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
    1. Re:easy by AndroidCat · · Score: 3, Insightful
      Dear Miss Manners:
      My home economics teacher says that one must never place one's elbows on the table. However, I have read that one elbow, in between courses, is all right. Which is correct?

      Gentle Reader:
      For the purpose of answering examinations in your home economics class, your teacher is correct. Catching on to this principle of education may be of even greater importance to you now than learning correct current table manners, vital as Miss Manners believes that is.

      There are rules, written or not, and they have no pity.
      --
      One line blog. I hear that they're called Twitters now.
  13. Object-based approach... by genneth · · Score: 3, Informative

    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.

  14. Passcodes by FLAGGR · · Score: 4, Funny

    Screw save games, just give the user a password to get back to the level. Simple. (Just kidding)

  15. whatever works for you by jeif1k · · Score: 2, Insightful

    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.

    1. Re:whatever works for you by arkanes · · Score: 3, Insightful

      This is the largest load of crap I've heard in a long time, especially directed to someone who's a student. The whole point of being a student is that you learn something, not that you just do whatever until you run into all the problems. Sure, what he has works. But he wants to know if there's a better way. He's been introduced to something that he doesn't understand, and doesn't see how it can make his programs better. So he's asking for some examples. This makes him an intelligent person (unless he's just got an axe to grind against OO and is trying to troll, but I'm assuming good faith and even if he is, this is a lousy example to pick), as opposed to being a reactionary turd who'll refuse to better himself.

  16. Re:how about.. by vinsci · · Score: 4, Informative

    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!
  17. "Serialize" by Ninja+Programmer · · Score: 4, Interesting

    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.)

    1. Re:"Serialize" by AndroidCat · · Score: 2, Interesting
      But if you're serializing two games, never cross the streams! (Total protonic reversal!)

      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?

      --
      One line blog. I hear that they're called Twitters now.
    2. Re:"Serialize" by miu · · Score: 2, Funny
      Toss all those old saved games?

      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.

      --

      [Set Cain on fire and steal his lute.]
  18. Configuration Object by sporty · · Score: 3, Insightful

    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

  19. Beware of Memory Dumps. by Jason+Pollock · · Score: 4, Informative

    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

    1. Re:Beware of Memory Dumps. by Scorchio · · Score: 2, Insightful

      Yep, in the long run, these problems will cost you more time than the quick and easy implementation saves you.

      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 occasion that backwards compatibility cannot be maintained, you can prevent the attempted load of an old file. Programmers get pissed after spending hours tracking a bug, only to find it was due to an old incompatible saved game file.

      A little extra work now can save a lot of effort later!

  20. In this case... by dmayle · · Score: 2, 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.

  21. Serialization by SteveX · · Score: 5, Informative

    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.

  22. Are you serious? by ttsalo · · Score: 4, Insightful

    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?
  23. Yes, global variables are bad by ZorroXXX · · Score: 2, Insightful
    and you should always avoid using them if you can. There are exceptions when using global variables are ok, but it should not be your normal style of programming.

    Now why are global variables evil? Because they hide information flow. With global variables any function might change them. For instance:

    int x = 1, y = 1;
    if (global_var != 0) {
    x = some_func();
    y = x + (x / global_var);
    }
    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:

    const char *argv0;
    int main(int argc, char *argv[])
    {
    argv0 = argv[0];
    ...
    }

    void usage()
    {
    printf("Usage: %s <arg1> <arg2> [arg3]\n", argv0);
    }
    --
    When you are sure of something, you probably are wrong (search for "Unskilled and Unaware of It").
  24. Do what Infocom did: dump everything to a file by jonadab · · Score: 2, Interesting

    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.
  25. boost.serialization by ville · · Score: 3, Informative

    Boost has boost.serialization which takes care of such things as pointers. Check it out.

    // ville

  26. How can this fail, let me count the ways... by wowbagger · · Score: 5, Interesting
    Summary of your technique:
    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:
    1. Layout of global section can change from link to link due to project changes, thus making saves version dependant.
    2. Non-save game global data can exist between save items, bloating save file.
    3. You can get the size of the save block wrong, and end up not saving the data you need. e.g. you use &foo and &bar as your start and end pointers, but due to a change there are variables after &bar that need to be saved.
    4. If the items you are saving become full-blown objects with compiler generated information (virtual function tables) you are overwriting that data with possibly incorrect data.
    5. No error checking in file - so a recovered file may screw the game up.

    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:
    • Define a function to allocate a block of "saved space" - sort of a "save_malloc()" function. Allocate the objects you wish to save via that function. In that function, you grab a block of N bytes at initialization, you initialize an end pointer, and you "malloc" by moving the end pointer (no freeing allowed). You now know exactly what you need to save. You can also write a version # at the beginning of the file, and you can compute a checksum of the data. For OO types, you can fancy this up by writing a "Save_game" base class, and implementing new() for that base class.
    • The more OO approach: Implement a "Save_game" base class. The base class implements a linked-list, with a static member as the head pointer. The class has Register/Deregister functions, and a pure virtual Size() method. Derived classes implement Size() { return sizeof(*this);} and call Register in their ctor. To save game, walk the list. This also allows you to save the size of the object (even better if you use RTTI, you can save the actual type of the object), and to checksum each object.

  27. Persistence discussion on LtU by Rich+Dougherty · · Score: 3, Interesting

    You may find a recent discussion on Lambda the Ultimate relevant to your question.

  28. Antiquated by gregRowe · · Score: 3, Insightful

    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 ~
  29. Reverse justification? by erinacht · · Score: 2, Insightful
    You're thinking in terms of your implementation, your method sounds suspiciously like you already had all of your globals and thought, hey I can save these easily, rather than a concious design choice.

    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?

  30. Singletons or Class variables.. all the same. by mystran · · Score: 4, Interesting

    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.
    1. Re:Singletons or Class variables.. all the same. by mrami · · Score: 2, Informative
      In C++, at least, using static variables poses a maintainability problem, because even though they may all be file scope for now, as soon as a cross-file dependency arises, you have the initialization-order problem (that is, C++ doesn't specify which file is to be initialized first). Singletons (whether classes or plain-old functions in C++) avoid this problem.

      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)

    2. Re:Singletons or Class variables.. all the same. by maraist · · Score: 2, Informative

      Depending on your language of choice. Container-based singleton management is an excellent design pattern. In Java, spring and picocontainer manage setter / constructor injection of singletons; possibly even hiding the fact that some objects are session-scope, request-scope or even non-singletons.

      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 environment. And as you expressed concern, you don't have to worry about running multiple applications with isolated singletons. The component-manager is an instance variable (not a singleton), so you can have multiple isolated environments within the same application.

      Makes testing a LOT easier too. If you use abstract / interface classes everywhere, you can easily swap out mock-instances for testing purposes. (having a testing-environment component-manager configuration file).

      I'm speaking from the Java side, but the basic concept should be applicable to C++ if there aren't already implementations.

      --
      -Michael
  31. Game Programming != Other Kinds of Programming by John_Booty · · Score: 2, Insightful

    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.
  32. Re:eh? mygame.savemethod()?? by etedronai · · Score: 2, Interesting

    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.

  33. That's not how you implement singleton by Chemisor · · Score: 2, Informative
    No, the proper way to implement the singleton pattern is:
    class Game {
    private:
    Game (void) {}
    public:
    static Game* Instance (void)
    {
    static Game theGame;
    return (&theGame);
    }
    void setup (int, char**);
    int run (void);
    };

    int main (int argc, char** argv)
    {
    Game* theGame = Game::Instance();
    theGame->setup (argc, argv);
    return (theGame->run());
    }
    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 occurs that crashes your application. With the above implementation Game::~Game is always called allowing you to do necessary cleanup. Good error recovery is especially important in console games, where a crash can leave the terminal in graphics mode and render the machine effectively unusable.
  34. Old Dogs and all by WyerByter · · Score: 2, Insightful

    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.
  35. Use these passcodes: by Chemisor · · Score: 2, Funny

    Use these passcodes. Then we won't have to memorize another 120 meaningless numbers.

  36. Re:Your instructor is insane by arkanes · · Score: 2, Informative
    I can translate your post as "I don't know OO very well, so using it takes me longer than doing things my way". This is all well and good but not a very helpful way of teaching. It doesn't take any longer to write an object than it does to write a whole slew of global variables, and you don't need to add any special amounts of scaffolding. Incidently, the OO technique often (but not always, although it generally depends on the skill of the programmer) makes it easier to leverage the fancy scaffolding into place after the fact.

    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.

  37. Re:how about.. by HeghmoH · · Score: 2, Informative

    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!
  38. Id does it quick and dirty. by Anonymous Coward · · Score: 2, Informative
    The major problem with persisting an object graph, is maintaining the internal relationships without causing duplication on load. To that end, Quake2 (and possibly its successors) has a fairly radical approach to this problem that works really well.

    Basically, keep all your game state in a large array of entities:
    #define MAX_ENTS 600 // or some other large number
    typedef struct Entity{
    int ent_type;
    int health;
    int armor;
    int behavior_state;
    Entity* target;
    Entity* leader;
    // and so on...
    };
    Entity[MAX_ENTS] game_ents;
    (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.
    FILE* save_file = fopen("save.dat","wb+");

    // save object graph of Entities via offset translation
    for(int i=0; i<MAX_ENTS; i++){
    Entity temp = game_ents[i]; // copy
    temp.target = (Entity*)((int)temp.target - (int)game_ents);
    temp.leader= (Entity*)((int)temp.leader - (int)game_ents);
    // you get the idea...
    fwrite(&temp,sizeof(Entity),1,save_file);
    }
    fclo se(save_file);
    (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.

  39. Not "under any circumstances" eh? by snorklewacker · · Score: 3, Interesting

    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
  40. Crime and Punishment by ratboy666 · · Score: 2, Insightful

    Your compiler is not obligated to keep the variable ordering. This means that some parts may not be saved.

    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 /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.

    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 /between/ parts of the program that are not documented, or are very difficult to document.

    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
  41. XML-like Tree serialize... by Leadhyena · · Score: 2, Informative
    Whenever I had to save a game state I would write a tree-like structure, and pass a file object around. Choose the format of your save file to be XML-like in the sense that you have some sort of marker that identifies the beginning of a game object and the end of a game object, and that your objects are nestable.

    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.

  42. Re:how about.. by stew1 · · Score: 3, Interesting

    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

  43. Sorry I Haven't Gotten It Yet by GreyArtist · · Score: 2, Informative

    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