Хранить значения редактора между сессиями редактора - PullRequest
0 голосов
/ 14 июня 2019

У меня есть следующий скрипт EditorWindow, который создает пользовательское окно инспектора.После нажатия кнопки в этом окне значение id будет увеличено на единицу.

static int id = 10000;

[MenuItem("Custom/CustomMenu")]
static void Init()
{
    // Get existing open window or if none, make a new one:
    CustomMenu window = (CustomMenu)EditorWindow.GetWindow(typeof(CustomMenu));
    window.Show();
}

if (GUILayout.Button("someButton"))
{
    id++;
    Repaint();
    EditorUtility.SetDirty(this);
}

Это работает нормально, однако, когда я запускаю режим воспроизведения или закрываю редактор единиц, увеличенное значение id теряется и возвращается к 10000.

Использованиенестатическая версия int id сохранит значение в сеансах «воспроизведения», но все равно будет потеряно после закрытия единицы.

Есть ли способ сохранить это значение между сеансами редактора / единства, что-токак playerprefs но для редактора может быть?

1 Ответ

4 голосов
/ 14 июня 2019

ScriptableObject

Возможно, более подходящим для Unity было бы использование выделенного ScriptableObject (также см. Введение в ScriptableObjects )

Вы можете объединить его с [InitializeOnLoadMethod], чтобы реализовать метод загрузки, который будет вызываться после открытия редактора и после повторной компиляции для создания ScriptableObject один раз .

// we don't need the CreateAssetMenu attribute since the editor window
// will create the ScriptableObject once by itself
public class CustomMenuData : ScriptableObject
{
    public int Id;
}

Обязательно поместите его в отдельный скрипт.

public class CustomMenu : EditorWindow
{
    // we can go privtae again ;)
    private static CustomMenuData data;

    // This method will be called on load or recompile
    [InitializeOnLoadMethod]
    private static void OnLoad()
    {
        // if no data exists yet create and reference a new instance
        if (!data)
        {
            // as first option check if maybe there is an instance already
            // and only the reference got lost
            // won't work ofcourse if you moved it elsewhere ...
            data = AssetDatabase.LoadAssetAtPath<CustomMenuData>("Assets/CustomMenuData.asset");

            // if that was successful we are done
            if(data) return;

            // otherwise create and reference a new instance
            data = CreateInstance<CustomMenuData>();

            AssetDatabase.CreateAsset(data, "Assets/CustomMenuData.asset");
            AssetDatabase.Refresh();
        }
    }

    [MenuItem("Custom/CustomMenu")]
    private static void Init()
    {
        // Get existing open window or if none, make a new one:
        var window = (CustomMenu)EditorWindow.GetWindow(typeof(CustomMenu));

        window.Show();
    }

    private void OnGUI()
    {
        // Note that going through the SerializedObject
        // and SerilaizedProperties is the better way of doing this!
        // 
        // Not only will Unity automatically handle the set dirty and saving
        // but it also automatically adds Undo/Redo functionality!
        var serializedObject = new SerializedObject(data);

        // fetches the values of the real instance into the serialized one
        serializedObject.Update();

        // get the Id field
        var id = serializedObject.FindProperty("Id");

        // Use PropertyField as much as possible
        // this automaticaly uses the correct layout depending on the type
        // and automatically reads and stores the according value type
        EditorGUILayout.PropertyField(id);

        if (GUILayout.Button("someButton"))
        {
            // Only change the value throgh the serializedProperty
            // unity marks it as dirty automatically
            // also no Repaint required - it will be done .. guess .. automatically ;)
            id.intValue += 1;
        }

        // finally write back all modified values into the real instance
        serializedObject.ApplyModifiedProperties();
    }
}

Огромное преимущество этого:

  • Это намного быстрее / эффективнее, чем использование FileIO для записи и сохранения, поскольку Unity автоматически выполняет (де) сериализацию этого объекта ScriptableObject.
  • Вам не нужно «вручную» загружать и сохранять данные ... это делается автоматически, поскольку ScriptableObject ведет себя как любой другой префаб.
  • Вы можете просто щелкнуть экземпляр ScriptableObject в ваших активах и напрямую изменить значения!

Использование простого текстового файла

Простым, но не очень эффективным альтернативным решением было бы сохранить его в файл, например. как JSON, как это

using System.IO;
using UnityEditor;
using UnityEngine;

public class CustomMenu : EditorWindow
{
    private const string FileName = "Example.txt";

    // shorthand property for getting the filepath
     public static string FilePath
     {
         get { return Path.Combine(Application.streamingAssetsPath, FileName); }
     }

    private static int id = 10000;

    // Serialized backing field for storing the value
    [SerializeField] private int _id;

    [MenuItem("Custom/CustomMenu")]
    static void Init()
    {
        // Get existing open window or if none, make a new one:
        CustomMenu window = (CustomMenu)EditorWindow.GetWindow(typeof(CustomMenu));

        if (File.Exists(FilePath))
        {
            // read the file content
            var json = File.ReadAllText(FilePath)

            // If the file exists deserialize the JSON and read in the values
            // for only one value ofcourse this is overkill but for multiple values
            // this is easier then parsing it "manually"
            JsonUtility.FromJsonOverwrite(json, window);

            // pass the values on into the static field(s)
            id = window._id;
        }

        window.Show();
    }

    private void OnGUI()
    {
        id = EditorGUILayout.IntField(id);

        if (GUILayout.Button("someButton"))
        {
            id++;
            Repaint();
            EditorUtility.SetDirty(this);

            // do everything in oposide order
            // first fetch the static value(s) into the serialized field(s)
            _id = id;

            // if not exists yet create the StreamingAssets folder
            if (!Directory.Exists(Application.streamingAssetsPath))
            {
                AssetDatabase.CreateFolder("Assets", "StreamingAssets");
            }

            // serialize the values into json
            var json = JsonUtility.ToJson(this);

            // write into the file
            File.WriteAllText(FilePath, json);

            // reload the assets so the created file becomes visible
            AssetDatabase.Refresh();
        }
    }
}

В настоящее время он читает файл каждый раз, когда вы открываете окно, и записывает его каждый раз, когда нажимаете кнопку. Это все еще можно улучшить.

Опять же, вы можете использовать [InitializeOnLoadMethod], чтобы прочитать файл только один раз - а именно, когда вы открываете редактор или перекомпилируете, как

public class CustomMenu : EditorWindow
{
    // Instead of having the field values directly as static fields
    // rather store the information in a proper serializable class
    [Serializable]
    private class CustomMenuData
    {
        public int Id;
    }

    // made this publlic for the saving process (see below)
    public static readonly CustomMenuData data = new CustomMenuData();

    // InitializeOnLoadMethod makes this method being called everytime
    // you load the project in the editor or after re-compilation
    [InitializeOnLoadMethod]
    private static void OnLoad()
    {
        if (!File.Exists(FilePath)) return;

        // read in the data from the json file
        JsonUtility.FromJsonOverwrite(File.ReadAllText(FilePath), data);
    }

    ...
}

Чтобы оптимизировать сохранение и выполнять запись файлов только при сохранении в UnityEditor, вы можете реализовать выделенный AssetModificationProcessor, как

public class CustomMenuSaver : SaveAssetsProcessor
{
    static string[] OnWillSaveAssets(string[] paths)
    {
        // do change nothing in the paths
        // but additionally store the data in the file

        // if not exists yet create the StreamingAssets folder
        if (!Directory.Exists(Application.streamingAssetsPath))
        {
            AssetDatabase.CreateFolder("Assets", "StreamingAssets");
        }

        // serialize the values into json        v That's why I made it public
        var json = JsonUtility.ToJson(CustomMenu.data);

        // write into the file         v needs to be public as well
        File.WriteAllText(CustomMenu.FilePath, json);            

        return paths;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...