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

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

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

Сообщение llka 16 дек 2014, 01:23

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

Редактор

Вот и перехожу я к самой интересной части. Вообще говоря, загляну немного в будущее, скажу что у нас будет ТРИ редактора:
один для способностей, другой для скриптовых объектов, и третий для некоего SpellManager который будет представлять собой
компонент который будет добавляться на объекты которые будут поддерживать систему способностей что мы разработаем.
По сравнению с редакторами способностей таких игр как StarCraft или WarCraft, мой редактор очень скромен на функционал, но
это лишь кажется изза того что другие редакторы уже заранее наполнены компонентами(свойствами) в способностях. В этом
редакторе, как я уже говорил, пользователь сам будет решать чем наполнять уникальную способность. Исходя из этого редактор
только предлагает пользователю сделать выбор какую из двух способностей он хочет создать: пассивную или активную. Дальше
редактор просто будет показывать доступные инструменты для работы со способностью.
pic9.png

Как видно на изображении я разбил редактор на три части.
Первая часть просто предоставляет пользователю выбор какую способность он хочет создать.
Вторая часть, уже после создания способности, предлагает наполнить ПУСТЫШКУ компонентами, эта важная часть - т к если вы
помните - у нас есть по два дополнительных типа "подспособности" которые имеют интсрументы для работы с компонентами. Так вот
эта вторая часть уже дает пользователю выбор при добавлении компонентов использовать следующий вид "подспособности" с
интерфейсом IAbilityComponentable и дальше пользователь переходит в третью часть.
Третья часть: в этой части редактор уже работает не с ПУСТЫШКОЙ, а со способностью в которую можно добавлять компоненты, и
эта часть показывает что за компоненты находятся в способности и что они из себя представляют.

На самом деле, тем кто сталкивался уже с работой о расширении редактора, эта часть статьи покажется знакомой потому что я не
буду использовать какие то сложные или хитрые схемы т к мы уже заранее создали всю базу и теперь просто надо представить это
все в визуальном виде - в виде редактора.

Для редактора я решил использовать специальное окно также известное как EditorWindow.
Ну а теперь переходим к программной части.
Я создал скрипт SpellWindow и унаследовал его от EditorWindow. Не забудьте подключить систему UnityEditor
Синтаксис:
Используется csharp
using System;
#if UNITY_EDITOR
using UnityEditor;
using System.IO;
public class SpellWindow : EditorWindow {

}
#endif

Как видите я заключил скрипт в некую область UNITY_EDITOR - эта область будет давать понять самому редактору unity3D что мы
работаем в режиме редактора и только в этом режиме, тоесть весь код который заключен в эту область не войдет в финальный билд
приложения.

Дальше я наполнил SpellWindow такими свойствами
defStyle - стиль по умолчанию, чисто визуальная корректировка, чтобы текст отображался симпатичнее.
localAbility - объект способности, будет служить нам переменной в которой будет содержаться редактируемая способность.
abilitiesComponent - интерфейс IAbilityComponentable объекта способности в котором мы будет содержать инструменты для работы
с компонентами.
localComponentType - тип компонента который пользователь захочет добавить.
Вот как выглядит скрипт теперь.
Синтаксис:
Используется csharp
using System;
#if UNITY_EDITOR
using UnityEditor;
using System.IO;
public class SpellWindow : EditorWindow {
 
 public static GUIStyle defStyle = null;
 
  private AbilityObject localAbility = null;
  private IAbilityComponentable abilitiesComponent = null;
  private AbilitySettings.ComponentValueTarget localComponentType;

}

Так ну теперь дальше я буду приводить только методы скрипта SpellWindow т к каждый раз копировать весь скрипт будет трудно.
Первая функция это знаменитый OnGUI, функция которая обрабатывает GUI элементы.
Синтаксис:
Используется csharp
private void OnGUI() {
 if (this.isEditor) {
  UpdateWindowGUI();
 }
}

Эта функция вызывает другую функцию UpdateWindowGUI в которой я уже буду работать с GUI элементами окна.
Синтаксис:
Используется csharp
private void UpdateWindowGUI() {
 if (localAbility == null) DrawMainStartGUI();
 else {
  DrawAbilityControl();
  GUILayout.Space(10);
  DrawControlState();
 }
}

Функция DrawMainStartGUI будет рисовать область в окне когда пользователь будет только в ПЕРВОЙ части редактора, когда ему
будет представлен выбор типа способности.
Функция DrawAbilityControl будет рисовать область в окне когда пользователь уже создал способность определенного типа и
теперь работает именно с ней.
Функция DrawControlState будет рисовать две кнопки "Сохранить способность" и "Удалить способность".
Начнем с функции DrawMainStartGUI. Эта функция будет рисовать две кнопки "Passive" и "Active" которые будут создать экземпляры способности.
Синтаксис:
Используется csharp
private void DrawMainStartGUI() {
 Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 15);
 r.height += 25;
 GUI.Box(r, "Create new ability");
 GUILayout.BeginHorizontal();
  if (GUILayout.Button("Passive")) this.localAbility = new PassiveAbility("New Passive");
  if (GUILayout.Button("Active")) this.localAbility = new ActiveAbility("New Active");
 GUILayout.EndHorizontal();
}

Готово. В методе GUILayoutUtility.GetRect я использовал одну статическую переменную WindowSize из статического класса
EditorSettings который я создал специально чтобы он хранил нужные мне данные для редактора. В данном случае этот статический
класс содержит свойство WindowSize Vector2 который помогает мне определить размеры окна. Рекомендую вам тоже создать подобный
класс чтобы наполнить его данными как и в моем случае, т к нам пригодится лишний объект для более удобной работы с редактором.

Теперь когда пользователь нажмет одну из кнопок - он создаст способность ПУСТЫШКУ определенного типа, и дальше через функцию
DrawAbilityControl будет работать именно с ней.
Функция DrawAbilityControl, будет содержать поля со свойствами способности.
pic10.png

Попробуем нарисовать что то типа этого как на картинке.
Синтаксис:
Используется csharp
private void DrawAbilityControl() {
 DrawAbilityMainSettings(this.localAbility);
}

Вызовим функцию DrawAbilityMainSettings с параметром - экземпляром способности.
Синтаксис:
Используется csharp
private void DrawAbilityMainSettings(AbilityObject ability) {
 Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 20);
 GUI.Box(r, "Settings of: " + ability.name + " ability");
 GUILayout.Label("Abilities description", defStyle);
 ability.abilityDescription = GUILayout.TextArea(ability.abilityDescription);
 ability.name = EditorGUILayout.TextField("Name: ", ability.name);
 GUILayout.Space(5);
 GUILayout.BeginHorizontal();
  GUILayout.Label("Icon: ");
  ability.icon = (Texture2D)EditorGUILayout.ObjectField(ability.icon, typeof(Texture2D), false);
 GUILayout.EndHorizontal();
 if (ability.type == AbilitySettings.AbilityType.Active) {
  ActiveAbility activeAbility = (ActiveAbility)ability;
  activeAbility.castKey = (KeyCode)EditorGUILayout.EnumPopup("Cast button: ", activeAbility.castKey);
  activeAbility.cooldown = EditorGUILayout.FloatField("Cooldown: ", activeAbility.cooldown);
 }
}

Как видите я выделил каждое поле под каждое свойство способности, но также я проверяю если наша способность "активная" то т к
этот тип способности содержит дополнительные свойства - я также должен их нарисовать.

Теперь вернемся к функции DrawAbilityControl и займемся областью с компонентами.
Синтаксис:
Используется csharp
private void DrawAbilityControl() {
 DrawAbilityMainSettings(this.localAbility);
 Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 15);
 GUILayout.Space(5);
 r.height += 50;
 GUI.Box(r, "Ability components settings");
 this.localComponentType = (AbilitySettings.ComponentValueTarget)EditorGUILayout.EnumPopup("Select component type: ",

this.localComponentType);
 GUILayout.BeginHorizontal();
  if (this.abilitiesComponent != null) {
   if (GUILayout.Button("Add component")) TryToAddComponent();
   if (GUILayout.Button("Remove all")) this.abilitiesComponent.RemoveComponents<AbilityComponent>();
  } else {
   if (GUILayout.Button("Add component")) TryToAddComponent();
  }
 GUILayout.EndHorizontal();

 GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 5);

 if (this.abilitiesComponent != null) {
  DrawAbilitiesComponents();
 }
}

И так, ну функцию DrawAbilityMainSettings мы уже рассмотрели, дальше я рисую область где с помощью EditorGUILayout.EnumPopup
пользователю предоставляется выбор какой тип компонента добавить в способность, как вы помните мы использовали перечисление
где указывали "цели" компонентов.
Дальше я проверяю если у редактируемой способности уже есть инструментарий для работы с компонентами в виде интерфейса
IAbilityComponentable - значит я рисую две кнопки, первая добавляет выбранный тип компонента и вторая удаляет все имеющиеся
компоненты у способности, если же у редактируемой способности нет этого интерфейса то тогда я просто рисую одну кнопку
которая будет добавлять выбранный компонент в способность.
Дальше я проверяю еще раз если у редактируемой способности имеется интерфейс IAbilityComponentable тогда с помощью функции
DrawAbilitiesComponents будем рисовать область с компонентами и их свойствами.
Так теперь сначала займемся добавлением компонентов а именно функцией TryToAddComponent. Но сначала в статической классе
EditorSettings создадим один метод который будет создавать нам способность которая будет содержать компоненты.
Синтаксис:
Используется csharp
public static class EditorSettings {
 
 public static Vector2 WindowSize = new Vector2(250, 200);

 public static AbilityObject CreateCustomAbility(AbilitySettings.AbilityType type) {
  AbilityObject ability = null;
  switch(type) {
   case AbilitySettings.AbilityType.Active: ability = new ActiveCustomAbility("New Active custom");
   break;
   case AbilitySettings.AbilityType.Passive: ability = new PassiveCustomAbility("New Passive custom");
   break;
  }
  return ability;
 }

}

Как видите метод CreateCustomAbility берет в виде параметра тип способности AbilityType ,а дальше с помощью switch решает
какую способность создать - все очень просто.
Возвращаемся в функцию TryToAddComponent.
Синтаксис:
Используется csharp
private void TryToAddComponent() {
 if (this.abilitiesComponent == null) {
  this.localAbility = EditorSettings.CreateCustomAbility(this.localAbility.type);
  this.abilitiesComponent = this.localAbility as IAbilityComponentable;
  if (this.abilitiesComponent != null) TryToAddComponent();
 } else {
  AbilityComponent component = AbilitySettings.CreateComponentForTarget(this.localComponentType, "New component");
  this.abilitiesComponent.AddComponent(component);
 }
}

И так в этой функции будет происходить самая черная магия всего редактора.
Для начала я проверяю есть ли у редактируемой способности интерфейс IAbilityComponentable - если у способности его нет, что
значит - мы работает со способность ПУСТЫШКОЙ, в этой случае нам сначала нужно с помощью метода CreateCustomAbility
статического класса EditorSettings создать новую способность, но уже способность которая содержит инструменты для работы с
компонентами. Дальше я присваиваю переменной this.abilitiesComponent объект
способности но как(as) интерфейс IAbilityComponentable, и если все таки эта новая способность им обладает еще раз вызываем
функцию TryToAddComponent чтобы в этот раз способность добавила в себя выбранный ранее компонент т к теперь она имеет для
этого нужный инструментарий.
В области else я проверяю если редактируемая способность имеет инструменты для работы с компонентами, тогда я создаю новый
компонент с помощью метода AbilitySettings.CreateComponentForTarget который мы объявили в статическом классе AbilitySettings
когда работали с "целями" в прошлых частях статьи. И после создания компонента просто добавляем его в способность с помощью
метода AddComponent.
Ну теперь дальнейшие действия будут по проще.
Вернемся к кнопке "Remove all" которая будет стирать все компоненты из способности.
Как вы помните мы добавляли метод RemoveComponents<T> где с помощью преобразования находили нужный компонент и удаляли его,
так вот как вы еще должны помнить все компоненты в способности представлены в виде объекта AbilityComponent т к когда я
передаю этот тип в метод RemoveComponents<AbilityComponent> то выбирают все компоненты данного типа - а раз каждый компонент
это и есть AbilityComponent то выбирутся все компоненты в способности и удаляться. Вот такая вот простая махинация.

Опять возвращаемся к функции DrawAbilityControl
После всех этих кнопок я наконец хочу нарисовать область где буду отображать компоненты способности с помощью функции DrawAbilitiesComponents
Синтаксис:
Используется csharp
private void DrawAbilitiesComponents() {
 if (this.abilitiesComponent == null) return;
 int count = this.abilitiesComponent.components.Length;
 GUILayout.Label("Ability components count: " + count, defStyle);
 GUILayout.Space(5);
 foreach(AbilityComponent component in this.abilitiesComponent.components) {
  DrawComponentSettings(component);
 }
}

Сначала я проверяю может ли редактируемая способность содержать в себе компоненты, дальше я рисую область где отображается
колво компонентов у способности.
Дальше я с помощью цикла буду рисовать специальную область под каждый компонент с помощью метода DrawComponentSettings
который будет брать в виде аргумента сам компонент. Перейдем теперь к методу DrawComponentSettings.

Но сначала теория!

И так что мы можем сказать о компоненте, какими свойствами он обладает!? Как вы знаете все компоненты в способности
сохраняются в общий список компонентов КАК объекты AbilityComponent а теперь если мы посмотрим какие свойствами обладает этот
объект: name, targetValue и objectValue, но эти все свойства не дают точного определения КАКОЕ именно значение в себе имеет
компонент, у нас есть свойство objectValue но: во первых это геттер который просто вернет значение, а во вторых редактор
unity3D не знает что это за значение и какую область рисовать для этого объекта, то ли это число или строка, или текстура - для
каждого этого объекта у unity3D есть свое определенное поле. Те кто раньше работал с расширением редактора могут решить: а
почему бы не использовать универсальную область PropertyField у редактора unity3D ведь он сам нарисует нам нужную область,
ведь для него нужно только знать тип значение, а его мы можем получить через свойство objectValue.GetType() и это верно. НО!
Это верно только для ШАБЛОНОВ, для компонентов с НЕОПРЕДЕЛЕННОЙ реализацией, которые содержать элементарные типы данных, а
что тогда делать с компонентами вроде AbilityTimeComponent ведь этот компонент уже имеет КОНКРЕТНУЮ реализацию, также он имеет
в себе посторонние свойства вроде timesRepeat, теперь мы не можем просто обойтись универсальной областью PropertyField. В
решении этой проблемы есть маленькая хитрость(не скажу что она больно уж оригинальна). Когда мы работаем с объектами на сцене
мы можем увидеть в окне Inspector все компоненты выбранного объекта - эти компоненты рисуют свои области для своих свойств,
тоесть нам не обязательно для каждого компонента отдельно расширять редактор чтобы нарисовать свойства этого компонента, это
уже имеется в наличии у стандартного редактора и он просто рисует свойства компонента через него самого - в этом и
заключается хитрость. В нашем случае редактор не знает сколько будет свойств у компонента, но мы можем просто отдать работу
по обработке свойств самому компоненту - пусть он сам рисует свойства которые содержит не вещая эту работу на редактор.
Для реализации этой хитрости мне нужно вызывать некую функцию у компонента которая будет содержать в себе нужную для
каждого компонента реализацию - следовательно мне нужно это сделать в базовом объекте AbilityComponent но при этом чтобы
каждый компонент мог сам решить какую реализацию выбрать в данной функции.
Я решил сделать эту функцию виртуальной и назвал ее DrawComponentBase, эта функция просто будет в зависимости от компонента
рисовать его свойства самостоятельно, но т к мы все еще работает в режиме редактора нужно будет эту функцию вынести в область
UNITY_EDITOR, и так вот как теперь выглядит базовый абстрактный класс AbilityComponent
Синтаксис:
Используется csharp
public abstract class AbilityComponent {
         
 public AbilitySettings.ComponentValueTarget targetValue { get; protected set;}
 public string name = "";
         
 public virtual void DrawComponentBase() {
#if UNITY_EDITOR
  this.name = EditorGUILayout.TextField("Name: ", this.name);
#endif
 }
         
 public bool CheckForAttribute<T>() where T : Attribute {
  foreach(Attribute attribute in Attribute.GetCustomAttributes(this.GetType())) {
   if (attribute is T) return true;
  }
  return false;
 }
         
 public abstract System.Object objectValue { get;}
         
}

Как видите вся реализация функции DrawComponentBase заключена в область редактора чтобы допустим вызвав ее в рантайме не
произошла ошибка.
Я мог сделать эту функцию абстрактной, но тогда бы мне пришлось ее еще перегрузить в классе AbilityComponentValue и наполнить
тем же самым, поэтому я решил объявить ее виртуальной.
И так теперь когда я вызову эту функцию у компонента в редакторе способностей, компонент сам будет рисовать свои свойства, в
данном случае для начала он будет рисовать его имя.
А теперь вернемся в функцию DrawComponentSettings
Синтаксис:
Используется csharp
private void DrawComponentSettings(AbilityComponent component) {
 Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 20);
 GUI.Box(r, component.name);
 component.DrawComponentBase();
 if (GUILayout.Button("Remove component: " + component.targetValue)) TryToRemoveComponent(component);
}

Как вы видите теперь я просто вызываю функцию DrawComponentBase у компонента и дальше редактору не нужно беспокоится сколько
и какие свойства находятся в компоненте.
pic11.png

А теперь попробуем построить что то типа этого как на картинке.
Теперь когда я независимо от типа компонента вызываю его редактор который будет рисовать только его свойства мне нужно чтобы
каждый шаблон в зависимости от типа своего значения рисовал нужную мне область - т к помните что Unity3D требует точно
определять какую область рисовать.

Переходим к шаблонам компонентов.
Первым будет шаблон AbilityFloatComponent который будет рисовать область для числа т к его значение имеет "float" тип.
Для начала перегрузим функцию DrawComponentBase
Синтаксис:
Используется csharp
public override void DrawComponentBase() {
 base.DrawComponentBase();
#if UNITY_EDITOR
 this.value = EditorGUILayout.FloatField(this.valueName, this.value);
#endif
}

Как вы видите я сначала вызываю функцию DrawComponentBase у базового типа который в свою очередь нарисует область с именем
компонента из того примера что в базовом абстрактном классе AbilityComponent и только потом, опять же заключая область
свойства в UNITY_EDITOR, я рисую область FloatField для компонента который будет хранить в себе данное значение. Ну а теперь
давайте сделаем это же для каждого шаблона, вот теперь как они будят выглядеть.
Синтаксис:
Используется csharp
public class AbilityFloatComponent : AbilityComponentValue<float> {
       
 public override void DrawComponentBase () {
  base.DrawComponentBase();
#if UNITY_EDITOR
  this.value = EditorGUILayout.FloatField(this.valueName, this.value);
#endif
 }
         
 public AbilityFloatComponent(string name, AbilitySettings.ComponentValueTarget target) : base(name, target) {}
         
}
       
public class AbilityIntComponent : AbilityComponentValue<int> {
       
 public override void DrawComponentBase () {
  base.DrawComponentBase();
#if UNITY_EDITOR
  this.value = EditorGUILayout.IntField(this.valueName, this.value);
#endif
 }
               
 public AbilityIntComponent(string name, AbilitySettings.ComponentValueTarget target) : base(name, target) {}
               
}
       
public class AbilityStringComponent : AbilityComponentValue<string> {
       
 public override void DrawComponentBase () {
  base.DrawComponentBase();
#if UNITY_EDITOR
  this.value = EditorGUILayout.TextField(this.valueName, this.value);
#endif
 }
               
 public AbilityStringComponent(string name, AbilitySettings.ComponentValueTarget target) : base(name, target) {}
               
}
       
public class AbilityObjectComponent : AbilityComponentValue<UnityEngine.Object> {

               
 public AbilityObjectComponent(string name, AbilitySettings.ComponentValueTarget target) : base(name, target) {}
               
}

Как видите я не коснулся только двух компонентов это ШАБЛОНА AbilityObjectComponent т к я не знаю какой именно объект вы
будете хранить в этом шаблоне - в этом случае вам придется самим решать какого типа ObjectField вам рисовать. И я не тронул
компонент AbilityTimeComponent т к там помимо базовых свойств также содержаться дополнительные.
Синтаксис:
Используется csharp
[DisableComponentMultiplyUsage]
public class AbilityTimeComponent : AbilityFloatComponent {
       
 private int componentRepeatTimes = 0;
         
 public AbilitySettings.AbilityTimedType timeType;
               
 public override void DrawComponentBase () {
  base.DrawComponentBase();
#if UNITY_EDITOR
  this.timeType = (AbilitySettings.AbilityTimedType)EditorGUILayout.EnumPopup("Timed type: ", this.timeType);
  if (this.timeType == AbilitySettings.AbilityTimedType.Repeat) {
   this.repeatTimes = EditorGUILayout.IntField("Repeat times: ", this.repeatTimes);
  }
#endif
 }
               
 public int repeatTimes {
  get { return this.componentRepeatTimes;}
  set { this.componentRepeatTimes = (value < 0) ? 0 : value;}
 }
               
 public float time {
  get { return base.value;}
  set { base.value = (value < 0f) ? 0f : value;}
 }
         
 public AbilityTimeComponent(string name, AbilitySettings.ComponentValueTarget target) : base(name,

AbilitySettings.ComponentValueTarget.Time) {}
         
}

Вот как теперь выглядит функция DrawComponentBase у компонента AbilityTimeComponent, он будет рисовать помимо базовых
областей еще дополнительные для своих ОСОБЫХ свойств.

Ну теперь все готово и можно смело рисовать каждый компонент не взирая на его тип просто вызвав функцию DrawComponentBase.
И тогда возвращаемся в редактор.
Синтаксис:
Используется csharp
private void DrawComponentSettings(AbilityComponent component) {
 Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 20);
 GUI.Box(r, component.name);
 component.DrawComponentBase();
 if (GUILayout.Button("Remove component: " + component.targetValue)) TryToRemoveComponent(component);
}

Теперь я думаю вам понятен принцип работы функции DrawComponentSettings.
Ну тогда завершим наш редактор функцией DrawControlState которая будет рисовать две кнопки сохранения и удаления редактируемой
способности.
Синтаксис:
Используется csharp
private void DrawControlState() {
 Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 2.5f);
 r.height += 27.5f;
 GUI.Box(r, "");
 GUILayout.BeginHorizontal();
  if (GUILayout.Button("Save: " + this.localAbility.name)) TryToSave();
  if (GUILayout.Button("Delete")) {}
 GUILayout.EndHorizontal();
}

Ну тут все просто: рисуем кнопку "Save" которая пока что НЕ будет сохранять способность т к об этом я расскажу в другой части
статьи, и кнопку "Delete" которая будет стирать способность, он ней я тоже расскажу в другой части.

И так теперь нам нужно создать это окно в редакторе unity3D, это делается легко.
Я создал статическую функцию InitWindow в статической классе EditorSettings и добавил аттрибут MenuItem чтобы эту функция
вызывалась по нажатию MenuItem, вот что эта функция в себе содержит.
Синтаксис:
Используется csharp
[MenuItem("Window/Spell editor")]
public static void InitWindow() {
 SpellWindow window = (SpellWindow)EditorWindow.GetWindow(typeof(SpellWindow));
 window.maxSize = window.minSize = WindowSize;
 if (SpellWindow.defStyle == null) {
  SpellWindow.defStyle = new GUIStyle();
  SpellWindow.defStyle.alignment = TextAnchor.LowerCenter;
  SpellWindow.defStyle.fontStyle = FontStyle.Bold;
 }
}

При нажатии на кнопку выпадающего меню Window.Spell editor в окне редактора unity3D
pic12.png

Что изображено на картинке, я создам окно SpellWindow нашего редактора а также стиль defStyle который я объявил как
статический в SpellWindow.
Ну вот в принципе и готов редактор, точнее его окно.
Привожу весь код редакторы чтобы вы сверили то что получилось у вас.
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;
using System;
#if UNITY_EDITOR
using UnityEditor;
using System.IO;

namespace AbilitiesSystem.Editor {

#region Editor settings
 public static class EditorSettings {
               
  public static Vector2 WindowSize = new Vector2(250, 200);
               
  [MenuItem("Window/Spell editor")]
  public static void InitWindow() {
   SpellWindow window = (SpellWindow)EditorWindow.GetWindow(typeof(SpellWindow));
   window.maxSize = window.minSize = WindowSize;
   if (SpellWindow.defStyle == null) {
    SpellWindow.defStyle = new GUIStyle();
    SpellWindow.defStyle.alignment = TextAnchor.LowerCenter;
    SpellWindow.defStyle.fontStyle = FontStyle.Bold;
   }
  }
               
  public static AbilityObject CreateAbility(AbilitySettings.AbilityType type) {
   AbilityObject ability = null;
   switch(type) {
    case AbilitySettings.AbilityType.Active: ability = new ActiveAbility("New Active");
    break;
    case AbilitySettings.AbilityType.Passive: ability = new PassiveAbility("New Passive");
    break;
   }
   return ability;
  }
               
  public static AbilityObject CreateCustomAbility(AbilitySettings.AbilityType type) {
   AbilityObject ability = null;
   switch(type) {
    case AbilitySettings.AbilityType.Active: ability = new ActiveCustomAbility("New Active custom");
    break;
    case AbilitySettings.AbilityType.Passive: ability = new PassiveCustomAbility("New Passive custom");
    break;
   }
   return ability;
  }
               
 }
#endregion
 
#region Editor window
public class SpellWindow : EditorWindow {
 
  public static GUIStyle defStyle = null;
 
  private AbilityObject localAbility = null;
  private IAbilityComponentable abilitiesComponent = null;
  private AbilitySettings.ComponentValueTarget localComponentType;
 
  private void OnGUI() {
   if (this.isEditor) {
    UpdateWindowGUI();
   }
  }
 
 private void OnInspectorUpdate() {
  if (this.isEditor) {
   if (this.abilitiesComponent != null) {
    EditorSettings.WindowSize.y = 250 + (100  * this.abilitiesComponent.components.Length);
    this.maxSize = this.minSize = EditorSettings.WindowSize;
    if (this.abilitiesComponent.components.Length <= 0) {
     this.abilitiesComponent = null;
     this.localAbility = EditorSettings.CreateAbility(this.localAbility.type);
    }
   }
  }
 }
 
 private void UpdateWindowGUI() {
  if (localAbility == null) DrawMainStartGUI();
  else {
   DrawAbilityControl();
   GUILayout.Space(10);
   DrawControlState();
  }
 }
               
 private void DrawAbilityMainSettings(AbilityObject ability) {
  Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 20);
  GUI.Box(r, "Settings of: " + ability.name + " ability");
  GUILayout.Label("Abilities description", defStyle);
  ability.abilityDescription = GUILayout.TextArea(ability.abilityDescription);
  ability.name = EditorGUILayout.TextField("Name: ", ability.name);
  GUILayout.Space(5);
  GUILayout.BeginHorizontal();
   GUILayout.Label("Icon: ");
   ability.icon = (Texture2D)EditorGUILayout.ObjectField(ability.icon, typeof(Texture2D), false);
  GUILayout.EndHorizontal();
  if (ability.type == AbilitySettings.AbilityType.Active) {
   ActiveAbility activeAbility = (ActiveAbility)ability;
   activeAbility.castKey = (KeyCode)EditorGUILayout.EnumPopup("Cast button: ", activeAbility.castKey);
   activeAbility.cooldown = EditorGUILayout.FloatField("Cooldown: ", activeAbility.cooldown);
  }
 }
               
 private void DrawAbilityControl() {
  DrawAbilityMainSettings(this.localAbility);
  Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 15);
  GUILayout.Space(5);
  r.height += 50;
  GUI.Box(r, "Ability components settings");
  this.localComponentType = (AbilitySettings.ComponentValueTarget)EditorGUILayout.EnumPopup("Select component type: ", this.localComponentType);
  GUILayout.BeginHorizontal();
   if (this.abilitiesComponent != null) {
    if (GUILayout.Button("Add component")) TryToAddComponent();
    if (GUILayout.Button("Remove all")) this.abilitiesComponent.RemoveComponents<AbilityComponent>();
   } else {
    if (GUILayout.Button("Add component")) TryToAddComponent();
   }
  GUILayout.EndHorizontal();
  GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 5);
  if (this.abilitiesComponent != null) {
   DrawAbilitiesComponents();
  }
 }
               
 private void DrawAbilitiesComponents() {
  if (this.abilitiesComponent == null) return;
  int count = this.abilitiesComponent.components.Length;
  GUILayout.Label("Ability components count: " + count, defStyle);
  GUILayout.Space(5);
  foreach(AbilityComponent component in this.abilitiesComponent.components) {
   DrawComponentSettings(component);
  }
 }
               
 private void DrawComponentSettings(AbilityComponent component) {
  Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 20);
  GUI.Box(r, component.name);
  component.DrawComponentBase();
  if (GUILayout.Button("Remove component: " + component.targetValue)) TryToRemoveComponent(component);
 }
               
 private void TryToRemoveComponent(AbilityComponent component) {
  if (this.abilitiesComponent != null)
   this.abilitiesComponent.RemoveComponent(component);
 }
               
 private void TryToRemoveComponent(int componentIndex) {
  if (componentIndex < 0 || this.abilitiesComponent == null) return;
  else if (componentIndex >= this.abilitiesComponent.components.Length) return;
                 
  TryToRemoveComponent(this.abilitiesComponent.components[componentIndex]);
 }
               
 private void TryToAddComponent() {
  if (this.abilitiesComponent == null) {
   string oldName = this.localAbility.name;
   this.localAbility = EditorSettings.CreateCustomAbility(this.localAbility.type);
   this.localAbility.name = oldName;
   this.abilitiesComponent = this.localAbility as IAbilityComponentable;
   if (this.abilitiesComponent != null) TryToAddComponent();
  } else {
   AbilityComponent com = AbilitySettings.CreateComponentForTarget(this.localComponentType, "New component");
   this.abilitiesComponent.AddComponent(com);
  }
 }
               
 private void TryToSave() {
                       
 }
               
 private void DrawMainStartGUI() {
  Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 15);
  r.height += 25;
  GUI.Box(r, "Create new ability");
  GUILayout.BeginHorizontal();
   if (GUILayout.Button("Passive")) this.localAbility = EditorSettings.CreateAbility(AbilitySettings.AbilityType.Passive);
   if (GUILayout.Button("Active")) this.localAbility = EditorSettings.CreateAbility(AbilitySettings.AbilityType.Active);
  GUILayout.EndHorizontal();
 }
               
 private void DrawControlState() {
  Rect r = GUILayoutUtility.GetRect(EditorSettings.WindowSize.x, 2.5f);
  r.height += 27.5f;
  GUI.Box(r, "");
  GUILayout.BeginHorizontal();
   if (GUILayout.Button("Save: " + this.localAbility.name)) TryToSave();
   if (GUILayout.Button("Delete")) {}
  GUILayout.EndHorizontal();
 }
 
  public bool isEditor {
   get { return Application.isPlaying == false;}
  }
 
 }
#endregion

}
#endif

Заключение: теперь у вас есть редактор способностей, можно поиграться с ним по добавлять разных способностей, но пока еще рано
говорить о Save\Load системе и скриптовых объектах, поэтому следующая часть будет посвящена другому объекту а именно SpellManager который и будет управлять способностями персонажа,
контроллировать их и тд но уже в рантайме.

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

автор: этот хрен, он же llka, он же lawsonilka
У вас нет доступа для просмотра вложений в этом сообщении.
llka
UNIверсал
 
Сообщения: 359
Зарегистрирован: 08 янв 2014, 05:00

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

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

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