TIGERFORGE
Easy File Save
1.1
[[[Introduction]]] Easy File Save is a lightweight, easy and practical way to save and load your data in a file. It has been designed to be very easy and fast to use, but complete and powerful as well. {{{HOW IT WORKS}}} Basically, everything starts with the declaration of an instance of EasyFileSave class. This instance represents a file that can be written and read. The instance contains all the methods and functionalities for working with data and that file. With the instantiation, you can specify a file name. The file name is optional: if your project just needs one file only, you can omit the file name and Easy File Save will use a default predefined name. Instead, if you plan to use more files, you must specify a file name. {{{SYSTEM INTERNAL STORAGE}}} When you create an EasyFileSave instance, the internal storage is initialized. Practically, this storage represents the file content. This means that if you want to write a file, you have to fill that storage with data. Then, through the Save() method, the storage is transferred into a file. Similarly, when you read a file, the whole file content is transferred into the storage, in a well-organized structure easily accessible. {{{WRITING}}} The writing process consists of transferring the internal storage into a file. This means that firstly you have to fill the storage with the data you want to save. Filling the storage is very easy: you have just to use the Add() method specifying the data to save and a unique id to find it later after you loaded the file. After that, simply calling the Save() method, the file is filled with the storage content. `void Save() { -// Instance declaration. -EasyFileSave myFile = new EasyFileSave(); -// Internal storage filling with some data. -myFile.Add("name", "Conan"); -myFile.Add("age", 30); -myFile.Add("has_sword", true); -// Saving. -myFile.Save(); }` {{{READING}}} The reading process consists of calling the Load() method, which will fill the storage with the file content. The storage is a simple dictionary where every single value is located by the unique id used during the writing process. Because the values are saved as object data-type, you must convert (cast) them in a proper way to have them in their original data-type. The instance comes with various Get methods that read and convert the values. `void Load() { -EasyFileSave myFile = new EasyFileSave(); -// Load() method returns true if everything is ok. -if (myFile.Load()) -{ --// Read the "name" value and return it as String. --string character = myFile.GetString("name"); --// Read the "age" value and return it as Integer. --int age = myFile.GetInt("age"); --// Read the "has_sword" value and return it as Boolean. --bool has_sword = myFile.GetBool("has_sword"); --// Dispose() method clears the storage in order to free the memory occupied by it. --myFile.Dispose(); -} }` {-{{NOTE}}} Data is always saved in binary format as to have a better writing/reading speed, smaller file size and a higher level of data protection. [[[]]] [[[CREATING AN INSTANCE]]] The first thing to do is creating an instance of EasyFileSave class. Usually, you do it once in your Script, for example in the variables declaration section or in a Start() function. {-{{DECLARATION}}} |-||[1]EasyFileSave variable_name = new EasyFileSave()||| |-||[2]EasyFileSave variable_name = new EasyFileSave(file_name)||| {{Parameter{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {file_name!string (optional)!The name of the file. If omitted, Easy File Save will use 'gamedata' as name.} `// Import TigerForge namespace for accessing to Easy File Save class. using TigerForge; public class Demo : MonoBehaviour { -// Declare the variable globally. -EasyFileSave myFile; -void Start() -{ --// Initialize the EasyFileSave instance. --myFile = new EasyFileSave(); -} }` `// Import TigerForge namespace for accessing to Easy File Save class.xx using TigerForge; public class Demo : MonoBehaviour { -// Declare the variable globally. -EasyFileSave myFile; -void Start() -{ --// Initialize the EasyFileSave instance and name the file 'my_game_data'. --myFile = new EasyFileSave("my_game_data"); -} }` [[[]]] [[[writing]]] When you declare an instance of EasyFileSave class, the system initializes also the internal storage. This storage represents the file content. It's a dictionary you have to fill with data. When the filling operation is completed, you have to just call the Save() method to transfer the storage data into the file. To fill the storage you have to use the Add() method. {{{.ADD}}} The Add() method adds a new, single value, to the internal storage.
|||variable_name.Add(key, value, ignoreExistingKey = false*)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value. This key must be unique and it used, during the reading operation, to get this value.} {value!object!The value to save.} {ignoreExistingKey!bool (optional)!By default, if the given key already exists into the 'internal storage', the existing key value is overwritten by the new value.
Set the optional 'ignoreExistingKey' parameter to true so as to prevent the reuse of an existing key. In this case, the original value won't be overwritten and a non-blocking warning will be thrown.} The Add() method can recognize different type of values and serialize them in the proper way: - in general, everything that is based on object type and can be serialized by BinaryFormatter;
- common data types: string, boolean, integer, float, bytes, etc.;
- common collections: list, dictionary, ecc.;
- common Unity data types: Vector2, Vector3, Vector4, Quaternion, Transform, Color, Color32, Rect;
- custom Class instances and collections of this Class (through a built-in dedicated serializer - see Writing Custom Classes paragraph for details);
- BoxCollider (through a custom Extension - see Extension paragraph for details). {{{.SAVE}}} The Save() method transfers the data collected into the internal storage into the file. This operation concludes the writing process and clears the storage to free the memory occupied by it. This method should be called at the end of the storage filling operation. |||variable_name.Save()||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {{Returned Value{}Type{}Description}} {true or false!boolean!The method returns true if the writing is completed without errors. Otherwise, it returns false.} `// It's supposed you have previously created a myFile instance. void SaveMyData() { -// Write some common data type value. -myFile.Add("name", "Conan"); -myFile.Add("age", 32); -myFile.Add("strenght", 122.5f); -myFile.Add("has_sword", true); -// Write a List of strings. -var equipment = new List<string>(); -equipment.Add("Hammer"); -equipment.Add("Knife"); -equipment.Add("Rope"); -myFile.Add("hero_equip", equipment); -// Write some Unity common type value. -myFile.Add("initialLocation", new Vector3(101.5f,-30.4f,22f)); -myFile.Add("player", gameObject.transform); -// Save the collected data into the file and clear the storage. -// Note that Save() method returns true (writing ok) or false (writing error). So you can use it inside an 'if' statement. -myFile.Save(); }` {{{.APPEND}}} The Append() method is similar to Save(), but it adds the internal storage content at the end of the file's existing content. If the file doesn't exist, Append() method simply works as Save() the first time. If the file exists, this method reads the file content and then add the storage at the end of this content. By default, if the file and the storage contain one same keys, the value of the file is overwritten with the storage's value. You can bypass this behavior setting the method's overwrite parameter to false. In this case, the storage's key is ignored and the file's value is preserved. |||[1]variable_name.Append()||| |||[2]variable_name.Append(overwrite)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {overwrite!boolean (optional)!true (default): file values are updated with storage values if they share the same key.
false: file values are preserved even if the storage contains same keys. } {{Returned Value{}Type{}Description}} {true or false!boolean!The method returns true if the writing is completed without errors. Otherwise, it returns false.} `myFile.Append();` [[[]]] [[[Writing custom classes]]] Easy File Save can save instances of custom classes and collections that use custom classes. Since the use of custom classes often results in the creation of complex data structures, EasyFileSave uses various methods to convert these data structures into simpler formats more suitable to be saved and loaded.| Two techinques are implemented at the moment: XML serialization and Binary conversion. Keep in mind that both have pros and cons. You should check if your custom class can be converted without issues or if it's too complex for a correct conversion. See a C# development manual to better undestand the limitations of those methods and do some test with your data. {{{.ADDSERIALIZED}}} The AddSerialized() method works exactly as the Add() method, but it convert the data through XML serialization, so as to turn the data structure into text format. |||variable_name.AddSerialized(key, data)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value. This key must be unique and it used, during the reading operation, to get this value.} {data!object!The data to save.}
`[System.Serializable] public class XXItemXX { -public string name; -public int quantity; } // It's supposed you have previously created a myFile instance. void SaveMyData() { -// Create a List of Item (custom class). -var items = new List<XXItemXX>(); -items.Add(new XXItemXX { name = "Gold", quantity = 15000 }); -items.Add(new XXItemXX { name = "Darts", quantity = 24 }); -items.Add(new XXItemXX { name = "Potions", quantity = 10 }); -// Add this custom List to the internal storage. -myFile.AddSerialized("items", items); -// Save the collected data into the file and clear the storage. -myFile.Save(); }` {{{.ADDBINARY}}} The AddBinary() method works exactly as the Add() method, but it convert the data through BinaryFormat, so as to turn the data structure into an array of bytes. |||variable_name.AddBinary(key, data)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value. This key must be unique and it used, during the reading operation, to get this value.} {data!object!The data to save.}
`[System.Serializable] public class XXItemXX { -public string name; -public int quantity; } // It's supposed you have previously created a myFile instance. void SaveMyData() { -// Create a List of Item (custom class). -var items = new List<XXItemXX>(); -items.Add(new XXItemXX { name = "Gold", quantity = 15000 }); -items.Add(new XXItemXX { name = "Darts", quantity = 24 }); -items.Add(new XXItemXX { name = "Potions", quantity = 10 }); -// Add this custom List to the internal storage. -myFile.AddBinary("items", items); -// Save the collected data into the file and clear the storage. -myFile.Save(); }` [[[]]] [[[Reading]]] When you declare an instance of EasyFileSave class, the system initializes also the internal storage. This storage represents the file content. This means that the reading operation will fill this storage with its content. As a result, you will have the internal storage with all the file values identified by their unique ids (the keys you used in the Add() method for writing data). Because the Add() method adds data as a generic type, when you read a value it's just an object and you have to convert (cast) it in the proper data type. You can get the object value and then manually convert it or you can use one of the built-in Get methods that convert the values in the most common types. {{{.LOAD}}} The Load() method fills the internal storage with the file content. It must be called before to start reading the values. It returns true when loading and storage filling is completed, so it must be used in an 'if' statement in order to avoid get value issues. |||variable_name.Load()||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {{Value{}Type{}Description}} {true or false!boolean!The method returns true when the reading process is completed without errors and the internal storage is filled with all the file data. Otherwise, it returns false.} {{{.GET* METHODS}}} The methods that can read the storage values have the name starting with Get. Basically, the name of the method specifies which type of data it will get. |||variable_name.GetData(key)||| |||variable_name.GetString(key)||| |||variable_name.GetBool(key)||| |||variable_name.GetInt(key)||| |||variable_name.GetFloat(key)||| |||variable_name.GetByte(key)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value.} {{Method{}Type{}Description}} {GetData!object!The value as object.} {GetString!string!The value as string.} {GetBool!bool!The value as boolean.} {GetInt!int!The value as integer.} {GetFloat!float!The value as float.} {GetByte!byte!The value as byte.} {{{.GETUNITY* METHODS}}} These methods get values that are Unity data types. |||variable_name.GetUnityVector2(key)||| |||variable_name.GetUnityVector3(key)||| |||variable_name.GetUnityVector4(key)||| |||variable_name.GetUnityQuaternion(key)||| |||variable_name.GetUnityColor(key)||| |||variable_name.GetUnityColor32(key)||| |||variable_name.GetUnityRect(key)||| |||variable_name.GetUnityTransform(key)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value.} {{Method{}Type{}Description}} {GetUnityVector2!Vector2!The value as Vector2.} {GetUnityVector3!Vector3!The value as Vector3.} {GetUnityVector4!Vector4!The value as Vector4.} {GetUnityQuaternion!Quaternion!The value as Quaternion.} {GetUnityColor!Color!The value as Color.} {GetUnityColor32!Color32!The value as Color32.} {GetUnityRect!Rect!The value as Rect.} {GetUnityTransform!Custom object!The Unity transform type contains various Vector3 and Quaternion values. For this reason, this method gets a custom structure where all these parameters are well organized. This structure contains the following parameters:
- (Vector3) position;
- (Quaternion) rotation;
- (Vector3) localScale;
- (Vector3) localPosition;
- (Quaternion) localRotation;
- (Vector3) lossyScale;
- (Vector3) eulerAngles;
- (Vector3) localEulerAngles;} {{{.DISPOSE}}} The Dispose() method manually clears the internal storage, in order to free the memory occupied by it. Even if it's not mandatory, it's recommended to call Dispose() at the end of the reading operations. Note that this method is automatically called by the Save() method. |||variable_name.Dispose()||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} `// It's supposed you have previously created a myFile instance. void LoadMyData() { -// The Load() method returns true when the storage is filled and ready. -if (myFile.Load()) -{ --// Use the various Get methods to read each value properly converted. --var character = myFile.GetString("name"); --var age = myFile.GetInt("age"); --var strenght = myFile.GetFloat("strenght"); --var has_sword = myFile.GetBool("has_sword"); --// Use the various GetUnity methods to read values that are Unity types. --var initialLocation = myFile.GetUnityVectorTRE("initialLocation"); --// GetUnityTransform() returns a custom object, so it requires a variable. --var tr = myFile.GetUnityTransform("player"); --gameObject.transform.position = tr.position; --gameObject.transform.rotation = tr.rotation; --gameObject.transform.localScale = tr.localScale; --// Now that all the data has been read, you can call Dispose() to free the memory. --myFile.Dispose(); -{ }` [[[]]] [[[Reading custom classes]]] Easy File Save can load instances of custom classes and collections that use custom classes. The methods to be used to read data depend on the system chosen when writing. {{{.GETDESERIALISED}}} The GetDeserialized() method convert the XML data structure into the custom data structure. |||variable_name.GetDeserialized(key, type)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value.} {type!System.Type!The type that must be used for deserialization.} {{Value{}Type{}Description}} {read data!object!The data as object. It must be manually converted in the proper way (cast).} `[System.Serializable] public class XXItemXX { -public string name; -public int quantity; } // It's supposed you have previously created a myFile instance. void LoadMyData() { -// The Load() method returns true when the storage is filled and ready. -if (myFile.Load()) -{ --// Because the saved data was a List of Item, --// GetDeserialized() method requires this type as parameter (using typeof to obtain the System.Type). --// The method returns an object, so it must be manually converted. --var items = (List<XXItemXX>)myFile.GetDeserialized("items", typeof(List<XXItemXX>)); --// Now that all the data has been read, you can call Dispose() to free the memory. --myFile.Dispose(); -{ }` {{{.GETBINARY}}} The GetBinary() method convert the bytes array data structure into the custom data structure. |||variable_name.GetDeserialized(key)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value.} {{Value{}Type{}Description}} {read data!object!The data as object. It must be manually converted in the proper way (cast).} `[System.Serializable] public class XXItemXX { -public string name; -public int quantity; } // It's supposed you have previously created a myFile instance. void LoadMyData() { -// The Load() method returns true when the storage is filled and ready. -if (myFile.Load()) -{ --// Because the saved data was a List of Item, --// GetDeserialized() method requires this type as parameter (using typeof to obtain the System.Type). --// The method returns an object, so it must be manually converted. --var items = (List<XXItemXX>)myFile.GetBinary("items"); --// Now that all the data has been read, you can call Dispose() to free the memory. --myFile.Dispose(); -{ }` [[[]]] [[[MANAGEMENT METHODS]]] Easy File Save comes with some methods to manage the file and the system. {{{FILE EXISTS}}} Return true if the file exists (if the file is physically existing in the device storage). |||variable_name.FileExists()||| {{{DELETE}}} Delete the file from the device storage (if it exists) and dispose the Easy File Save internal storage. |||variable_name.Delete()||| {{{KEY EXISTS}}} Check if the internal storage contains the given key. This control can be useful to check if a certain key exists before trying to read its value. |||variable_name.KeyExists(key)||| `// It's supposed you have previously created a myFile instance. void LoadMyData() { -// The Load() method returns true when the storage is filled and ready. -if (myFile.Load()) -{ --// Check if 'name' key exists before using the Get method. --string name = ""; --if (myFile.KeyExists("name")) name = myFile.GetString("name"); -{ }` {{{GET FILE NAME}}} Return the current file name assigned to the instance, with the full path. |||variable_name.GetFileName()||| {{{DELETE ALL FILES}}} The EasyFileSave class has a static function to delete all the files currently instantiated. Use it with caution. |||variable_name.DeleteAll()||| {{{CLASS SERIALIZATION}}} The EasyFileSave class has a static function to serialize a custom class instance. It returns the XML representation of the given data (object). |||EasyFileSave.Serialize(data)||| {{{CLASS DESERIALIZATION}}} The EasyFileSave class has a static function to deserialize an XML in the user custom class instance. It returns an object. |||EasyFileSave.Deserialize(data, type)||| [[[]]] [[[Extensions]]] An Extension allows you to extend what Easy File Save can save and load. With this feature, you can save and load data types that aren't built-in in the system, for example, Unity components or complex custom data types. When you implement an Extension, it becomes part of the Easy File Save system and it's accessible by all the EasyFileSave instances. {-{{EasyFileSaveExtension Class}}} New Extensions must be developed inside the EasyFileSaveExtension C# script, that's placed inside the EasyFileSave folder. This script already contains an Extension that allows to save and load BoxColliders. {{{HOW TO CREATE AN EXTENSION}}} Developing an Extension is pretty easy and it basically requires just some steps. The following instructions will show how to implement an Extension adding the support for RigidBody Unity component as example: {.{{1.}}} Inside the Start() function, use the AddExtension() method to declare a new Extension. This method requires a name for the Extension (a string) and the name of the callBack function (the function to call in order to make this Extension working). `public void Start() { -// Name of this extension and the callBack function which contains the extension configuration. -AddExtension("BoxCollider", BoxCollider); -// My new Extension: support for saving and loading RigidBody. -AddExtension("RBody", RBodyExtension); }` {.{{2.}}} In the Script file (ideally after the Start() function) create the callBack function (for this example RigidBodyExtension). `public void Start() { -// Name of this extension and the callBack function which contains the extension configuration. -AddExtension("BoxCollider", BoxCollider); -// My new Extension: support for saving and loading RigidBody. -AddExtension("RBody", RBodyExtension); } // My RigidBodyExtension callback function, as declared in AddExtension() method. void RBodyExtension() { }` {.{{3.}}} The callBack function is called every time you perform a writing operation. So, it must contain the logic to collect the RigidBody data you want to save. The Extension system recognizes basic data type only (string, bool, int, float, byte, etc.), so you must collect only RigidBody values that are of these types and that are important for your needs. Inside this function you have just to perform 3 operations: 3.1
You have to use the GetData() method to obtain the value you are going to save. GetData() method requires the Extension name as parameter: `// My RigidBodyExtension callback function, as declared in AddExtension() method. void RBodyExtension() { -// Use GetData, with Extension name as parameter, to get the value you are going to save. -var data = GetData("RBody"); }` 3.2
Because GetData() method returns an object, you have to convert it in the original type (in this example, RigidBody Unity data type): `// My RigidBodyExtension callback function, as declared in AddExtension() method. void RBodyExtension() { -// Use GetData, with Extension name as parameter, to get the value you are going to save. -var data = GetData("RBody"); -// I convert the object from GetData() into a RigidBody data type. -RigidBody rb = (RigidBody)data; }` 3.3
Now that you have the RigidBody that you're going to save, you have to use the SetParameters() method to collect the RigidBody values you want to save. This method requires the Extension name followed by a list of parameters. Each parameter, described by the Par() object, must contain one of the RigidBody values and a unique name that identifies it: `// My RigidBodyExtension callback function, as declared in AddExtension() method. void RBodyExtension() { -// Use GetData, with Extension name as parameter, to get the value you are going to save. -var data = GetData("RBody"); -// I convert the object from GetData() into a RigidBody data type. -RigidBody rb = (RigidBody)data; -// With SerParameters() method you can choose which RigidBody values to save and load. -// Every single value requires a name. A good practice is to use the same RigidBody property name. -SetParameters( --"RBody", --new Par { name = "angularDrag", value = rb.angularDrag }, --new Par { name = "angularVelocity", value = rb.angularVelocity }, --new Par { name = "detectCollisions", value = rb.detectCollisions }, --new Par { name = "freezeRotation", value = rb.freezeRotation }, --new Par { name = "useGravity", value = rb.useGravity }, --new Par { name = "velocity", value = rb.velocity } -); }` That's all and the new Extension for RigidBody is ready to be used. {{{HOW TO SAVE EXTENSION DATA}}} Because Extension data is a custom functionality, you can't use the standard Add() method, but you must use AddCustom() method. |||variable_name.AddCustom(key, data, extension_name)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value.} {data!object!The custom data to save.} {extension_name!string!The name of the Extension that will properly save the data.} `// It's supposed you have previously created a myFile instance. void SaveMyData() { -// Add my GameObject RigidBody to the internal storage through a dedicated Extension. -myFile.AddCustom("player_rigid_body", gameObject.GetComponent<RigidBody>(), "RBody"); -// Save the collected data into the file and clear the storage. -myFile.Save(); }` {{{HOW TO LOAD EXTENSION DATA}}} To get data that has been saved by an Extension you must use GetCustom() method. This method returns a special dictionary that contains all the saved values, identified by a specific name, and methods to convert each value in a common data type. |||variable_name.GetCustom(key, extension_name)||| {{Parameters{}Type{}Description}} {variable_name!!The name of the EasyFileSave instance.} {key!string!A unique id to identify the value.} {extension_name!string!The name of the Extension that will generate the dictionary.} Returned value:
a special dictionary with the values indentified by unique keys and the following methods to convert (cast) each value: - variable[key].ToString();
- variable[key].ToBool();
- variable[key].ToInt();
- variable[key].ToFloat();
- variable[key].ToByte();
- variable[key].data (property that contains the value as object); `// It's supposed you have previously created a myFile instance. void LoadMyData() { -// The Load() method returns true when the storage is filled and ready. -if (myFile.Load()) -{ --// Use GetCustom to obtain the dictionary with all the saved values. --var rb = myFile.GetCustom("player_rigid_body", "RBody"); --// Use rb dictionary as needed. --var playerRB = gameObject.GetComponent<RigidBody>(); --playerRB.angularDrag = rb["angularDrag"].toFloat(); --playerRB.angularVelocity = rb["angularVelocity"].toFloat(); --playerRB.detectCollisions = rb["detectCollisions"].toBool(); --playerRB.freezeRotation = rb["freezeRotation"].toBool(); --playerRB.useGravity = rb["useGravity"].toBool(); --playerRB.velocity = rb["velocity"].toFloat(); -{ }` [[[]]] [[[WRITE / READ TEST]]] The TestDataSaveLoad() method is a practical way to test the writing and reading operation. This method is pretty useful to test own data structure, expecially in case you're going to manage complex data structures.| To use this method just fill the "internal storage" with your data e call TestDataSaveLoad(). In the Unity Console you will see the test results. `void MyTest() { -// The data structure to test. -myFile.Add("name", "Conan"); -myFile.Add("age", 32); -myFile.Add("strenght", 122.5f); -myFile.Add("has_sword", true); -var equipment = new List<string>(); -equipment.Add("Hammer"); -equipment.Add("Knife"); -equipment.Add("Rope"); -myFile.Add("hero_equip", equipment); -myFile.Add("initialLocation", new Vector3(101.5f,-30.4f,22f)); -myFile.Add("player", gameObject.transform); -// Test -myFile.TestDataSaveLoad(); }` [[[]]]