Game Development Community

Save game / saving and loading single player game thoughts.

by Clint S. Brewer · in Torque Game Engine · 02/07/2005 (9:49 pm) · 39 replies

I'm making a single player game with some rpg elements.
I'm about to add the save game part right now and would appreciate some advice.

I've seen all the resources (I think), they look ok and were helpful, but definitely don't handle saving and loading the entire game. I'm hoping I can do this will a little less work.

I was thinking about:

save:
just saving out the mission / missioncleanup / relevant groups to a file.

load:
just pretend you are loading a new mission and load that file you saved out.


problems:
1. make sure that everything is in those groups that I plan to save out.

2. I store handle references some places, so I'll have dynamic variables set up like myStatic = "1897" this won't work since I assume it'll be very possible that handle number will change?

solutions:
For 1, it's not really a problem, have to do this for things anyway

for 2. I was thinking perhaps if I used names instead of handles and made sure things had unique names then it would be ok...

seems to me like someone else must have run through this and come up with a nice solution before. Of course I can just roll my own and loop through the objects saving things as I see fit, but maybe I can get this mission saving type of save game to work.

any advice?

thanks,
clint
Page «Previous 1 2
#1
02/07/2005 (10:21 pm)
Sorry this is kind of rambling as I'm working the idea out.

more problems
I also do some interesting things for some objects. For instance when a particular type of object is created, it creates a trigger and adds it to the mission group.

so then that trigger would need to not be saved out.


--------------
roll my own save file is sounding more appealing by the moment.


Here's what I'm probably going to do when it comes time to load a game.

1. load mission as normal,

2. load and parse custom save file
for each object see if it already exists and if it does then update it's state with what's saved. Otherwise, create the object with the saved info.

most things will be updating existing objects, but for spawned monsters etc they will have to be created at the appropriate spots and updated with the appropriate state info.


If there was some gaurantee that the handle's can be re-used in another game that would be nice...but I think the randomness in my game will make that impossible.
#2
02/07/2005 (11:19 pm)
Hey Clint,

I messed around with something along the lines you mention a month or so back. I wasnt really doing it for anything other then idle interest so I only spent an hour or so on it, but here's what I did:

Working off the original Mission stuff, there's the usual MissionGroup etc except I added a PersistGroup. This group is saved out to a file using the existing group export functions (PersistGroup.save();) whenever the mission ends, and is loaded whenever the mission is loaded. Any objects added to the game that need to be saved are added to PersistGroup instead of MissionGroup when they're added to the world. In the MissionInfo script object in the mission there is a new field (persistFile) which gives the filename for the persistant group to be saved in. The nice thing about this is its really just a few lines of code, and loading the persistant info is simply a case of an exec().

My prototype worked in both single player and multiplayer modes IIRC. It's also trivial to save additional information in the script, for example player name, stats, etc.

Re: your problems:

If you store pointers to other objects they wont get saved/loaded properly. One way around this is to simply not store object handles, another is to create a new (and probably game specific) export function that deals with it properly. I dont think it would be too hard to fit that into the existing object saving framework, provided you dont mind digging into the C++ code. Unfortunately, it's game specific things like that that make it hard to write a generic save game function :)

One more caveat is that since the save file is just a script, its easily modifyable. One quick and dirty solution would be to just compile the script when you save it then delete the .cs, which would probably be effective enough and not take more then 5 seconds to write.

I can probably go through and dig out the code (all script) if you want it.

T.
#3
02/08/2005 (4:35 am)
Ok, I'm also doing a single player game with some RPG elements. Saving/Loading is one of my biggest challenges and I worked on it for a few days some three months ago using a database but didn't finish it yet so don't know if I'll end up using this system. I'll explain how I did it below.

First of all I'd like to say that I never messed with MissionGroup stuff simply because I thought that it wouldn't be enough. I mean, you also want to store stuff like various global variables, inventory arrays, possible game notes or journal, quest conditions, etc. Those things would't be in MissionGroup would they? I could be wrong though.

So what I did was I applied the SQLite resource, then I created a table so that I can store variables and their values.

Then I created a save game GUI and a load game GUI. So when you type a name and hit save, I store all the variables that I choose in the database including a current screenshot, time and date.

Now when you choose to load a saved game, I do a disconnect() and load all the variables from the database.

After initial tests, it worked fine for simple things like player position/direction but don't how it's gonna end up when I start thinking about more complex stuff like the ones mentioned above. It was a great learning experience though.

And one of the reasons I chose this path is because I didn't want to store game variables in clear text format but Tom's way in the above post seems like a great idea!

Nick
#4
02/08/2005 (9:15 am)
@Tom

thanks for the info, it's good to hear that this sounds do-able. I'll work more with it today and see how it goes.

Quote:I can probably go through and dig out the code (all script) if you want it.

s'ok sounds pretty straight forward.


@Nick
Quote:First of all I'd like to say that I never messed with MissionGroup stuff simply because I thought that it wouldn't be enough. I mean, you also want to store stuff like various global variables, inventory arrays, possible game notes or journal, quest conditions, etc. Those things would't be in MissionGroup would they? I could be wrong though.

they could be, all of those are dynamic properties that will get saved out (and loaded as well I presume) currently tge1.3 has player inventory as part of the player datablock PlayerBody which is part of the DataBlockGroup so it would not be saved out with the MissionCleanup or MissionGroup, but that could be changed.

instead of global variables you could use a global script object of some sort that has dynamic fields. When you create the new script object add it to the persistgroup that Tom was talking about and then it should be saved and loaded as well.

for instance here's one I had, using one of the problematic handles though
new ScriptObject(AIBotManager) {
         Player = "1896";
   };



so here is my plan:
1. make sure that anywhere I was using handles I use names
1.5 make sure everything has a unique name, maybe just generate it from the handle
"obj_" @ %obj

2. create PersistGroup just like MissionCleanup
3. make sure everything that should be saved gets added to the PersistGroup
4. make sure that PersistGroup gets deleted just like MissionCleanup
4.5 run arround pick up stuff and save a game out
5. add loading functions that bypass normal creation of game stuff and exec's the saved game file after the mission file is loaded

mm hmm
#5
02/08/2005 (9:58 am)
Quote:currently tge1.3 has player inventory as part of the player datablock PlayerBody
I take that back, inventory is stored in the player object, it's just the maxInventory variables that are in the datablock. so curernt inventory does get saved out as part of missioncleanup if you were to save it out.
#6
02/08/2005 (10:45 am)
1.5 make sure everything has a unique name

make that, make sure that anything I will need to save and load a reference to has a unique name.
here is how I'm doing this right now:

function TotemSpawner::onAdd(%this,%obj)
{
	if(%obj.getName $= "");
		%obj.setName("totSpawn" @ %obj); //make a name out of the handle so that
									 // we can use the ref for save and load
}

so when you add one of these objects to the mission in the mission editor it will generate a unique name. When you save the mission out it will save that name, and when you load the mission it will use the name that it generated before, not generating a new name...at least that's the plan.
#7
02/08/2005 (11:07 am)
Ack.

I think this is one of those times where I could either

A. do all the work to get a nice elegant solutions saving out to mission files and execing them making sure my references line up etc...

B. just save out the info I need to my own format and let the random things be regenerated randomly when you load and get on with making my game

B it is.
#8
02/08/2005 (11:26 am)
Sounds interesting Clint !
I never figure out how i should approach this issue yet .
I have rare objects in my game and need some kind of save function.
But in my spiderweb game all objects is needed to enter the last level.
Hope i find some kind of solution when i get there.
#9
02/08/2005 (12:37 pm)
Nick,

I think perhaps "MissionGroup" is confusing the issue a little. There is nothing special about MissionGroup. It's just global SimGroup that's managed by the default scripts. The mission editor uses the save() method of SimGroup to save the mission, which simply dumps the contents of the SimGroup to a cs file. The same approach can be taken for pretty much any data you want. e.g.

new SimGroup(FoobarGroup)
{
};

...

%foo = new ScriptObject()
{
   bar = "foo";
};

FoobarGroup.add(%foo);

...

FoobarGroup.save("foo.cs");

...

exec("foo.cs");

Needless to say, it doesnt *have* to be tied into mission loading. I was creating a mini persistant world so in my case I wanted the mission to be persistant and thus I tied my save/load code in there.

The other option is to look at the prefs saving code (script). If I remember correctly there's a script function named something along the lines of export() which dumps a variable to a .cs file. You could combine the two to get all needed info saved out.

All that said, I would wager that RPG type games may benefit more from a database approach similar to the one you are taking. It all totally depends on the game, hence why there's no save/load code in stock TGE.

Clint,

My advice is go with whats easiest and get on with making the game. If you run into difficulties later down the line then it's a pretty simple job to replace the save game stuff with something better, especially if you're careful now when writing the code. I would also suggest you have a read through the save() code in SimGroup ... its pretty simple to understand and would probably also be a good base for saving to your own format should you decide to go that route.

Good luck :)

T.
#10
02/08/2005 (1:56 pm)
Ok, while I have already put tens of hours into creating this database saving/loading approach, I think I'm gonna give the MissionGroup/PersistGroup style a try. I'm willing to dump the database if this turns out to be a simpler approach. The game is not a true RPG, but a 3rd person with some RPG elements so it might be enough.

See, my problem was that I didn't want to store variables in clear text format but didn't know you can dump them in a .cs file and then complile it to binary. Just one last question though, if you open up the prefs.cs.dso file and alter some variables (text is still viewable) would they take effect in game? I tried changing the resolution but got reverted back.

Nick
#11
02/08/2005 (2:52 pm)
Nick,

I think that the group thing is so simple it doesnt take much time to investigate whether its going to work better or not. Who knows, maybe your database solution will turn out to work better after all ? :)

As for dsos ... if you edit it, yes those changes will take effect provided the end result is a valid .dso file. Probably the best way to experiment with that is to create a simple .cs file that contains one variable, e.g. $Bla = 354; and try execing it to compile it. Then rename the .cs and modify the .dso, re-exec and then echo($Bla) to see if the change took effect. The same goes for SQLite, though ... anyone could download the sqlite command line client and access your .db files, with probably a lot more ease then hacking .dsos.

Its pretty simple to scramble the .dso format enough that other copies of Torque cant read it and make it harder to read/edit in a hex editor. This would just be a case of modifying the console to mutate the fileformat when saving it, perhaps just a simple XOR with some value, and recover the original format when loading.

T.
#12
02/10/2005 (8:17 am)
Ok after messing it for about 45 minutes, here are my findings:

I'm going to use the PersistGroup way. Yay!
I tried saving ScriptObjects to file (i.e. Data.cs) by doing something like you said and worked fine.
Ex: new SimGroup(PersistGroup)
{};

PersistGroup.add(%foo);
PersistGroup.save(Data.cs);

Then I compiled that file to .dso and tried to alter a variable in the .dso to see if it's gonna work. Turns out that it screws up the SimGroup and won't load it. In fact I just opened it up in Notepad and Wordpad and simply saved it without changing anything and it still didn't load.
I'm sure there's a way but that's good enough for me. I can even scrabble it so I'm not worrying about that anymore. That's what I with the variables in the .db.

However, saving to a SimGroup does not let you save simple variables so I'll just use the export() function like you said.

I think my saving/loading worries are finally over. I really thank you and Clint's contribution for your help.

Nick
#13
02/10/2005 (3:19 pm)
Glad to hear it works well for you :) The simple solutions are often the best :)

T.

Edit: Forgot to mention, editing the file in wordpad/notepad/any other text editor wont work at all as it will convert the file to text format, namely it'll fudge anything it thinks are line endings (0x0a/0x0d). A more acid test is to use a hex editor, but like you say you can just scramble it so it doesnt really matter.
#14
02/10/2005 (3:57 pm)
This is heavy !
Must be the language barrier .

@Tom ,Nick,Clint
Any of you , can you make a simple example.
My head get messed up with all translations only :)
#15
02/10/2005 (4:51 pm)
@Billy,
I'm still in the middle of making all of this work, save game, load game, character creation.
I'll have to post more info when I'm done, can't say which will be best for my game.

at it's core though the idea is to simply save out a simgroup just like you would a mission file

(from Tom's example above)
FoobarGroup.save("foo.cs");

then you can just exec that file to re-create the simgroup's members in the game
exec("foo.cs");


try it with MissionCleanup and see what it looks like.
MissionCleanup.save("missionCleanupSimGroup.cs");


so, if you were to put only things that need to be recreated in the simgroup, you can save it out, then later exec it to recreate them.

make sense?
#16
02/10/2005 (5:16 pm)
Thanks Clint !!

Short and Clear .
I think i understand the basic part now.
Time for some testing .
#17
02/11/2005 (8:01 am)
This is working great and i can tweak it so it works in my game.
I cant thank you guys enough.

-Billy
#18
02/11/2005 (11:08 am)
So my next related question is how do you reccomend handling things that need to be _removed_ from the normal missiongroup?

for instance, say I have a magic sword, that I placed in the mission editor, saved out the mission file..then in the game my character picks it up. There can be only one so next time I need to not create the sword....hmmm I guess if I just added the sword to the persist group originally instead of to the mission group then that would work. I could modify the mission editor to keep two sim groups instead of just the one...I don't know.
ahhh I guess this is what you were talking about here Tom:
Quote:In the MissionInfo script object in the mission there is a new field (persistFile) which gives the filename for the persistant group to be saved in. The nice thing about this is its really just a few lines of code, and loading the persistant info is simply a case of an exec().

so in the editor for things that will need to have their state saved with the game, I could just add a checkbox in the mission editor and check it.
#19
02/11/2005 (11:32 am)
Something to be careful about when messing around with "groups":

There are two primary "container" objects in TorqueScript: simSets and simGroups. simSets are the basic level, and objects can be included in multiple simSets with no issues.

However, simGroups are a special derivative of simSets: An object may be a member of one and only one simGroup at one time. If you try to add an object to a second simGroup, the code will silently remove the object from any other simGroup it was previously within. For example, MissionCleanup() is a simGroup, because the entire purpose of the MissionCleanup group is to guarantee that all objects within the group are deleted when the group is deleted. simSets do not have this special property.

Moral of the story: If you are starting to create new script "groups" for data management like this, be aware of the difference between the two types, and by default you should use a simSet, not a simGroup unless there is a very special reason for using the latter.
#20
02/11/2005 (2:24 pm)
SimGroups, when deleted, kill all their contents. SimSets do not.
Page «Previous 1 2