Search Unity

A useful script I created and want to share!

Discussion in 'Scripting' started by BinaryOrange, Oct 23, 2014.

  1. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    Just thought I'd share this with everyone!

    I love using PlayerPrefs to save basic information for game data files, however I found they were lacking in terms of what all they could save. I rectified this problem by creating a custom FileManager class to create convenience functions to specifically add the ability to...

    1) save and load 3D vectors (useful for storing and reloading the player's exact position for game files)
    2) save and load boolean values (useful for a multitude of things!)

    Here is the script, I'm sure something like it is publicly available elsewhere but I couldn't find it, hence my posting it!

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public static class FileManager
    5. {
    6.  
    7.     /* --------------------------------------------------------
    8.      * a custom class that still uses the ease of PlayerPrefs
    9.      * but adds more functionality, such as the ability to
    10.      * save and load 3D vectors and boolean values!
    11.      * ----------------------------------------------------- */
    12.  
    13.     // all boolean methods
    14.     public static void SaveBoolean(string name, bool value)
    15.     {
    16.         PlayerPrefs.SetInt(name, value ? 1 : 0);
    17.     }
    18.  
    19.     public static bool LoadBoolean(string name)
    20.     {
    21.         return PlayerPrefs.GetInt(name) == 1 ? true : false;
    22.     }
    23.  
    24.     // all string methods
    25.     public static void SaveString(string name, string value)
    26.     {
    27.         PlayerPrefs.SetString(name, value);
    28.     }
    29.  
    30.     public static string LoadString(string name)
    31.     {
    32.         return PlayerPrefs.GetString(name);
    33.     }
    34.  
    35.     // all int methods
    36.     public static void SaveInt(string name, int value)
    37.     {
    38.         PlayerPrefs.SetInt(name, value);
    39.     }
    40.  
    41.     public static int LoadInt(string name)
    42.     {
    43.         return PlayerPrefs.GetInt(name);
    44.     }
    45.  
    46.     // all float methods
    47.     public static void SaveFloat(string name, float value)
    48.     {
    49.         PlayerPrefs.SetFloat(name, value);
    50.     }
    51.  
    52.     public static float LoadFloat(string name)
    53.     {
    54.         return PlayerPrefs.GetFloat(name);
    55.     }
    56.  
    57.     // all vector3 methods
    58.     public static void SaveVector3(string name, Vector3 vector)
    59.     {
    60.         PlayerPrefs.SetFloat(name, vector.x);
    61.         PlayerPrefs.SetFloat(name + "1", vector.y);
    62.         PlayerPrefs.SetFloat(name + "2", vector.z);
    63.     }
    64.  
    65.     public static Vector3 LoadVector3(string name)
    66.     {
    67.         Vector3 result;
    68.  
    69.         result.x = PlayerPrefs.GetFloat(name);
    70.         result.y = PlayerPrefs.GetFloat(name + "1");
    71.         result.z = PlayerPrefs.GetFloat(name + "2");
    72.  
    73.         return result;
    74.     }
    75. }
    76.  

    If there's other functionality people want added, I will see what I can do! Keep in mind it's still relatively limited since I'm still using PlayerPrefs behind the scenes, but any ideas will be appreciated!
     
    JoeStrout likes this.
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    This is nice. However, instead of saving a vector as three entries, I would suggest writing it out as one entry with the three values separated by semicolons or spaces or whatever. Then, upon reading, you split on that same delimiter and parse each part.
     
  3. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    I did think about that but it felt way too messy! Another idea was to add multiple arguments to the method so that you could save a vector like this...

    Code (CSharp):
    1. FileManager.SaveVector3("Vector", vector.x, vector.y, vector.z);
    ...but that feels really messy, although it would allow you to save only the information you wanted (say, the height of an object for example). It would become cumbersome if you wanted to something like this, though...
    Code (CSharp):
    1. FileManager.SaveVector3("Vector", transform.position.x, transform.position.y, transform.position.z);
    What do you think? I'm open to any and all ideas!

    If you have an example of how I might be able to parse the value like you said, I would love to see it because I'm not actually 100% how to do that yet!
     
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    No, I wasn't suggesting you change the interface to your class — passing or getting back a Vector3 is the obvious right way to go.

    But inside the class, write it out as a string, constructed with String.Format. And upon reading, break it up with Split and then use float.Parse to convert each part.
     
  5. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    That seems like way more work than necessary. The way I have it now seems more simplified, what exactly would storing it in a string offer over this?

    The way I have it now, I just read it in line-by-line and I don't have to do any converting.
     
  6. sam268

    sam268

    Joined:
    Apr 21, 2014
    Posts:
    149
    Cool script. However, can't you just set the vector3 as a vector3? You know, instead of saving an x, y, and z?
     
  7. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    No, because PlayerPrefs only allows you to save string, float or int values. If there had been a way to save vectors natively I probably wouldn't have made the script :p
     
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Ah, the way you have it now, there are hidden side-effects. Suppose I have three vectors I want to save. So I tell your module to save "SpawnPoint", "SpawnPoint1", and "SpawnPoint2". Doh! Saving SpawnPoint actually overwrites the other two (or vice versa).

    Side-effects are bad; hidden side-effects are even worse. As long as your code base is only used by you, and it's small enough and fresh enough that you remember all these little details, then you get away with it. But sooner or later it will bite you in the tender parts.

    So, it's always better to design your code so that it works no matter what inputs are thrown at it, in any order, and with any names... and to do that, you need to save one value as one prefs entry. And really, it's not that hard to do.

    (But hey, it's only a suggestion — feel free to ignore it if it doesn't resonate with you!)
     
    Magiichan likes this.
  9. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    Ah, I see what you're saying now!
    I tested out a modified version of the method and I think I solved that potential problem, I saved 3 separate vectors and they all printed out exactly as expected.

    Modified methods:
    Code (CSharp):
    1. // all vector3 methods
    2.     public static void SaveVector3(string name, Vector3 vector)
    3.     {
    4.         PlayerPrefs.SetFloat(name + "X", vector.x);
    5.         PlayerPrefs.SetFloat(name + "Y", vector.y);
    6.         PlayerPrefs.SetFloat(name + "Z", vector.z);
    7.     }
    8.  
    9.     public static Vector3 LoadVector3(string name)
    10.     {
    11.         Vector3 result = new Vector3(PlayerPrefs.GetFloat(name + "X"), PlayerPrefs.GetFloat(name + "Y"), PlayerPrefs.GetFloat(name + "Z"));
    12.         return result;
    13.     }
    14. }
    Now when you call the method it will add X, Y, and Z to the end of the name you assign, and save the fields appropriately. And as you can see, it also helped clean up the load method quite a bit too!

    It seems to work fine, I made a (very sloppy!) test script...
    Code (CSharp):
    1. if(Input.GetKeyDown(KeyCode.S))
    2.         {
    3.             Vector3 vector1 = new Vector3(23.45f, 54.67f, 34.655f);
    4.             Vector3 vector2 = new Vector3(-22.09f, 4.5f, 3.2f);
    5.             Vector3 vector3 = new Vector3(-12f, 0.6557f, -90.75f);
    6.  
    7.             FileManager.SaveVector3("Position1", vector1);
    8.             FileManager.SaveVector3("Position2", vector2);
    9.             FileManager.SaveVector3("Position3", vector3);
    10.  
    11.  
    12.             Vector3 result1 = FileManager.LoadVector3("Position1");
    13.             Vector3 result2 = FileManager.LoadVector3("Position2");
    14.             Vector3 result3 = FileManager.LoadVector3("Position3");
    15.  
    16.             Debug.Log(result1);
    17.             Debug.Log(result2);
    18.             Debug.Log(result3);
    19.         }
     
  10. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I guess I wasn't clear... and this is good practice for me, since sometimes I teach this stuff, and probably don't know when I'm not being clear!

    All you've done with this modification is swap one problem for a different problem. Your new code has the (hidden and implied) rule that you mustn't save something called "foo" and also save something called "fooX", "fooY", or "fooZ". Break this rule, and your code will silently fail, with things ending up with incorrect values.

    That's not really any better than the previous hidden rule. It's just different.

    But again, feel free to ignore me if this doesn't make sense to you. (Eventually, after you've been bitten by your own code enough times, it will make more sense!)
     
    Magiichan likes this.
  11. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    You're right, I guess I don't understand... are you saying that I can't save something with the same name as something else? Because of course that wouldn't work!

    Or do you mean I can't save something like "position1", and then save something with a name like "position1x"? Because that should still work just fine, since I add the X, Y and Z in the actual method, not in the name when I'm actually saving a vector.

    I also still fail to see how storing the actual values in a string would apparently solve the problem, since you're saying the problem is to do with the NAME, not the value. Unless I read that wrong? I really don't know :p
     
  12. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    Magiichan likes this.
  13. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    Interesting read, will definitely look that over!
    @JoeStrout, I modified my methods to work with strings! I just did a copy>paste for the actual parsing method (found on an old thread), and it seems to work pretty flawlessly!

    Code (CSharp):
    1.     // all vector3 methods
    2.     public static void SaveVector3(string name, Vector3 vector)
    3.     {
    4.         string vectorData = string.Format("{0}, {1}, {2}", vector.x, vector.y, vector.z);
    5.  
    6.         PlayerPrefs.SetString(name, vectorData);
    7.     }
    8.  
    9.     public static Vector3 LoadVector3(string name)
    10.     {
    11.         string stringToParse = PlayerPrefs.GetString(name);
    12.  
    13.         string[] temp = stringToParse.Substring(1, stringToParse.Length-2).Split(',');
    14.         float x = float.Parse(temp[0]);
    15.         float y = float.Parse(temp[1]);
    16.         float z = float.Parse(temp[2]);
    17.         Vector3 result = new Vector3(x, y, z);
    18.         return result;
    19.     }
    EDIT: Got it a bit cleaner, and now upon looking in the actual playerprefs file, I'm glad I did it this way!
     
    Last edited: Oct 24, 2014
    JoeStrout and GarthSmith like this.
  14. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    You've fixed it by this point, but it sounds like maybe you're still confused as to why the new approach is better, so let me try to explain it better.

    OK, so let's walk through this step by step and see what happens.
    1. Your (old) method is called with a vector named "position1". Inside the method, it calls PlayerPrefs.SetFloat three times, and writes to the prefs three values called "position1X", "position1Y", and "position1Z".
    2. Now the caller, in some completely unrelated bit of code, needs to save a float called "position1X". So they call FileManager.SaveFloat with a name of "position1X". This calls PlayerPrefs.SetFloat with that name, and clobbers the first element of the previously saved position1 vector.
    Unlikely? Sure! But unlikely bugs are the worst kinds of bugs, because they lurk quietly for years until you've completely forgotten about the possibility, and then they jump out and bite you when you have no idea what's going on.

    The fundamental problem was: the caller was saying to save one thing, but you were actually saving several other things, introducing the possibility of a name collision the caller would have no way to know about (without peeking inside the FileManager source code).

    Now you're writing out only one item for each save call, using the actual name the caller specified, so if there is a name collision it's their own darn fault!

    Cheers,
    - Joe

    P.S. I'm puzzled by the substring in your new code, that appears to be stripping off the first and last character of the value... but when you write out the value, I don't see any extra first/last characters, so I don't understand why that works.
     
  15. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    Oh, Ok, I now see what you mean!

    Probably didn't help that I was running on only 5 hours of sleep yesterday, haha! But now I definitely see what you're talking about!

    As for the substring, I am also not entirely sure why that's there, but when I did a search on how to parse through text to reconstruct a vector, that was the top result and also looked the least complicated. I am not sure how it works either, but if I try to modify it it breaks and tells me the array is out of bounds. Sometimes I think computers are too moody and just like to mess around with our minds :p
     
  16. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I suspect that the code you found was assuming the vector would have some extra stuff around it, like this: "<3.14, -12, 42>".

    But the way you're writing the vector out, it doesn't have any such extra stuff. So I think you can remove the Substring call entirely, and just use stringToParse.Split(',').

    EDIT: And, do not try to bend the spoon. That would be impossible.