Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (494)
Games in Android Showcase (114)
games submitted by our members
Games in WIP (563)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  A good way to make save games that doesn't have version portability probs  (Read 5408 times)
0 Members and 1 Guest are viewing this topic.
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Posted 2005-08-16 03:35:52 »

To anyone who wants to know how to do this, check my conclusion post in this thread. It's page 2 post #17

Hello friends,

I have a game and currently we save levels with the ObjectOutputStream, where it just writes all the objects in the level to a data file.  This works fine, except for if I update any of the classes saved at all, like adding in a new method or modifying the parameters of an old one. There's the issue. In something other than a game, I'm sure you can ignore this, but that is simply impossible in a game that may release patches or new versions.

So, what's a solution? I was thinking I would save all the specific primitives to a file in a certain order, and then construct each object upon loading of the level, but this would get very tedious for so many classes and would mean modifying the load level code each version update. I will do this if nobody can think of a better way, but I wouldn't be surprised if I'm not the first one who's dealt with this.

If there is an way in LWJGL, I'll try that also, as I will eventually convert much of the game to it.

Thanks.

See my work:
OTC Software
kul_th_las
Guest
« Reply #1 - Posted 2005-08-16 03:59:43 »

Write a class version number of some kind to the file as well. When you read it back from the file simply have a different method for each version of the class in question.

That is - you always have just 1 (for example) outputSavedGame() method, and only as many inputSavedGameV1_0(), inputSavedGameV1_1(), etc. methods as you want to support.

The other alternative is just not to change it, or set default values for certain variables - that is, if you read a class back from a file and it doesn't contain all the data you want, you just use defaut values for each of the blank class members. Does that make sense?

Using java.util.Properties makes this really easy, in my opinion. The java.util.Properties class will input/output as many or as few values as you need (easily scaling if you add/remove class members), and will probably cut down on program I/O Exceptions that might result from trying to do it all by hand, as you're doing now with ObjectInputStream and ObjectOutputStream.

Still another alternative, which (luckily) few commercial games have taken, is just forcing the player to start their game over again if they patch the program to a new version. It works, but it's an ass of a way to be as a developer.
Offline Jeff

JGO Coder




Got any cats?


« Reply #2 - Posted 2005-08-16 04:12:39 »

You can make Serialization accept differnt versions of the class by defining and setting the SerialVersionUID manually.

http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html#wp4100

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Jeff

JGO Coder




Got any cats?


« Reply #3 - Posted 2005-08-16 04:14:23 »

Note that if you are just serializing all your game structures, you may want to thinka bout what really doesnt need to be saved and mark  it with the Transient keyword to reduce the size of the output and the time it takes to save.

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #4 - Posted 2005-08-16 05:21:31 »

Okay, first of all thanks for the quick responses.

Now I've got some questions though.

While your method looks like it could work, kul-th-las, with the number of updates I'm doing now it would be a total pain in the ass, and I was hoping to not really need to do it so manually. I suppose I could always do that if I waited until the game was entirely finished first, but I'd like level development to start before everything is done, so that's not very possible. I looked at the Properties some... it seems to be a way just to save specific variables en masse, like I simply plug in a stream followed by the variable I want to save and it will save it for me, then I can access it later? Then does this completely eliminate OIS and OOS? Sorry about my questions – never used it at all before.

As for Jeff's link: a lot of the stuff in that link is pretty confusing, but regardless – it sounds like you are saying I can manually keep the signatures the same, thereby making them readable at all times... but wouldn't this cause some issues if I drastically changed things?

From what was mentioned, it looks like Properties is that way to go. I'll do a search on the forum as I'm sure many people have already asked about this.

See my work:
OTC Software
Offline Jeff

JGO Coder




Got any cats?


« Reply #5 - Posted 2005-08-16 06:04:07 »


As for Jeff's link: a lot of the stuff in that link is pretty confusing, but regardless – it sounds like you are saying I can manually keep the signatures the same, thereby making them readable at all times... but wouldn't this cause some issues if I drastically changed things?

Yes and yes.

You are taking repsonsability for handling changes.  When the class is de-serialized, for any fields you have removed  the corrosponding data in teh serialized block will just get ignored.  For any fields you add, it will not get set. Moral is... have good default values for all your fields!  Seond moral is, don't chnage the name of a field 'cause thats the equivalent of deleting the old one and creating a  new one.

Thrid moral. If the class is changing so much that ity just doesnt make sense to reuse ANY of the old values, then change the UID and scrap the save Smiley

TANSTAAFL Smiley

Quote
From what was mentioned, it looks like Properties is that way to go. I'll do a search on the forum as I'm sure many people have already asked about this.

Properties are the equivalent of a Win32 registry. Its great for saving a few values, though really you could just dump those to a  file and read them back in about as easily.

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline c_lilian

Senior Member


Projects: 1


Java games will probably rock someday...


« Reply #6 - Posted 2005-08-16 06:27:32 »

You can also have a look at the Externalizable interface : you do everything by hand, but at least, you can easily manage what you load, save, in which format, and even specify default values.

Lilian

Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #7 - Posted 2005-08-16 17:09:25 »

Ooh, I like Externalizable. Simple and definitely more of what I was looking for. Manual control is actually more or less what I wanted to be able to do. This way makes it much much easier.

Thanks. Cheesy

See my work:
OTC Software
kul_th_las
Guest
« Reply #8 - Posted 2005-08-16 21:40:18 »

Yeah, Properties is basically equivalent to the registry. I figured it'd be good for saved games (that is, if you're just saving, for example, a one player's stats). But if you need to dump the entire state of the game, or the current state of several game levels to disk, then Properties is definitely not the way to go.

Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #9 - Posted 2005-08-17 03:19:23 »

Yeah, I use it to save entire levels. Then I also use it for save files, which include every level the player has ever been to as levels are not reset when you leave them. Probably too much for Properties.

I've been changing over to Externalizable, but I find myself with a problem. Say I add in a new Boolean that is written to the file. How can I make it know that when reading an old version to set the boolean to a default value, rather than scanning and not finding what it expects? Should I use the serial version?

See my work:
OTC Software
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Jeff

JGO Coder




Got any cats?


« Reply #10 - Posted 2005-08-17 05:00:46 »

Yeah, I use it to save entire levels. Then I also use it for save files, which include every level the player has ever been to as levels are not reset when you leave them. Probably too much for Properties.

I've been changing over to Externalizable, but I find myself with a problem. Say I add in a new Boolean that is written to the file. How can I make it know that when reading an old version to set the boolean to a default value, rather than scanning and not finding what it expects? Should I use the serial version?

Well, now your facing the issues that the writers of ther default serialization code did Smiley

What they do is to save a name/value tuple for every non-transient field.  That way the data is not field order specific.  As its read back in its destination field is found absed on the field name in the data.

Anything not set is, by default, left at the default value it was set  on when the empty constrcutor was invoked to create the object.

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline c_lilian

Senior Member


Projects: 1


Java games will probably rock someday...


« Reply #11 - Posted 2005-08-17 05:35:23 »

Yeah, I use it to save entire levels. Then I also use it for save files, which include every level the player has ever been to as levels are not reset when you leave them. Probably too much for Properties.

I've been changing over to Externalizable, but I find myself with a problem. Say I add in a new Boolean that is written to the file. How can I make it know that when reading an old version to set the boolean to a default value, rather than scanning and not finding what it expects? Should I use the serial version?

You can put your field in a HashMap, then save that map. It will allow for future structure update and default values (when map.get() returns null).


Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #12 - Posted 2005-08-17 06:03:38 »

Well, now your facing the issues that the writers of ther default serialization code did Smiley

What they do is to save a name/value tuple for every non-transient field.  That way the data is not field order specific.  As its read back in its destination field is found absed on the field name in the data.

Anything not set is, by default, left at the default value it was set  on when the empty constrcutor was invoked to create the object.
Okay, that makes sense. I see how it works more clearly now.

Quote from: c_lilian
You can put your field in a HashMap, then save that map. It will allow for future structure update and default values (when map.get() returns null).
Oh yeah, duh. I already had it saving to a LinkedList, anyway. Don't see why I was even thinking I would do it step-by-step with multiple streams.


Thanks for all the great help on this, guys. I'm really happy I happened to stumble on this forum.

See my work:
OTC Software
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #13 - Posted 2005-08-18 18:38:42 »

Well I'm rather annoyed. I converted everything to Externalizable (which took 3 hours), and successfully made it possible for the game to save and load everything as Externalizable's rather than Serializable's.

But, regardless, it STILL yells at me and breaks with different serial id's! Do I need to manually override that now? I thought it was supposed to not care about that anymore?

*grumble*

So pretty much it seems I must use what Jeff posted to tell it to stop caring, which seems incredibly redundant to me if I am already doing it all manually anyway.

See my work:
OTC Software
Offline Jeff

JGO Coder




Got any cats?


« Reply #14 - Posted 2005-08-18 21:21:20 »

Well I'm rather annoyed. I converted everything to Externalizable (which took 3 hours), and successfully made it possible for the game to save and load everything as Externalizable's rather than Serializable's.

But, regardless, it STILL yells at me and breaks with different serial id's! Do I need to manually override that now? I thought it was supposed to not care about that anymore?

*grumble*

So pretty much it seems I must use what Jeff posted to tell it to stop caring, which seems incredibly redundant to me if I am already doing it all manually anyway.

Yes.  Externalizable turns of the default serialization of the streams, but the mechanism that understands what class msut be re-created is left untouched and thats where the versionUID check is.

Sorry.

JK

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline Jeff

JGO Coder




Got any cats?


« Reply #15 - Posted 2005-08-18 21:22:00 »

FWIW I just discovred that Eclipse has a one-click way to create the SerialversionUID field ona Serializable class...

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #16 - Posted 2005-08-19 03:49:40 »

Nice of you to mention how...

 Huh

See my work:
OTC Software
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #17 - Posted 2005-08-19 04:14:03 »

For anyone who might find this post in a search who is trying to do the same thing, here is a quick how-to.
In Conclusion:
ObjectOutputStream can still be used to save to files, it just needs to writes Externalizable's instead of Serializable's. A Serializable will automatically write all the data to a file, but it will make it impossible to use a file that was saved after modification of a class. An Externalizable gives you direct control over what is saved to a file, therefore enabling default values for new fields or ignoring deleted ones. Furthermore, you can make smaller save files by specifying exactly what needs to be saved and ignoring what doesn't.

I made a class called Writable that became the superclass for any class that is saved to a file. Writable implements Externalizable, and is provided below.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashMap;

/**
 * @author Eli Delventhal
 *
 * This class is the superclass of anything that will ever need to be written to a file.
 * This includes practically everything in the game. The class includes all the methods needed
 * to write to a file as an Externalizable, and provides the means for subclasses to specify
 * exactly what is written and read to and from the files.
 */

public abstract class Writable implements java.io.Externalizable
{
   //Set the version ID as something constatnt so it can always read the file
  //This variable can be absolutely anything you want, as long as it remains constant
  private static final long serialVersionUID = 3487495895819393L;
   
   //Reads from a file, required by Externalizable
  public void readExternal(ObjectInput input)
    {
          try
      {
             loadFile((HashMap) input.readObject());
      }
          catch (Exception e) {System.out.println("Error reading " + this + "! " + e);}
         
    }
   
   //Writes to a file, required from Externalizable
    public void writeExternal(ObjectOutput out)
    {
          try
      {
             out.writeObject(setFile());
      }
          catch (Exception e) {System.out.println("Error writing " + this + "! " + e);}
    }
   
    //A sublass that is loaded from a file must override this method
    //in order to specify exactly what is loaded from the file
    public void loadFile(HashMap map)
    {
          //implemented by subclasses
    }
   
    //A sublass that is saved to a file must override this method
    //in order to specify exactly what is saved to the file
    public HashMap setFile()
    {
          return new HashMap();
    }
}

Then, within each sublass of Writable (whatever is written to the file), override the last two methods in order to specify what is saved and loaded. Make sure to call the superclass version of the method as well, in case you have a class hierarchy. An example is provided below.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
    //Sets the values inside the passed HashMap to the appropriate members
    public void loadFile(HashMap map)
    {
          String nameVar = (String) map.get("name");
          String imageFolderVar = (String) map.get("imageFolder");
          Integer valueVar = (Integer) map.get("value");
          Integer weightVar = (Integer) map.get("weight");
          if (nameVar != null)
             name = nameVar;
          if (imageFolderVar != null)
             imageFolder = imageFolderVar;
          if (valueVar != null)
             value = valueVar.intValue();
          if (weightVar != null)
             weight = weightVar.intValue();
          super.loadFile(map);
    }
   
    //Creates a HashMap to be saved to a file
    public HashMap setFile()
    {
          HashMap map = new HashMap();
          map.put("name",name);
          map.put("imageFolder",imageFolder);
          map.put("value",new Integer(value));
          map.put("weight",new Integer(weight));
          map.putAll(super.setFile());
          return map;
    }

Make sure to set the SerialVersionUID in each and every class that is written to the file. Annoying, yes. Neccessary, yes. Also, each and every saved class must have a default no argument constructor. This is what Externalizable calls before it calls your loadFile method. You can use this to create any default values you might need. Say, for example, you add in a Color field and want any older versions of the class loaded to start with Color.RED, while normally within your coding the class is constructed with a Color parameter, you simply set this in the default constructor. The purpose of the "if (so-and-so != null)" is for if an older version of the class never wrote that to the file. You want to use what was already set in your default constructor rather than setting it to the null that the HashMap will return.

And that's pretty much it. Go ahead and use any of that code all you want, I hope this thread and this post helped anyone who might stumble on this thread.

Also, a big hats off to Jeff, c_lilian, and kul_th_las. Thanks a lot for the help and prodding me through this, guys.

See my work:
OTC Software
Offline Jeff

JGO Coder




Got any cats?


« Reply #18 - Posted 2005-08-22 03:46:55 »

For anyone who might find this post in a search who is trying to do the same thing, here is a quick how-to.
In Conclusion:
ObjectOutputStream can still be used to save to files, it just needs to writes Externalizable's instead of Serializable's. A Serializable will automatically write all the data to a file, but it will make it impossible to use a file that was saved after modification of a class.

Hmm my experience is just the opposite.

Serializable IF you set the SerialVersionUID dopes a rpetty good job of reading data back and settign what it can, leaving the fields it cant set unset.

Externalizable emans YOU have to create a scheme to do that,.  Most scehems used by msot peoepl infact will not worlk if the fields change without a lot of custom twisting of the input routine to handle the specific changes in question.

Oh, and in Eclispe all you need to so is declare the class externalizable and an exclaimation mark will appear next to the class declaration in the left column,.  Click it and it will give you options for autogenerating the SVUID.

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #19 - Posted 2005-08-22 18:26:14 »

Hmm interesting. So you actually have had little issues with Serializable having problems if you set the UID manually? So it fills in default values on its own?

While it is very nice to get everything done manually, it does in fact load files slower, which could become a big issue. On this powerbook G4 it went from a half second load time per level to a 2 second load time when using externalizable. So I suppose it really is dependent upon how many changes you are expecting to make in your classes. I'll stick with externalizable at this point because I still might make many drastic changes in my classes, but later I will probably switch it all back to serializable in favor of faster load times.

See my work:
OTC Software
Offline chase

Innocent Bystander





« Reply #20 - Posted 2005-08-24 22:29:36 »

There is something in the standard Java API to do exactly what you want, the catch is that it's for Java Beans style class. Take a look at the java.beans.XMLEncoder. One of the things it was designed to solve is the version problem with serialized Swing objects but it's also got some other cool stuff.
Offline Jeff

JGO Coder




Got any cats?


« Reply #21 - Posted 2005-08-26 05:51:16 »

Hmm interesting. So you actually have had little issues with Serializable having problems if you set the UID manually? So it fills in default values on its own?

Serialization invokes the default constructor to create the object. As long as you einitialize the field in either its declaration or the default constructor, then yes by definition its handled.


Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline Eli Delventhal

JGO Kernel


Medals: 42
Projects: 11
Exp: 10 years


Game Engineer


« Reply #22 - Posted 2005-08-26 17:08:32 »

Okay, well then maybe I'll just stick with keeping the same UID like you mentioned in the first place.  Roll Eyes

See my work:
OTC Software
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Dwinin (21 views)
2014-09-12 09:08:26

Norakomi (55 views)
2014-09-10 13:57:51

TehJavaDev (66 views)
2014-09-10 06:39:09

Tekkerue (33 views)
2014-09-09 02:24:56

mitcheeb (54 views)
2014-09-08 06:06:29

BurntPizza (38 views)
2014-09-07 01:13:42

Longarmx (24 views)
2014-09-07 01:12:14

Longarmx (30 views)
2014-09-07 01:11:22

Longarmx (28 views)
2014-09-07 01:10:19

mitcheeb (37 views)
2014-09-04 23:08:59
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!