I love the smell of UnrealEd crashing in the morning. – tarquin
UltimateSaveSystem is a ready-to-use solution for writing and reading variables from UnrealScript or Kismet into and from binary files, using unique names to find the value in the file. It is capable of saving Integer, Float, Boolean, Vector and String variables (with unlimited length).
The DLL creates a separate file for each data type set, since it's easier to handle that way. This doesn't have any effect on the usability of the code. If the save name is "Test", the file you save is a float and the extension is the default one, then the resulting file will be named "Test_f.sav". Files are saved in the Binaries\Win32\UserCode folder.
This page will give you a quick guide through the contents of this package. Afterwards will I give you a quick example of how to use this UltimateSaveSystem to spawn the player at a checkpoint he touched in a previous session when he starts the game the next time.
The DLL-side should be irrelevant for most people here and the others can probably find out more by simply reading through it, so I will only talk about the Unreal side here.
- 1 Main Class
- 2 Kismet SeqActions
- 3 Tutorial: Using the Kismet-parts of this SaveSystem
- 4 Download
- 5 Permissions
- 6 Credits
The heart of this pack is the UltimateSaveSystem class. It provides the functionality to communicate with the DLL and can be referenced from any point in script to save or load values. All other classes in this pack rely on it as well. It basically consists of the DLLImport functions and their UScript pendants as wrapper functions. You should always call the wrappers and not the actual DLLImports.
There are 9 different operations that can be executed in the DLL and each of these 9 operations exist for each of the 5 supported data types plus one that works for all data types plus one additional static function, giving you 47 functions in this class.
Let's start with the static function: GetSaveSystem()
This one will simply give you a reference to the UltimateSaveSystem instance so that you can reference it for easy use in other classes. If the Actor doesn't exist yet, a new one is spawned and returned. Same as WorldInfo.static.GetWorldInfo() would do.
Now we come to the functions that communicate with the DLL. Most of them simply call the actual DLL-function, but the String implementation has some special implementation that allows it to save and load strings of unlimited length. You should also pay attention that the Bool functions use Int as argument, where 0 is False and Non-Zero (usually 1) is True.
X is the name of variable type here. (Int,Float,Bool,Vect,String)
Import*X*(Value, PropertyName) will load a name-value pair of type X into a dynamic array inside the DLL. The entry is in the array now, but it's not yet saved (this is for optimization purposes because you generally only need to save once after importing many stuff). If an entry with the given name already exists in that array, only it's value will be changed. No return value for this function.
Save*X*List(SaveName) will save the whole array in the DLL into a file with the specified name. The different files are independent per data type and list, so if you add entries to a Bool and Int list, you have to save both lists manually. True is returned if the file could be saved/created successfully.
Load*X*List(SaveName) will load an array from the file with the matching name into the DLL. A loaded list will stay valid until a new map is loaded or you load a list of the same type from another file. True is returned if a file with the save name was found.
Export*X*(PropertyName, out Value) will look for an entry with the matching property name and returns the associated value as out parameter. True is returned if an entry with the specified name was found (you can also use this as test to see if a certain name-value pair already exists).
Delete*X*(PropertyName) will look for an entry with the matching property name and remove it from the array. The array needs to be saved manually afterwards. True is returned if an entry was found.
DeleteAll*X*(SaveName) will delete the whole file that holds this data type. A new one will be created as soon as something needs to be saved again. Returns True if the file was deleted successfully.
DeleteWholeSave(SaveName) will call the function above for all data types and only returns False if no file for none of the data types existed. This is just helpful for a SeqAction.
The Load, Save and Delete functions come also in a variant that works with arrays:
Save*X*Array(Values, ArrayName) will call the functions above for each individual entry of the array passed as "Values" and save and load them with the name ArrayName[i], where [i] is the index of the element in the array. So if you submit an Int array with 3 entries and the ArrayName "Numbers", it will save the three values in the file with the names Numbers0, Numbers1 and Numbers2. Keep in mind that these are not specially distinguished from other names in the file, so you could accidentally overwrite old values if you saved on as "Numbers1", for example. But on the other hand can you also load individual properties of the array this way, if you know their index.
Load*X*Array(ArrayName, Values) will load the values that were saved with the above array-naming layout and return them as a real array.
Delete*X*Array(ArrayName) will delete all values named after the array-naming layout. As for the Load function, this will start at index 0 and will continue in consecutive order until no more name+index entries are found.
In the end I should mention that you can change the file extension of your saves in the DefaultProperties of this class, in case you're going to re-compile it anyway. The default extension is ".sav", but you are free to use any other or none at all, if you don't want people to know immediately what the file is actually about.
You need to have at least one of these in your whole Kismet sequence when using any of the other SeqActions! This simply takes the part of globally setting the name of the file to which you save or from which you read. A name will be active once the action gets activated and it is always the save name of the last activated SeqAction used.
When activated, it will save all variables plugged into it's slots to a file with the name from SeqAct_SetCurrentSaveName. You can plug up to 999 variables of a type into a SeqAction of this kind, but the variable needs to have a VarName set - it does't work with unnamed variables because the VarName is the one that is saved in the file with the value. But in any case should you use a NamedVariable (the specifc type) because multiple "normal" variables with the same name may cause trouble later.
The SeqAction will activate the Success output if everything went well and the Failure output if at least one variable could not be saved for whatever reason.
When activated, it will load the values of all variables with VarNames plugged into it's slots from a file with the name from SeqAct_SetCurrentSaveName. You can plug up to 999 variables of a type into a SeqAction of this kind.
It will activate the Success output if everything went well and the Failure output if at least one variable could not be loaded for whatever reason (for example it wasn't saved in the file yet).
When activated, it will delete all variables with VarNames plugged into the slots from a file with the name from SeqAct_SetCurrentSaveName. You can plug up to 999 variables of a type into a SeqAction of this kind.
It will activate the Success output if everything went well and the Failure output if at least one variable could not be deleted for whatever reason (it didn't exist in the file, for example).
This has 6 variable links and each of them accepts a single unnamed boolean variable. It will get rid of a whole file of the data type whose link has a boolean variable with True value.
The sixth link is labeled All and is a workaround. If you use the other links alone, the Failure output will be activated if at least one data type that has a True link did not exist yet. The All link will only activate the Failure output if nothing with the current file name existed at all.
The Value SeqActions differ from the Variable SeqActions in that the latter require variables with VarNames while the first can save the value of an unnamed variable of whatever type (one of the 5 supported types) plugged into the Value link under the current file name with a property name defined by up to 999 String variables plugged into the Name link.
If you plug a plain float variable with value 15.0 into the Value and two string variables with "CountA" and "Result" into the Name slot, the SeqAction will save CountA and Result both with value 15.0 in the file.
Failure is activated, if the value could not be saved.
Again, this doesn't require a named variable but uses a single string to determine the name of the property to load. The type of the property is determined by the single variable that you plug into the Value slot.
Failure is activated, if the value could not be loaded.
This only takes up to 999 string variables into it's only slot. It will then delete the entries from the file of a fixed file type, which is set in the properties of this SeqAction.
Failure is activated, if at least one entry did not exist in the file of the specified data type.
Tutorial: Using the Kismet-parts of this SaveSystem
At first we need to get familiar with the test map:
The player spawns regularly at the PlayerStart at the bottom. Furthermore we have a Trigger and a PathNode on each of the smaller platforms, one Trigger on the bigger platform and a DynamicPointLight at the center of the map. The other PathNodes are not important.
Now let's take a look at how to set up the checkpoints:
As you can see, we get the location and rotation of the PathNodes on the platforms. The PathNode basically acts as additional PlayerStart, just that the engine doesn't recognize it as that yet.
The float values that I saved there too is just for demonstration and testing purpose and doesn't have any effect on the checkpoint system.
The next step is to handle the spawning of the player:
We use a workaround at this point: the location of the player can't be just changed by using Set Actor Location. We need to use a Teleport for that, but that only accepts Actors as destination and not vectors. The solution is to use Set Actor Location on a dynamic Actor (bStatic = False), a DynamicPointLight in my case so that we can clearly see that it moved.
The end of this sequence belongs to the demonstration with the float again. If the player touched the Trigger on the right platform, the value of MyFloat in the file will be 1.0 and thus will the Compare Float activate the "Got MyFloat!" message at the end. If the player touched the Trigger on the left, the value of MyFloat will be 0.5, which will activate the "Fail Float" message.
We are actually done at this point, but I should also show how to reset the whole stuff to the initial state again:
Nothing easier than that. I could also have used a "Delete Variable" SeqAction and pass it the individual variable references, but this one is more efficient in this case - it radically sweeps the whole files from the disk.
There are more SeqActions that were not discussed in this tutorial, but you should be able to figure them out yourself. With some creativity can you achieve a lot of stuff with this SaveSystem.
Mediafire Download (3.55 MB)
This zip package contains the compiled UnrealScript files (.u) and all necessary compiled DLL files (.dll, .exp, .ilk, .lib, .pdb), as well as the source code of the UnrealScript files (.uc in the Development/src/UltimateSaveSystem/classes folder) and the Visual C++ 2008 project folder with the source code for the DLL.
Mediafire Download (19.69 KB)
This package contains the map I used for testing. You can give it a shot yourself and experiment, if you like.
The content of this package is provided under the terms of the Creative Commons Attribution-Share Alike 3.0 license. In short does that mean, that you may freely use this for commercial and non-commercial projects as long as you provide credits (see the UltimateSaveSystem.uc file for reference about to whom and how credit needs to be given in this particular case) and provide any modifications you perform on the code in this package under the same or a compatible license. If you don't apply changes to the code, it is enough to just point people who are interested in this SaveSystem to this page.
The purpose of these terms in a nutshell: Be nice guys and give credit where it's due and if you happen to develop some improvements for some part of this SaveSystem, be fair and share it with everyone as I shared with you. It would be very kind if you could write me a message in such a case so that I can incorporate it into this package to make it easy accessible for everyone. :)
Furthermore would it be kind if you notify me if you release something that uses this. Simply because I am curious (and for having at least some little hope that you don't forget about me in case you earn millions with your project). :p
But questions about this are preferably handled in the forum topic than in PMs.
- Me for the idea and coding.
- [WuTz!], creator of the wTech engine, for endless help on the C++ side of this (since I was a complete newbie to C++).
- Ayalaskin for providing the tutorial on the first DLLBind Save System on which this project is basing.
- Wormbo for useful hints when I couldn't find the mistakes again.