Сохранение способностей.
В прошлой части я рассмотрел разницу между обычным - пользовательским объектом и unity объектом.
В этой части статьи нам будут нужны только скриптовые объекты которые будут храниться в проекте.
Это будет выглядеть примерно как на картинке.
Для того чтобы наш базовый класс AbilityObject "оживить" и сделать его скриптовым объектом достаточно просто унаследовать его от
класса ScriptableObject, чтобы его редактор представлял в виде простого ассета.
Синтаксис:
Используется csharp
public class AbilityObject : ScriptableObject {
private AbilitySettings.AbilityType abilityType;
public string name = "";
public Texture2D icon = null;
public string abilityDescription = "Abilities Description here";
public AbilitySettings.AbilityType type {
get { return this.abilityType;}
protected set { this.abilityType = value;}
}
protected AbilityObject(AbilitySettings.AbilityType type, string name) {
this.type = type;
this.name = name;
}
}
private AbilitySettings.AbilityType abilityType;
public string name = "";
public Texture2D icon = null;
public string abilityDescription = "Abilities Description here";
public AbilitySettings.AbilityType type {
get { return this.abilityType;}
protected set { this.abilityType = value;}
}
protected AbilityObject(AbilitySettings.AbilityType type, string name) {
this.type = type;
this.name = name;
}
}
И все. Больше мы ничего менять или добавлять в этом классе пока не будем.
Перейдем к его сохранению.
Теперь создадим метод который будет сохранять уже скриптовый объект - способность в проекте.
Давайте в статическом классе EditorSettings создадим статический метод SaveAbility который будем принимать один параметр - нашу
способность.
Синтаксис:
Используется csharp
public static class EditorSettings {
public static void SaveAbility(AbilityObject ability) {
}
}
public static void SaveAbility(AbilityObject ability) {
}
}
Пока этот метод будет пустым.
Как было изображено на картинке в начале этой части статьи, я для каждой способности создаю отдельную папку, сама эта папка храниться
в общей папке под названием UserAbilities в папке ресурсов.
И чтобы обеспечить сохранение каждой способности в своей собственной папке для начала ДО сохранения способности мне нужно создать эти
папки. Поэтому я создал небольшой метод который все это будет контроллировать.
Опять же в статическом классе EditorSettings создадим статический метод CreateAbilityFolders который будет создавать необходимые нам
папки. Этот метод будет брать только один параметр - имя способности.
Синтаксис:
Используется csharp
public static class EditorSettings {
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
}
}
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
}
}
Также мне нужна будет строковая константа где я буду хранить название общей папки UserAbilities
Синтаксис:
Используется csharp
public static class EditorSettings {
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
}
}
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
}
}
Иерархия папок будет простой: Все способности хранятся в Ресурсы\ПользовательскиеСпособности\ПапкаСпособности\Способность.ассет
Теперь перейдем в статический метод CreateAbilityFolders и создадим папки. Не забудьте подключить пространство System.IO.
Синтаксис:
Используется csharp
public static class EditorSettings {
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
string path = Application.dataPath + "/" + SPELL_ASSET_FOLDER;
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
path += abilityName + "/";
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
}
}
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
string path = Application.dataPath + "/" + SPELL_ASSET_FOLDER;
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
path += abilityName + "/";
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
}
}
Здесь я в начале получаю полный путь к проекту включая папку "Assets", дальше я прибавляю к этому пути путь к папке UserAbilities и
проверяю существует эта папка или нет, если не существует то создаю ее в проекте с помощью метода Directory.CreateDirectory
Дальше, когда я уже имею папку UserAbilities мне нужна папка способности, поэтому я к полному пути прибавляю еще и имя способности и
создаю папку которая будет хранилищем способности.
Должно получиться что то типа этого.
На картинке показана простейшая иерархия папок.
И так когда папки созданы их надо чемто наполнить. Поэтому переходим в созданию ассета способности.
Многие разработчики часто сталкиваются делать разного рода загрузчики которые выполняют какой то процесс во времени.
Допустим многие знают чтобы загрузить сцену не моментально, а постепенно - разработчики используют корутину для того чтобы потянуть
время - имитировать загрузку.
В нашем же случае если способность будет иметь много всяких данных или компонентов, то при сохранении - при моментальном сохранении
вы можете заметить лаг - ожидания выполнения сохранения, размер этого лага может быть разным в зависимости от того насколько крупный
объект вы хотите сохранить. Поэтому чтобы скрасить этот лаг и чтобы пользователю не показалось что его редактор перестал работать мы
должны будем создать некую видимость работы - процесса который будет наглядно давать понять пользователю что редактор чем то занят.
Но сложность в том что корутину в редакторе запускать нельзя, а все апдейты перестанут отвечать при лаге, поэтому не совсем понятно
как же такой крупный процесс как "сохранение способности" сделать постепенным процессом!?
На этот случай есть в области редактора unity метод DisplayProgressBar который показывает простейший прогресс бар.
Примерно как на картинке только для Windows.
Чтобы его отобразить нужно вызвать EditorUtility.DisplayProgressBar, и чтобы прекратить его показывать EditorUtility.ClearProgressBar
Для его отображения нам нужна будет некая итерация - простой цикл.
Помните я в раньше упоминал об yield инструкциях. Эти инструкции полезны тем, что конечный объем массива мы заранее не знаем, т к
можем во время пересчета одного елемента за другим в цикле также выполнять какие то другие действия до первой остановки - yield
инструкции которая сообщает циклу о том что нужно перейти к другому элементу из "последовательности". Я рассматривал их работу в
части где мы создавали обертки для способностей в менеджере способностей.
В отличие от этого случая нам не будет важен результат массива или что он будет возвращать нам нужно только будет разбить процесс
сохранения способности на части.
Метод DisplayProgressBar берет три параметра: заголовок процесса, название текущего процесса и процент его выполнения от 0 до 1.
Поэтому для начала создадим простую структуру которая будет возвращаться в цикле и показывать ПРОГРЕСС выполнения процесса сохранения.
Это простая структура
Синтаксис:
Используется csharp
public struct progress {
public string content;
private float prog;
public float currentProgress {
get { return this.prog;}
set { this.prog = Mathf.Clamp01(value);}
}
public progress(float progress, string content) {
this.currentProgress = progress;
this.content = content;
}
}
public string content;
private float prog;
public float currentProgress {
get { return this.prog;}
set { this.prog = Mathf.Clamp01(value);}
}
public progress(float progress, string content) {
this.currentProgress = progress;
this.content = content;
}
}
При каждом возвращении елемента progress из последовательности мы будем наглядно показывать какой процесс мы выполняем и сколько нам
осталось его выполнять в процентах.
Теперь создадим статический метод SaveProgressive в статическом классе EditorSettings который будет брать параметр - способность и
возвращать нумерованный список с элементом progress.
Синтаксис:
Используется csharp
public static class EditorSettings {
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
string path = Application.dataPath + "/" + SPELL_ASSET_FOLDER;
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
path += abilityName + "/";
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
}
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
yield break;
}
}
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
}
public static void CreateAbilityFolders(string abilityName) {
string path = Application.dataPath + "/" + SPELL_ASSET_FOLDER;
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
path += abilityName + "/";
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
}
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
yield break;
}
}
Теперь переходим в метод SaveAbility в котором мы будем отображать прогресс бар.
Синтаксис:
Используется csharp
public static class EditorSettings {
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
try {
foreach(progress process in SaveProgressive(ability))
EditorUtility.DisplayProgressBar("Saving ability: " + ability.name, process.content, process.currentProgress);
} finally {
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
}
public static void CreateAbilityFolders(string abilityName) {
string path = Application.dataPath + "/" + SPELL_ASSET_FOLDER;
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
path += abilityName + "/";
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
}
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
yield break;
}
}
public const string SPELL_ASSET_FOLDER = "Resources/UserAbilities/";
public static void SaveAbility(AbilityObject ability) {
try {
foreach(progress process in SaveProgressive(ability))
EditorUtility.DisplayProgressBar("Saving ability: " + ability.name, process.content, process.currentProgress);
} finally {
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
}
public static void CreateAbilityFolders(string abilityName) {
string path = Application.dataPath + "/" + SPELL_ASSET_FOLDER;
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
path += abilityName + "/";
if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
}
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
yield break;
}
}
Его работа проста - метода SaveAbility. Он в цикле показывает прогресс бар с текущим ПРОГРЕССОМ, по завершению цикла из области в try мы переходим в область finally и закрываем окошко прогресс бара.
Я запихнул действия прогресс бара в try finally области т к finally всегда вызывается после выполнения или не выполнения действия в области try, а catch вызывается только при возникновении ошибки в области try.
Все эти действия можно было записать и так
Синтаксис:
Используется csharp
public static void SaveAbility(AbilityObject ability) {
foreach(progress process in SaveProgressive(ability))
EditorUtility.DisplayProgressBar("Saving ability: " + ability.name, process.content, process.currentProgress);
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
foreach(progress process in SaveProgressive(ability))
EditorUtility.DisplayProgressBar("Saving ability: " + ability.name, process.content, process.currentProgress);
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
}
И разницы в производительности вы не увидите абсолютно - это чисто мое собственное решение.
Теперь когда мы наглядно показываем процесс сохранения способности в методе SaveAbility мы переходим в метод SaveProgressive где
займемся уже конкретно сохранением.
Я буду рассматривать этот метод отдельно от класса потому что он довольно таки объемный.
Наши способности в редакторе создаются через вызов оператора new() который создает экземпляры через конструкторы. Такой вид создания
скриптовых объектов
Скрытый текст:
не подходит для физического создания объектов в редакторе unity, о чем я говорил в прошлой части.
Создание скриптовых объектов на физическом уровне достигается путем вызова метода CreateInstance в который мы передаем тип объекта
который хотим создать.
Выглядит это так.
Синтаксис:
Используется csharp
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
}
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
}
Как видите таким способом я создаю скриптовый объект на физическом уровне
Скрытый текст:
Но полученный скриптовый объект data пустой - т к мы создали его только что.
Чтобы не переписывать методы создания способностей с помощью метода CreateInstance каждой способности, а не с помощью оператора new(), я просто передам ему ссылку на виртуальный объект способности. Т к AbilityObject уже наследуется от ScriptableObject
Синтаксис:
Используется csharp
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
data = ability;
data.name = ability.name;
yield return new progress(.9f, "Updating data");
}
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
data = ability;
data.name = ability.name;
yield return new progress(.9f, "Updating data");
}
Дальше мне надо будет создать нужные мне папки где будет содержаться сохраненная способность.
Синтаксис:
Используется csharp
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
CreateAbilityFolders(ability.name);
yield return new progress(.2f, "Creating folders");
data = ability;
data.name = ability.name;
yield return new progress(.9f, "Updating data");
}
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
CreateAbilityFolders(ability.name);
yield return new progress(.2f, "Creating folders");
data = ability;
data.name = ability.name;
yield return new progress(.9f, "Updating data");
}
И перейдем к сохранению(созданию ассета) способности - к классу AssetDatabase
Синтаксис:
Используется csharp
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
CreateAbilityFolders(ability.name);
yield return new progress(.2f, "Creating folders");
data = ability;
data.name = ability.name;
yield return new progress(.9f, "Updating data");
string path = "Assets/" + SPELL_ASSET_FOLDER + data.name + "/" + data.name + ".asset";
AssetDatabase.CreateAsset(data, path);
AssetDatabase.SaveAssets();
yield return new progress(1, "Complete");
}
ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
yield return new progress(.1f, "Creating instance");
CreateAbilityFolders(ability.name);
yield return new progress(.2f, "Creating folders");
data = ability;
data.name = ability.name;
yield return new progress(.9f, "Updating data");
string path = "Assets/" + SPELL_ASSET_FOLDER + data.name + "/" + data.name + ".asset";
AssetDatabase.CreateAsset(data, path);
AssetDatabase.SaveAssets();
yield return new progress(1, "Complete");
}
Статический метод CreateAsset класса AssetDataBase работает с путями начинающиеся с папки "Assets".
Думаю здесь все просто для понимания.
Теперь давайте вернемся к методу TryToSave класса SpellWindow и передадим способность для сохранения.
Скрытый текст:
Синтаксис:
Используется csharp
public class SpellWindow : EditorWindow {
private void TryToSave() {
EditorSettings.SaveAbility(this.localAbility);
this.localAbility = null;
}
}
private void TryToSave() {
EditorSettings.SaveAbility(this.localAbility);
this.localAbility = null;
}
}
Просто вызовим статический метод SaveAbility статического класса EditorSettings и обнулим нашу способность чтобы создать следующие.
Можно протестировать, сохранив любую способность.
Как видно на картинке примерно такой у вас должен быть результат если не возникнут какие либо ошибки.
Заключение: в этой части я лишь рассмотрел первую задачу в "SaveLoad" системе - сохранение способности. В следующей части я расскажу
как сохранять компоненты способностей.
Следующая часть
автор: этот хрен, он же llka, он же lawsonilka