Search Unity

Resolved Trying desperately to save PlayerPrefs float value

Discussion in 'Scripting' started by vainoharha, Feb 12, 2024.

  1. vainoharha

    vainoharha

    Joined:
    Jul 1, 2019
    Posts:
    5
    Greetings. It's been a while when I had visited this forum :)

    I've been struggling with this strange behaviour for two days and jumping between two AI to get it sorted out with no success.

    Somehow I just can't save float values with PlayerPrefs. Everytime when I check through Regedit.
    There's only a error on saved data: Invalid DWORD (32-bit) value with a garbage in value.

    But! When I save int or string value it works flawlessly.

    Would it be possible because i'm running originally Windows 11 23H2 english version and added
    locales for finnish?

    Read about using CultureInfo.InvariantCulture because some countries use period instead of comma in fractions. As we know; "Unity handles the conversion of the float value internally and ensures that it's saved in a platform-independent manner. You don't need to worry about formatting or culture-specific issues when saving floats to PlayerPrefs directly." <-- Sounds good on paper..

    I was using Unity 2022.2.20f so I decided to update to 2022.3.19f if that helps.
    Nope. Same behaviour continues.

    I wouldn't want to save certain floats from sliders to string and convert them somehow back to float for
    actually use of those values.

    Those values are being used in Options menu with sliders for adjusting brightness, music volume and fx volume.

    Should I just forget using floats for more accurate results and set things to work with int? :(

    I'll post those three scripts what I used when debugging this problem:

    First is for saving string:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class PlayerPrefsStringExample : MonoBehaviour
    4. {
    5.     private const string stringTestKey = "StringTest";
    6.  
    7.     void Start()
    8.     {
    9.         // Save the string "This is A test!" to PlayerPrefs
    10.         SaveStringToPlayerPrefs(stringTestKey, "This is A test!");
    11.  
    12.         // Retrieve the saved string from PlayerPrefs
    13.         string retrievedString = RetrieveStringFromPlayerPrefs(stringTestKey);
    14.  
    15.         // Print the retrieved string to the Unity console with a message
    16.         Debug.Log("Retrieved String: " + retrievedString);
    17.     }
    18.  
    19.     // Function to save a string value to PlayerPrefs
    20.     private void SaveStringToPlayerPrefs(string key, string value)
    21.     {
    22.         PlayerPrefs.SetString(key, value);
    23.         PlayerPrefs.Save();
    24.     }
    25.  
    26.     // Function to retrieve a string value from PlayerPrefs
    27.     private string RetrieveStringFromPlayerPrefs(string key)
    28.     {
    29.         return PlayerPrefs.GetString(key, ""); // Provide a default value ("") if the key doesn't exist
    30.     }
    31. }
    Second is for int:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class PlayerPrefsExample : MonoBehaviour
    4. {
    5.     private const string devilsNumberKey = "DevilsNumber";
    6.  
    7.     void Start()
    8.     {
    9.         // Save the integer value 666 to PlayerPrefs
    10.         SaveIntToPlayerPrefs(devilsNumberKey, 666);
    11.  
    12.         // Retrieve the saved integer value from PlayerPrefs
    13.         int retrievedValue = RetrieveIntFromPlayerPrefs(devilsNumberKey);
    14.  
    15.         // Print the retrieved integer value to the Unity console
    16.         Debug.Log("Devil's Number: " + retrievedValue);
    17.     }
    18.  
    19.     // Function to save an integer value to PlayerPrefs
    20.     private void SaveIntToPlayerPrefs(string key, int value)
    21.     {
    22.         PlayerPrefs.SetInt(key, value);
    23.         PlayerPrefs.Save();
    24.     }
    25.  
    26.     // Function to retrieve an integer value from PlayerPrefs
    27.     private int RetrieveIntFromPlayerPrefs(string key)
    28.     {
    29.         return PlayerPrefs.GetInt(key, 0); // Provide a default value (0) if the key doesn't exist
    30.     }
    31. }
    32.  
    And third what stopped my progress with floats:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class PlayerPrefsFloatTest : MonoBehaviour
    4. {
    5.     private const string floatKey = "MyFloatValue";
    6.     private float defaultValue = 0.69f;
    7.  
    8.     void Start()
    9.     {
    10.         // Save a float value to PlayerPrefs
    11.         SaveFloatToPlayerPrefs(floatKey, defaultValue);
    12.  
    13.         // Retrieve the saved float value from PlayerPrefs
    14.         float retrievedValue = RetrieveFloatFromPlayerPrefs(floatKey, defaultValue);
    15.  
    16.         // Log the retrieved float value
    17.         Debug.Log("Retrieved Float Value: " + retrievedValue);
    18.     }
    19.  
    20.     // Function to save a float value to PlayerPrefs
    21.     private void SaveFloatToPlayerPrefs(string key, float value)
    22.     {
    23.         PlayerPrefs.SetFloat(key, value);
    24.         PlayerPrefs.Save();
    25.     }
    26.  
    27.     // Function to retrieve a float value from PlayerPrefs
    28.     private float RetrieveFloatFromPlayerPrefs(string key, float defaultValue)
    29.     {
    30.         return PlayerPrefs.GetFloat(key, defaultValue);
    31.     }
    32. }
    33.  
    As you can see there aren't much changes between them but i'll post those scripts because why not :confused:
     
  2. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,175
    No. It's just an inherent limitation of the registry. It's aware of integers and strings which is why you see them but it has no concept of a floating point number. Unity is taking advantage of the fact that a DWORD (a 32-bit unsigned integer) is the same size as a single precision number and storing the float in it using a conversion process.

    Just think of it as similar to storing a boolean in an integer as a zero and a one or storing a double as a string.

    Technically a floating point number has lower accuracy than an integer. A 32-bit integer has nine digits that can represent the full 0 through 9 while a float (single precision) has approximately 7.
     
    Last edited: Feb 12, 2024
  3. vainoharha

    vainoharha

    Joined:
    Jul 1, 2019
    Posts:
    5
    Tested out other regedits and they're also saying that is a bad value error. :rolleyes: Quickly tested saving that float to JSON, add that value to playerprefs and convert is back. Result was zero while it supposed to be 0.609. So looks like I need to get back to old drawing board and start thinking of using int and string. It's going to be a blast when I add in future checkpoint saving method for gameobjects position and rotation. Strings4win. :D
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,727
    Building on this further:

    You can convert a
    float
    into an
    int
    and back and forth losslessly using the
    BitConverter
    and
    Convert
    classes.

    NOTE: when it is an
    int
    it is not human-readable in any sense of understanding the floating point value within it.

    Here's some examples from deep inside my Datasacks project:

    https://github.com/kurtdekker/datas...ks/Assets/Datasack/Core/DatasackFormatting.cs
     
    vainoharha likes this.
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,175
    Alternatively just don't use PlayerPrefs. It's not intended for anything beyond preferences which is why it's lacking support for things like arrays and lists. Most games just store their preferences and save data in My Documents or in AppData which if you're already working with JSON is trivial to do.

    Load:
    Code (csharp):
    1. var path = Path.Combine(Application.persistentDataPath, filename);
    2. var jsonStringData = File.ReadAllText(path);
    Save:
    Code (csharp):
    1. var path = Path.Combine(Application.persistentDataPath, filename);
    2. File.WriteAllText(path, jsonStringData);
     
    Last edited: Feb 12, 2024
    vainoharha likes this.
  6. vainoharha

    vainoharha

    Joined:
    Jul 1, 2019
    Posts:
    5
    Darn. This would suit my needs just like I dreamed of :) Thinking of saving six to eleven gameobjects rotation and position to file through System.Serializable, crypt it to prevent tampering positions+playtime for cheating (my nickname means paranoid in finish :eek:) and save that file to persistentDataPath. When loading; decrypt, set values for gameobjects and tadah :cool:

    PlayerPrefs are good for brightness, volume settings and opened game levels. Thank you for opening new doors to me <3
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,998
    I don't quite get why you look up the values in regedit? As Ryiah already said a value like
    0.609f
    would be stored as
    0x3f1be76d
    . Have you actually tried just reading those values back with PlayerPrefs? Especially since you seem to be worried that people could temper with your values. Storing them as floats would actually help a "bit" against tempering. At least against a small number of people who know that Unity stores playerprefs in the registry but don't know the IEEE754 format. There are online tools like this one which you can use to easily convert a single float value into it's binary representation and also the hex number of that binary number.

    I quickly looked through the registry to find some old value where I might have stored a float and I actually have found one. Unity seems to store 8 bytes instead of 4 bytes as it seems to try to store it as a binary string. It has an int32 value which indicates the actual number of bits (usually 00 00 00 20 ==> 32, so 32 bits) followed by the actual float value. Since regedit expects a DWORD (which means 32 bits) but it actually gets a 64 bit binary value it shows invalid DWORD value which is of course correct. However the value should still be able to be stored and read back by Unity. Or do you try to tamper with it in the registry?
     
    Ryiah likes this.
  8. vainoharha

    vainoharha

    Joined:
    Jul 1, 2019
    Posts:
    5
    After a good night sleep I returned back to the project and checked if there's another answer to my problem and there it was.

    After your text I started to think more thoroughly and had that: "But of course!" -moment.

    I was in WYSIWYG -mode :oops: so if I couldn't see that float value with my own eyes I thought it didn't save.
    Now I recreated my brightness script to use float instead of that int. Set function to save that value to playerprefs and it's actually working now flawlessly :D

    Just need to set DontDestroyOnLoad(gameObject) so settings will follow player to the bitter end :)

    Thank you for clarification on this subject. I needed gentle slap :p