Универсальная система способностей(спеллов) часть XIII

Научился сам? Помоги начинающему.

Универсальная система способностей(спеллов) часть XIII

Сообщение lawsonilka 19 янв 2015, 20:05

Создание системы универсальных способностей(спеллов) на unity3D Часть XIII

Сохранение способностей.

В прошлой части я рассмотрел разницу между обычным - пользовательским объектом и unity объектом.
В этой части статьи нам будут нужны только скриптовые объекты которые будут храниться в проекте.
pic29.png

Это будет выглядеть примерно как на картинке.

Для того чтобы наш базовый класс 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;
 }

}

И все. Больше мы ничего менять или добавлять в этом классе пока не будем.

Перейдем к его сохранению.
Теперь создадим метод который будет сохранять уже скриптовый объект - способность в проекте.

Давайте в статическом классе EditorSettings создадим статический метод SaveAbility который будем принимать один параметр - нашу
способность.
Синтаксис:
Используется csharp
public static class EditorSettings {
 
 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) {

 }

}

Также мне нужна будет строковая константа где я буду хранить название общей папки 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) {

 }

}

Иерархия папок будет простой: Все способности хранятся в Ресурсы\ПользовательскиеСпособности\ПапкаСпособности\Способность.ассет
Теперь перейдем в статический метод 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);
 }

}

Здесь я в начале получаю полный путь к проекту включая папку "Assets", дальше я прибавляю к этому пути путь к папке UserAbilities и
проверяю существует эта папка или нет, если не существует то создаю ее в проекте с помощью метода Directory.CreateDirectory
Дальше, когда я уже имею папку UserAbilities мне нужна папка способности, поэтому я к полному пути прибавляю еще и имя способности и
создаю папку которая будет хранилищем способности.
Должно получиться что то типа этого.
pic30.png

На картинке показана простейшая иерархия папок.

И так когда папки созданы их надо чемто наполнить. Поэтому переходим в созданию ассета способности.

Многие разработчики часто сталкиваются делать разного рода загрузчики которые выполняют какой то процесс во времени.
Допустим многие знают чтобы загрузить сцену не моментально, а постепенно - разработчики используют корутину для того чтобы потянуть
время - имитировать загрузку.
В нашем же случае если способность будет иметь много всяких данных или компонентов, то при сохранении - при моментальном сохранении
вы можете заметить лаг - ожидания выполнения сохранения, размер этого лага может быть разным в зависимости от того насколько крупный
объект вы хотите сохранить. Поэтому чтобы скрасить этот лаг и чтобы пользователю не показалось что его редактор перестал работать мы
должны будем создать некую видимость работы - процесса который будет наглядно давать понять пользователю что редактор чем то занят.
Но сложность в том что корутину в редакторе запускать нельзя, а все апдейты перестанут отвечать при лаге, поэтому не совсем понятно
как же такой крупный процесс как "сохранение способности" сделать постепенным процессом!?

На этот случай есть в области редактора unity метод DisplayProgressBar который показывает простейший прогресс бар.
pic31.png

Примерно как на картинке только для 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;
 }
                 
}

При каждом возвращении елемента 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;
 }

}

Теперь переходим в метод 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;
 }

}

Его работа проста - метода 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();
}

И разницы в производительности вы не увидите абсолютно - это чисто мое собственное решение.

Теперь когда мы наглядно показываем процесс сохранения способности в методе SaveAbility мы переходим в метод SaveProgressive где
займемся уже конкретно сохранением.
Я буду рассматривать этот метод отдельно от класса потому что он довольно таки объемный.

Наши способности в редакторе создаются через вызов оператора new() который создает экземпляры через конструкторы. Такой вид создания
скриптовых объектов
Скрытый текст:
А базовый класс AbilityObject уже является скриптовым объектом т к мы унаследовали его от класса ScriptableObject

не подходит для физического создания объектов в редакторе unity, о чем я говорил в прошлой части.
Создание скриптовых объектов на физическом уровне достигается путем вызова метода CreateInstance в который мы передаем тип объекта
который хотим создать.
Выглядит это так.
Синтаксис:
Используется csharp
private static IEnumerable<progress> SaveProgressive(AbilityObject ability) {
 ScriptableObject data = AbilityObject.CreateInstance(ability.GetType());
 yield return new progress(.1f, "Creating instance");
}

Как видите таким способом я создаю скриптовый объект на физическом уровне
Скрытый текст:
Т к способность AbilityObject созданный оператором new() находится в виртуальном виде

Но полученный скриптовый объект 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");
}

Дальше мне надо будет создать нужные мне папки где будет содержаться сохраненная способность.
Синтаксис:
Используется 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");
}

И перейдем к сохранению(созданию ассета) способности - к классу 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");
}

Статический метод CreateAsset класса AssetDataBase работает с путями начинающиеся с папки "Assets".
Думаю здесь все просто для понимания.

Теперь давайте вернемся к методу TryToSave класса SpellWindow и передадим способность для сохранения.
Скрытый текст:
Я привожу лишь часть класса SpellWindow т к его создание я разбирал в предыдущих частях.

Синтаксис:
Используется csharp
public class SpellWindow : EditorWindow {
 private void TryToSave() {
   EditorSettings.SaveAbility(this.localAbility);
   this.localAbility = null;
 }
}

Просто вызовим статический метод SaveAbility статического класса EditorSettings и обнулим нашу способность чтобы создать следующие.

Можно протестировать, сохранив любую способность.
pic32.png

Как видно на картинке примерно такой у вас должен быть результат если не возникнут какие либо ошибки.

Заключение: в этой части я лишь рассмотрел первую задачу в "SaveLoad" системе - сохранение способности. В следующей части я расскажу
как сохранять компоненты способностей.

Следующая часть

автор: этот хрен, он же llka, он же lawsonilka
У вас нет доступа для просмотра вложений в этом сообщении.
lawsonilka
UNIверсал
 
Сообщения: 390
Зарегистрирован: 21 окт 2014, 14:48

Вернуться в Уроки

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 2


cron