Как Unity сохраняет данные в PlayerPrefs?

Я хочу прочитать данные из реестра в своем автономном приложении WPF, которое создается внутри игры на Unity с помощью PlayerPrefs. Вопрос в том, как читать и записывать float в реестр как DWORD32.

public static float Int64ToFloat(long value)
{
    byte[] bytes = BitConverter.GetBytes(value);
    byte[] floatBytes = new byte[4];
        
    Array.Copy(bytes, floatBytes, 4);

    if (BitConverter.IsLittleEndian)
                Array.Reverse(floatBytes);

    float floatValue = BitConverter.ToSingle(floatBytes, 0);

    return floatValue;
}

Этот код возвращает очень маленькие значения, например 1,35E-43.

Использовали ли вы regedit для просмотра сохраненного значения? Возможно, Unity не использует стандартную кодировку для значений с плавающей запятой.

Max Play 14.04.2024 16:42

@MaxPlay Да, я посмотрел значения в реестре, но, честно говоря, мне это не особо помогло, так как я никогда не работал с таким кодированием, поэтому на глаз сложно сказать, что это такое. Если бы у меня был доступ к исходникам класса PlayerPrefs, было бы гораздо проще.

Azule 14.04.2024 21:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
2
74
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Методы представления целых типов и чисел с плавающей запятой различны.

Возьмем пример
Целочисленные типы можно преобразовать в интуитивно понятный двоичный формат следующим образом:
целое число 11 = 00001011

но.
Представление с плавающей запятой немного отличается.

В качестве примера возьмем 8-битное число.
01011111
Первые 4 цифры этого числа представляют собой целое число
0101 = 5
Последние 4 цифры рассчитываются следующим образом:
1111 = 0,5 + 0,25 + 0,125 + 0,0625
Каждая цифра представляет собой 2 в степени -n.

Если вы хотите отрезать часть длинного типа и использовать его как целое число, а последнее число использовать как десятичное и сохранить его в типе с плавающей запятой, существует также следующий метод.

int n = 17012165
float f = n / 10000 // 1701.2165f

Поплавки представлены совсем иначе, чем целые числа.

Целые числа хранятся в 32 битах, где справа налево каждый бит хранит большее число. (0110) => 03+2²+2¹+0⁰, где 0⁰ = 0 равно 6.

Плавающие числа хранятся с использованием знака, показателя степени и дроби, где первый бит — это знак (0 = положительный, 1 = отрицательный), следующие 8 бит — это показатель степени, и это похоже на int, но в конце это ^ (-127). (по крайней мере, в IEEE752), и дробь начинается с первого значения, считая 0 или 2 ^(-1), следующего ^(-2) и так далее до ^(-23). Наконец все подсчитывается вместе:

EXP^(-127) × (1+FRAC

Ответ принят как подходящий

Unity сохраняет не float, а double! Это действительно довольно хитрый хак, но если вы присмотритесь, то обнаружите, что ваш DWORD (32bit) действительно содержит 64-бит long! (понятия не имею, почему они там не использовали должным образом QWORD или прямо двоичный код)

Как уже упоминалось для типов с плавающей запятой, вы не можете просто вырезать bytes и ожидать, что значение останется таким же, как вы могли бы сделать с long -> int.

Вы можете просто использовать

// Have in mind that the key here is NOT the one you use in Unity but also contains a certain trailing hash string
public static float GetFloat(string key, float fallback = 0f)
{
    using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(@"Software\Unity\UnityEditor\" + Application.companyName + "\\" + Application.productName))
    {
        var value = registryKey?.GetValue(key);
        if (value is long longValue)
        {                      
            // Convert the 64-bit value to a double
            var doubleValue = BitConverter.Int64BitsToDouble(longValue);
            // then simply cast to cut its precision down
            var floatValue = (float)doubleValue;
            return floatValue;
        }
    }

    return fallback;
}

Чтобы вы проверили

var input = 12345678901234567890.1234567890f;
PlayerPrefs.SetFloat("TestFloat", input);
var output = GetFloat("TestFloat_h1435782019");
Assert.AreApproximatelyEqual(input,output, "Value retrieved from registry does not match the value stored in PlayerPrefs.");

Я тоже наткнулась на этот фрагмент

Если мы можем этому доверять, хеш-строка в основном рассчитывается как

public class PlayerPrefsHash
{
  // Returns the string property name Unity would generate for a player prefs registry key value 
  // e.g., "PlayerGold" -> "PlayerGold_hXXXXXXXXXXXXXX"
  public static string Hash(string name)
  {
    uint hash = 5381;
    foreach (char c in name)
      hash = hash * 33 ^ c;
    string key = name + "_h" + hash;
    return key;
  }
}

Таким образом, вы можете использовать это в моем методе выше и иметь возможность использовать ту же ключевую константу в Unity, что и на стороне WPF.

И, по крайней мере, мой быстрый тест показал, что это точно.

var key = PlayerPrefsHash.Hash("TestFloat");
Assert.AreEqual("TestFloat_h1435782019", key, "PlayerPrefsHash.Hash does not match PlayerPrefs generated hash.");

Другие вопросы по теме