Follow Slashdot stories on Twitter

 



Forgot your password?
typodupeerror
×
Data Storage Programming Software IT Technology

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?"
This discussion has been archived. No new comments can be posted.

Object-Oriented 'Save Game' Techniques?

Comments Filter:
  • ... 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.
  • by mrami ( 664567 ) on Tuesday February 15, 2005 @05:49AM (#11675962) Homepage
    :) 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__)
    • 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?

      • 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.
      • In addition to the things that arkanes lists, there is also the fact that in C++ a properly coded reference-counting Singleton does not need to take up program memory except when you are using it. One static method is used to grab the instance of the class and another to free it. When all references are freed, the Singleton can clean up its instance and not take up memory. This allows your program data to remain properly modularized without also keeping a conslidated copy except during the actual process
    • by mystran ( 545374 ) on Tuesday February 15, 2005 @08:10AM (#11676372)
      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.
      • So you're effectively treating the file as a closure to 'bracket' a few variables.
        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.

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

      • 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 environm
    • 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.
  • Uhhh.. (Score:3, Interesting)

    by QuantumG ( 50515 ) <qg@biodome.org> on Tuesday February 15, 2005 @05:51AM (#11675967) Homepage Journal
    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.
  • by Anonymous Coward
    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.
  • by jamsho ( 721796 ) on Tuesday February 15, 2005 @05:56AM (#11675985)
    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 ....)

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


      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
  • by stoborrobots ( 577882 ) on Tuesday February 15, 2005 @05:57AM (#11675989)
    Just implement java.io.Serializable [sun.com]

    From the guide [sun.com]:
    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...
    • by Anonymous Coward
      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.
      • 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.

        • This is a good start, but there are a couple of ways to improve.

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

        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] :

        [...]

        Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

        private void writeObject(java.io.ObjectOutput

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

        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

    • 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 W
  • Actually... (Score:3, Funny)

    by orangesquid ( 79734 ) <orangesquid&yahoo,com> on Tuesday February 15, 2005 @05:59AM (#11675994) Homepage Journal
    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 ;)
    • 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 occ

      • Can I ask you how a memeory leak occurs with a system using virtual memory? The application crashes with a fatal error -- as a result the memory is reclaimed by the VM. Do I have some large misconception reguarding the concepts?
        • > Can I ask you how a memeory leak occurs with a system using
          > 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
  • by torpor ( 458 ) <ibisum@@@gmail...com> on Tuesday February 15, 2005 @06:10AM (#11676021) Homepage Journal
    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 ... ;)
    • 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 thread
    • 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.
    • No, what I would do is write a save_game and restore_game method in a universal subclass for all game objects. It will know how to serialized all the objects contained within a game object, and how to read them back out. That way, I only implement the save/restore method once.
  • Single instance (Score:5, Insightful)

    by Bluelive ( 608914 ) on Tuesday February 15, 2005 @06:17AM (#11676044)
    This only makes sense if you have a single instance. (ie. Singleton) class. Sounds like your a C user lost in OO land
  • easy (Score:5, Funny)

    by DrSkwid ( 118965 ) on Tuesday February 15, 2005 @06:22AM (#11676058) Journal
    just ignore the instructor

    there are no rules !

    • Re:easy (Score:3, Insightful)

      by AndroidCat ( 229562 )

      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

  • by genneth ( 649285 ) on Tuesday February 15, 2005 @06:34AM (#11676086) Homepage
    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 [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)

    by FLAGGR ( 800770 ) on Tuesday February 15, 2005 @06:36AM (#11676090)
    Screw save games, just give the user a password to get back to the level. Simple. (Just kidding)
  • by jeif1k ( 809151 ) on Tuesday February 15, 2005 @06:42AM (#11676117)
    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.
    • by arkanes ( 521690 ) <(arkanes) (at) (gmail.com)> on Tuesday February 15, 2005 @09:59AM (#11677162) Homepage
      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.
      • This is the largest load of crap I've heard in a long time, especially directed to someone who's a student.

        The guy took a C++ class; he is now writing games (presumably for fun or for a living). So, this is not a "student" question, it's a "how do I get this done in the real world" kind of question.

        as opposed to being a reactionary turd who'll refuse to better himself.

        Well, at least he isn't someone who automatically follows every new fad. Global variables are still useful and reasonable, as is savi
        • If he's satisfied with what he's doing, then he wouldn't be asking questions. Moreover, anyone who is doing anything of any complexity who is satisfied with using global variables and serializing via memory dumps isn't someone who's doing anything I would want to be associated with. It's bad code. I suppose as long as he keeps it to himself without asking anyone else to run it there's no harm.
  • "Serialize" (Score:5, Interesting)

    by Ninja Programmer ( 145252 ) on Tuesday February 15, 2005 @06:54AM (#11676150) Homepage
    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.)
    • Re:"Serialize" (Score:2, Interesting)

      by AndroidCat ( 229562 )
      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?

      • by miu ( 626917 )
        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.

      • If you're talking about a console game, it's probably not going to change. If you're talking about a PC game, why not use a structured file format for your save games? There is no way to keep the user from tampering with it, so why not just make it straightforward? You can afford fairly large save games on PC platforms.
  • by sporty ( 27564 ) on Tuesday February 15, 2005 @07:00AM (#11676161) Homepage
    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
    • Could you explain the situation a bit more? I'm not clear on what scenario you're describing, and why a Singleton is not appropriate for the scenario.
      • Sure. A system I am working on allows for multiple people to log into it in tandem. It is convenient to have configuration files for each user. I *could* start a second JVM and let life happen with singletons. To save headaches, and simplicity, I have one class that servers as the kernel for this system and a configuration file object that works with that kernel class and classes it works with.

        In short, now I can have two, three, four copies of my softare running, each with a different config file, ea

        • Okay, I see. That makes perfect sense. Obviously a Singleton isn't ideal for every "save-data" situation. But even in your situation, I could imagine a Singleton might be used to manage the config objects, rather than directly storing the config data. Tell the Singleton which user's data you need and it comes back with the config object for that user.
          • Then my singleton would be just some static (java) method that just reads in a configuraiton object or writes it out. That's not the singleton pattern. Also, having a class that manages doing one thing with the object and prevents me from seeing the object as I should prevents me from doing dynamic things with it, like cloning, or writing it to other data sources. Singletons are good for two things. Globals and single ever existing things (Boolean.True.getInstance(), Boolean.False.getInstance()....)
  • by Jason Pollock ( 45537 ) on Tuesday February 15, 2005 @07:08AM (#11676176) Homepage
    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
    • 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
  • In this case... (Score:3, Insightful)

    by dmayle ( 200765 ) * on Tuesday February 15, 2005 @07:11AM (#11676186) Homepage Journal

    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)

    by SteveX ( 5640 ) on Tuesday February 15, 2005 @07:13AM (#11676190) Homepage
    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.

  • Are you serious? (Score:5, Insightful)

    by ttsalo ( 126195 ) on Tuesday February 15, 2005 @07:28AM (#11676219)
    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?
    • Hey, don't be so harsh. The Microsoft Word development team is a good place for this guy to work in. He obviously doesn't have a clue about OOP and serialization. This is one of those "someone told me I'm doing something wrong, but I immediately said no way. Is there someone here who does stuff the way I do? we're not wrong, right? right?" ask slashdot posts.
  • 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 knowin

    • Sorry, I think you put the cart before the horse. The problem is not global variables, but the limitations imposed on the programmer by the OOPs model. While OOPs offers some benfits over traditional procedural and fuctional methods, it also creates new problems and some nasty hacks to get around them.

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

      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)

    by ville ( 29367 ) on Tuesday February 15, 2005 @07:46AM (#11676268)
    Boost has boost.serialization [boost.org] which takes care of such things as pointers. Check it out.

    // ville
  • by wowbagger ( 69688 ) on Tuesday February 15, 2005 @07:53AM (#11676297) Homepage Journal
    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.

  • by Rich Dougherty ( 593438 ) on Tuesday February 15, 2005 @07:59AM (#11676322) Homepage

    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)

    by gregRowe ( 173838 ) on Tuesday February 15, 2005 @08:00AM (#11676328)
    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.
    • "Too old to be fashionable, suitable, or useful; outmoded." -- From dictionary.com

      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.

  • by erinacht ( 592019 ) on Tuesday February 15, 2005 @08:02AM (#11676337) Homepage
    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?

  • ... and the unit of migration is the checkpoint (:-))

    --dave

  • I don't know what other ways there are to do it - I frequently come across situations where I'm working on something and I am faced with a choice between implementing a feature using procedural techniques with about an hour's worth of effort and some nice easy code, or implementing it using OOP techniques over the course of a day using a ridiculous, bloated, and confusing pile of scaffolding that really isn't needed.

    What's my solution? Easy. Freedom from religion. I think of this IT obsession with think
    • 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 p
      • I can translate your post as "I don't know OO very well, so using it takes me longer than doing things my way".

        (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,
        • If the guy had asked me how to serialize *one* global variable I wouldn't have said anything. But it's never just one, as I'm sure you know. I wouldn't write this in anything like the way you describe - I'd create a GameState object with accessors for all of the variables that would normally be flat global vars. I might spend 5 minutes making it a real singleton. Access to global state would be through the GameState object only (and I might make that state object global) and I'd be done with it. Complicated
  • 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.
  • What you're doing is bad because:

    1. Your program cannot be easily extended to support two or more instances of the game without launching two copies of the application. This may not be too important for games, but for other applications with save-state behavior it surely is. With an object storing the

    2. If you simply write out the data, the save binary will not be portable across platforms with different byte orders. You also are limited to the kinds of things you can put there; structured data with point
  • game.toString();
  • This is merely a design / storage question. It has no special case for OO.

    You must design a save game file format, and this can be as easy/hard in any language.

    Of course, OO like Java has object serialization and XMLSerialization. This is a way of storing data from an object directly into a file in a symmetric way.

    This is not a question about OO, this is a fairly dumb question

    As game saves are file formats, it makes it a design issue, not related to OO mechanics.
  • Old Dogs and all (Score:2, Insightful)

    by WyerByter ( 727074 )
    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,
  • by Anonymous Coward
    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...
    };
    Enti

  • by snorklewacker ( 836663 ) on Tuesday February 15, 2005 @10:43AM (#11677618)
    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.
  • by ratboy666 ( 104074 ) <fred_weigel AT hotmail DOT com> on Tuesday February 15, 2005 @10:47AM (#11677654) Journal
    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.
  • Pffft, whatever.

    There are times when a goto is best. There are times when a global actually is the best and correct answer. There are times when a more object oriented answer is best. There are times when a functional programming solution is best.

    We should learn from our experiences but you don't throw the baby out with the bath. Everything has a place. I hate it when (especially teachers) say things like "never use globals." There is a time and place for everything. What they really need to do is s
  • I'm not quite sure what you mean by globals exactly, but I'd assume you're speaking about game state changes (big things, like You Got the Red Key or You Finished the Ninja King and Therefore Now Have the Sword of Green Fire).

    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
  • When ever I write anything of any substantial size I typically go through a stage of coding libraries. One of the libraries I have found *invaluable* is the "name space." All it is is an object that takes a string and a primitive datatype (int8, int16, int32, int64, string, or byte array) and an name space. This lets you make a tree of values which you call write() on the root value and the whole tree is written to a stream. This lets you push configs to a file or over a TCP stream and read said objects
  • 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 yo

  • Encapsulate your data with a proper API (getters and setters and maybe some additional stuff). Maybe throw some factory method in that returns the appropriate 'game' object given e.g. a filename.

    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 really for games, or any engineering problems for that matter. It's designed to model business or "real world" concepts.

    Thus, if you try and use it for engineering style problems as you want to, you will always run into, shall we say, philosophical problems. Many coders just use OO as a way of allocating memory and grouping related functions anyway.
  • So... make a single object called GameState, and that object can contain all the fields you want:

    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
    }

    /* better yet, use a language where there isn't a discrepency between interface declaration and implementation, and source unit and class... */
    • As others have mentioned you can of course use a singleton object, which buys you not having to declare a global object at all because you can access the object through a static accessor (getInstance()), however, as I found recently, there often isn't a guarantee as to when static object data is initialized (i.e. you could call getInstance() before the struct was actually initialized, assuming you used a struct and it was statically initialized). Your call.

The herd instinct among economists makes sheep look like independent thinkers.

Working...