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

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

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

Сообщение lawson 06 дек 2014, 18:22

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

Способности. Активная.

И так в прошлый раз я рассмотрел создание ПАССИВНОЙ способности, теперь перехожу ко второму типа способностей - АКТИВНОЙ.

Для АКТИВНОЙ способности тоже нужно будет создать базовый класс ПУСТЫШКУ который будет наследоваться от главной базовой
способности AbilityObject, я назвал этот класс ActiveAbility с одним публичным конструктором как и в случае PassiveAbility,
только теперь конструктор будет задавать тип способности как Active.
Синтаксис:
Используется csharp
public class ActiveAbility : AbilityObject {

 public ActiveAbility(string name) : base(AbilitySettings.AbilityType.Active, name) {}

}

Но в отличии от PassiveAbility в АКТИВНОЙ способности ПУСТЫШКИ я добавил парочку свойств.
castKey - KeyCode это будет переменная кнопки на которую нужно нажать чтобы активировать способность
cooldows - это будет числовая переменная которая будет содержать время перезарядки способности.
Добавим эти свойства
Синтаксис:
Используется csharp
public class ActiveAbility : AbilityObject {
 
 public KeyCode castKey = KeyCode.F;
 
 private float abilityCooldown = 1f;
 
 public float cooldown {
  get { return this.abilityCooldown;}
  set { this.abilityCooldown = (value < 0.1f) ? 0.1f : value;}
 }
 
 public ActiveAbility(string name) : base(AbilitySettings.AbilityType.Active, name) {}
 
}

Активная способность ПУСТЫШКА готова.

Теперь мне как и в случае ПАССИВНОЙ способности с КОНКРЕТНОЙ реализацией, нужно создать аналогичную АКТИВНУЮ способность с
КОНКРЕТНОЙ реализацией которая будет наследоваться от АКТИВНОЙ способности ПУСТЫШКИ и от интерфейса IAbilityComponentable
(смотрите предыдущую часть) чтобы способность приобрела возможность добавлять в себя компоненты, я назвал этот класс ActiveCustomAbility.
Синтаксис:
Используется csharp
public class ActiveCustomAbility : ActiveAbility, IAbilityComponentable {

 public ActiveCustomAbility(string name) : base(name) {}

}

Ну а теперь нужно скопировать методы AddComponent, RemoveComponents<T>, RemoveComponent и свойство components из класса
PassiveCustomAbility в класс ActiveCustomAbility.
Должно получиться чтото типа этого.
Синтаксис:
Используется csharp
public class ActiveCustomAbility: ActiveAbility, IAbilityComponentable {
 
 private List<AbilityComponent> abilityComponents = new List<AbilityComponent>();
 private AbilityComponent[] abilityArrayComponents;
               
 public AbilityComponent AddComponent(AbilityComponent component) {
  if (this.abilityComponents.Any(c => c.GetType() == component.GetType())) {
   if (component.CheckForAttribute<DisableComponentMultiplyUsage>()) return null;
  }
  this.abilityComponents.Add(component);
  this.abilityArrayComponents = this.abilityComponents.ToArray();
  return component;
 }
               
 public void RemoveComponents<T>() where T : AbilityComponent {
  foreach(AbilityComponent component in this.abilityArrayComponents.Where(c => c is T))
   RemoveComponent(component);           
 }
               
 public void RemoveComponent(AbilityComponent component) {
  for(int index = 0; index < this.abilityComponents.Count; index++) {
   if (this.abilityComponents[index] == component) {
    this.abilityComponents.RemoveAt(index);
    this.abilityArrayComponents = this.abilityComponents.ToArray();
    return;
   }
  }
 }
               
 public AbilityComponent[] components {
  get { return this.abilityArrayComponents;}
 }

 public ActiveCustomAbility(string name) : base(name) {
  this.abilityArrayComponents = this.abilityComponents.ToArray();
 }

}

Ну вот в принципе и готова АКТИВНАЯ способность, она похожа по реализации на ПАССИВНУЮ только отличается тем что у этой
способности есть свойство castKey и cooldown.

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

Теперь я хотел бы немного вернуться к компонентам, в частности к данным которые могут они содержать. Поэтому для начала
немного теории.

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

Как видно на изображения показано собственно иерархия обычной способности, которая содержит в себе компоненты.
Эти компоненты могут быть простыми ШАБЛОНАМИ с конкретными типами данных вроде "float", "string" или "Texture2D", а могут
уже реализовывать какойто процесс в себе на примере компонента AbilityTimeComponent, ну а что на счет тех компонентов у
который неизвестна заранее реализация, как способность узнает что за компонент она получила - для этого мы создали шаблоны
во второй части статьи и пользователь сам может определить какую функцию будет выполнять любой компонент, но как это узнает
способность!? - вернемся немного ко второй части статьи, а именно к редактору способностей из игры WarCraft.
pic1.png

Как видно на картинке, как я уже упаминал во второй части, у каждого компонента в способности есть два свойства, это
название значения, и само значение. Но на самом деле там три свойства, просто третье свойство запечатано в самом
компоненте. Это третье свойство определяет реализацию компонента. На изображении перед свойством "название значения" мы
видим ТИП этого значения - только оно представлено как ПОСТОЯННОЕ значение - которое нельзя уже поменять у компонента, в
данном случае я говорю о свойстве "Art" или "Sound" у способности что на картинке. Это третье свойство заранее дает понятие
о том с какой реализацией компонента мы имеем дело - но главное не запутаться что это свойство НЕ определяет какое значение
мы можем задать компоненту, у пользователя все также есть возможность устанавливать любое значение в компонент на свое
усмотрение. Объясню на примере: допустим я хочу чтобы способность имела компонент который увеличивает здоровье на 100 ед. -
я знаю заранее что лучше всего мне использовать уже созданный ШАБЛОН который содержит в себе тип значения "float", далее
способность получает этот компонент и когда персонаж приобритет способность, способность начнет перебор всех компонентов у
себя и сможет по этому третьему свойству определить с какой целью этот компонент присутствует в способности - в моем случае
с целью увеличить здоровье, далее способность обращается к нужному параметру у персонажа и увеличивает ему здоровье.
И так что я имею: теперь когда я буду использовать допустим ШАБЛОН компонента с типом "float" я также могу и задать ему
направление куда способности использовать значение этого компонента. Надо теперь задать это третье свойство каждому
компоненту - поэтому я в базовом абстрактном классе AbilityComponent добавил еще одно свойство enum(перечисление) - целей,
я назвал это ComponentValueTarget и добавил в статический класс данных AbilitySettings.
Синтаксис:
Используется csharp
public static class AbilitySettings {
 
 public enum ComponentValueTarget {

 }

}

И так теперь я могу задать ЛЮБЫЕ цели которые я захочу которым будут следовать компоненты.
Для начала я задал такие цели:
Special - это универсальная цель - которая в будущем не будет иметь точной реализации - эта цель как бы скажет способности:
"можешь делать с моим значение что хочешь", что поможет пользователю самому выбрать УНИКАЛЬНУЮ реализацию - ту которую
невозможно предугадать заранее, к примеру если я захочу чтобы если игрок приобретает какуюто способность - персонаж умирал.
Health - ну тут понятно что целью этого компонента будет здоровье персонажа.
Armor - тут тоже, целью будет защита персонажа и тд. Дальше вы можете задать любые другие цели, будь то эффект или звук - здесь у вас полная свобода.
Теперь мне нужно было задать цели для компонентов КОНКРЕТНОЙ реализации вроде AbilityTimeComponent, для этого компонента я
задал цель Time, чтобы способность знала какая цель у компонента "время".
Вот как в моем случае выглядит это перечисление.
Синтаксис:
Используется csharp
public static class AbilitySettings {
 
 public enum ComponentValueTarget {
  Special,
  Health,
  Armor,
  Time
 }

}

Так теперь как я сказал выше нужно добавить это перечисление как свойство в абстрактный класс AbilityComponent.
Синтаксис:
Используется csharp
public abstract class AbilityComponent {
         
 public AbilitySettings.ComponentValueTarget targetValue { get; protected set;}
 public string name = "";
         
 public bool CheckForAttribute<T>() where T : Attribute {
  foreach(Attribute attribute in Attribute.GetCustomAttributes(this.GetType())) {
   if (attribute is T) return true;
  }
  return false;
 }
         
}


И теперь нужно добавить в конструктор каждого наследованного компонента параметр который будет изменять его свойство цели.
Для начала компонент НЕОПРЕДЕЛЕННОЙ реализации AbilityComponentValue<T>
Синтаксис:
Используется csharp
public class AbilityComponentValue<T> : AbilityComponent {
               
 public T value;
 public string valueName = "";
               
 public AbilityComponentValue(string name, AbilitySettings.ComponentValueTarget target) {
  this.name = name;
  this.targetValue = target;
  this.valueName = this.targetValue.ToString();
 }
       
}

И теперь этоже сделаем для каждого шаблона.
Синтаксис:
Используется csharp
public class AbilityFloatComponent : AbilityComponentValue<float> {
         
 public AbilityFloatComponent(string name, AbilitySettings.ComponentValueTarget target) : base(name, target) {}
         
}
       
public class AbilityIntComponent : AbilityComponentValue<int> {
               
 public AbilityIntComponent(string name, AbilitySettings.ComponentValueTarget target) : base(name, target) {}
               
}
       
public class AbilityStringComponent : AbilityComponentValue<string> {
               
 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) {}
               
}

Готово. Но у меня есть компонент с уже КОНКРЕТНОЙ реализацией - я об AbilityTimeComponent, я знаю уже заранее какой целью
будет обладать этот компонент, поэтому и указываю цель в его конструкторе заранее.
Синтаксис:
Используется csharp
public AbilityTimeComponent(string name) : base(name, AbilitySettings.ComponentValueTarget.Time) {}

Вот как теперь будет выглядеть компонент AbilityTimeComponent.
Синтаксис:
Используется csharp
[DisableComponentMultiplyUsage]
public class AbilityTimeComponent : AbilityFloatComponent {
       
 private int componentRepeatTimes = 0;
         
 public AbilitySettings.AbilityTimedType timeType;
               
 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) {}
         
}

Готово. Теперь у каждого компонента в способности будет цель(направления).
Так, ну есть у компонента свойство "цель" ну и что теперь, как способность должна реагировать на это, как она узнает что
ИМЕННО за КОМПОНЕНТ обладает этой целью, допустим обладаем способность компонентом "Health" и что это за компонент число
или строка или объект? А вот теперь наступает период нудной писанины руками :) , но сначала теория.

И так, каждый компонент может владеть данными - эти данные могут быть любого типа(смотри первую часть), а самый
элементарный тип данных в C# это System.Object и чтобы этот элементарный тип данных стал каким то конкретным объектом нужно
знать во что точно мы хотим его преобразовать. Но у нас все по проще - нам не надо знать в какой тип данных нам нужно
преобразовывать System.Object нам только надо знать какой тип компонента у нас, а дальше он сам скажет нам что за данные
внутри него спрятаны. Для этого я опять решил использовать аттрибут, но в этот раз он будет содержать одно свойство. Этим
аттрибутом я буду помечать перечисления - точнее какой тип данных будет ПРЕДСТАВЛЯТЬ каждый его елемент, будь то "Health"
или "Time". Для этого я создал аттрибут ComponentValueType с конструктором и одним свойством type.
Синтаксис:
Используется csharp
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public sealed class ComponentValueType : Attribute {
         
 public Type type;
         
 public ComponentValueType(Type type) {
  this.type = type;
 }

}

В его конструктор я буду указывать тип компонента который будет содержать каждый элемент перечисления ComponentValueTarget.
Допустим для цели "Health" - я укажу тип AbilityFloatComponent и теперь способность знает какой тип данных представляет эта
цель, для цели "Armor" тоже самое что и с "Health", но для цели "Special" - я укажу тип AbilityObjectComponent т к я не
знаю как будут использоваться этот компонент и его значение, это решит уже пользовательно в своей УНИКАЛЬНОЙ реализации.
Выглядеть это будет примерно так:
Синтаксис:
Используется csharp
public enum ComponentValueTarget {
 [ComponentValueType(typeof(AbilityObjectComponent))] Special,
 [ComponentValueType(typeof(AbilityFloatComponent))]  Health,
 [ComponentValueType(typeof(AbilityFloatComponent))]  Armor,
 [ComponentValueType(typeof(AbilityTimeComponent))]   Time
}

Что абсолютно одно и тоже если написать так:
Синтаксис:
Используется csharp
public enum ComponentValueTarget {
 [ComponentValueType(typeof(AbilityComponentValue<Object>))] Special,
 [ComponentValueType(typeof(AbilityComponentValue<float>))]  Health,
 [ComponentValueType(typeof(AbilityComponentValue<float>))]  Armor,
 [ComponentValueType(typeof(AbilityTimeComponent))]          Time
}

Вы дальше можете указать любую другую цель и любой компонент который должна эта цель представлять.
Допустим если бы я хотел добавить цель "Invisibility" для способности, то я бы сделал это так: создал бы цель Invis, и
установил бы для этой цели тип компонента который будет содержать тип данных "bool" - видимый\невидимый, как в примере.
Синтаксис:
Используется csharp
[ComponentValueType(typeof(AbilityComponentValue<bool>))]   Invis


Так готово. Теперь любая цель будет давать понять способности с каким видом компонента мы работаем.
Для начала мне нужно вообще узнать какой тип представляет елемент перечисления, для этого я создал статический метод
GetValueType в аттрибуте ComponentValueType чтобы из элемента вытянуть тип не забудьте подключить System.Reflection, вот
как он выглядит.
Синтаксис:
Используется csharp
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public sealed class ComponentValueType : Attribute {
         
 public Type type;
         
 public ComponentValueType(Type type) {
  this.type = type;
 }

 public static Type GetValueType(AbilitySettings.ComponentValueTarget value) {
  Type t = typeof(System.Object);
  FieldInfo info = value.GetType().GetField(value.ToString());
  ComponentValueType attribute = (ComponentValueType)Attribute.GetCustomAttribute(info, typeof(ComponentValueType));
  if (attribute != null) t = attribute.type;
  return t;
 }

}

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

Просто так создать объект не имея его Generic Type имени "нельзя", тоесть когда я разложу "цель" и получу тип компонента я
не могу создать этот компонент просто вызвав new типКомпонента(), но есть другой выход, есть в System статический класс
Activator который позволяет создавать экземпляры объектов по его типу(Type). Поэтому я просил вас писать в каждом ШАБЛОНЕ
публичный конструктор с ДВУМЯ параметрами - и это важно, т к мы будем создавать эти компоненты без вызова new Component().
Вот как это получилось у меня: я создал статический метод CreateComponentForTarget в статическом классе AbilitySettings в
котором я раскладываю "цель" и создаю по полученному типа компонент.
Синтаксис:
Используется csharp
public static AbilityComponent CreateComponentForTarget(ComponentValueTarget target, string name) {
 AbilityComponent component = new AbilityComponentValue<System.Object>(name, target);
 Type type = ComponentValueType.GetValueType(target);
 try {
  component = (AbilityComponent)Activator.CreateInstance(type, name, target);
 } catch {
  Debug.LogError("Error: faild to initialize type: " + type);
 }
 return component;
}

И так: первая строка создает дефолтный компонент AbilityValueComponent с универсальным типом System.Object, далее я
раскладываю с помощью метода GetValueType "цель" и беру ее тип компонента который эта "цель" представляет, дальше я просто
с помощью Activator.CreateInstance создаю компонент полученного ранее типа, заметьте что я написал этот блок в try catch т к
в случае если у компонента будет не ОПРЕДЕЛЕННО два параметра в конструкторе то при создании компонента вылетит ошибка -
поэтому еще раз, очень важно чтобы каждый ШАБЛОН(компонент) имел хотябы один публичный конструктор с двумя параметрами!

Ну вот и все, теперь можно протестировать так: напишите данный код гденибудь
Синтаксис:
Используется csharp
AbilityComponent component = AbilitySettings.CreateComponentForTarget(AbilitySettings.ComponentValueTarget.Time, "Name");
Debug.Log(component.GetType());

И посмотрите как тип выведется в дебаг, если тот который указан в "цели", в моем случае "Time" - должен вывести тип
компонента AbilityTimeComponent, то значит все работает верно.

Заключение: теперь с помощью "целей" я могу создавать любые ШАБЛОНЫ любых компонентов, просто пометив ту или иную "цель"
нужным мне типом компонента. Также и с "целями" я могу создать цель "Звук" которая будет представлять компонент с типом значения "Sound" и тд.

На этом все в четвертой части статьи. В следующий раз я расскажу как же собирать нужные данные из компонентов и наконец
перейдем к редактору. Попробуем для начала сделать что то похожее на это
Скрытый текст:
Editor1.png


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

автор: этот хрен, он же llka, он же lawsonilka
У вас нет доступа для просмотра вложений в этом сообщении.
Последний раз редактировалось lawson 10 дек 2014, 23:07, всего редактировалось 1 раз.
lawson
UNIверсал
 
Сообщения: 481
Зарегистрирован: 14 сен 2012, 21:20

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

Сообщение bwolf88 10 дек 2014, 19:09

А почему бы Вам не делать видеоуроки с пошаговой инструкцией и наглядными запусками того, что получилось. Это займет меньше времени чем печатать тексты. (Это по поводу улчшений :) )
Сюда периодически чего нибудь выкладываю https://github.com/LuchunPen
Аватара пользователя
bwolf88
Адепт
 
Сообщения: 2184
Зарегистрирован: 30 апр 2014, 06:40
Skype: bwolf331

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

Сообщение lawson 10 дек 2014, 20:25

Спасибо за предложение, но у меня в этом совершенно нет опыта - делать видеоуроки, а делать как попало я не хочу, хотя согласен так было бы намного удобней, поэтому решил постаринке писать статью - если что изменил(переписал) что не так, а к видео нужно писать сценарий действий, чтобы не было типа "нажмите сюда, а нет лучше сюда, нет все же сюда".
Скрытый текст:
На самом деле меня вдохновили статьи на хабре - где размер статьи не является препятсвием к ее прочтению, хотя мне еще есть чему поучиться у них.
lawson
UNIверсал
 
Сообщения: 481
Зарегистрирован: 14 сен 2012, 21:20

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

Сообщение bwolf88 10 дек 2014, 20:57

lawson писал(а):На самом деле меня вдохновили статьи на хабре - где размер статьи не является препятсвием к ее прочтению, хотя мне еще есть чему поучиться у них.


Это понятно, но к сожалению этот форум не очень приспособлен для объемных статей. Не тот формат подачи текста. :)
Сюда периодически чего нибудь выкладываю https://github.com/LuchunPen
Аватара пользователя
bwolf88
Адепт
 
Сообщения: 2184
Зарегистрирован: 30 апр 2014, 06:40
Skype: bwolf331

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

Сообщение lawson 10 дек 2014, 21:36

Не тот формат подачи текста.

Это вы про что? Про эту статью? - если да, то подскажите что исправить, как подавать информацию?
lawson
UNIверсал
 
Сообщения: 481
Зарегистрирован: 14 сен 2012, 21:20

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

Сообщение bwolf88 10 дек 2014, 23:11

Нет не про статью. Просто неудобно читать большие тексты в таком формате - это же не дневник, тут нет некоторых фишек форматирования. "Хабр" к примеру предназначен для больших текстов с красивым оформлением. А форум для небольших простеньких сообщений.
Сюда периодически чего нибудь выкладываю https://github.com/LuchunPen
Аватара пользователя
bwolf88
Адепт
 
Сообщения: 2184
Зарегистрирован: 30 апр 2014, 06:40
Skype: bwolf331

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

Сообщение lawson 10 дек 2014, 23:12

bwolf88 писал(а):Нет не про статью. Просто неудобно читать большие тексты в таком формате - это же не дневник, тут нет некоторых фишек форматирования. "Хабр" к примеру предназначен для больших текстов с красивым оформлением. А форум для небольших простеньких сообщений.

Спасибо, учту это в будущем.
lawson
UNIверсал
 
Сообщения: 481
Зарегистрирован: 14 сен 2012, 21:20

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

Сообщение Alex2D 10 фев 2015, 21:01

Не надо блин никаких видео-уроков! -Вся Труба забита этими "обучающими видео" где надо в течении часа выслушивать, как "Мэтр" тычет, то в одну кнопочку, то в другую, бекая и мекая в микрофон, непрерывно поправляясь и удивляясь сам произведенному Эффекту!

Время того, кто хочет научится, не менее дорого, чем время такого "Гуру", освоившего интерфейс Юнити и радостно поскакавшего сообщить об этом всем и научить Весь Мир тому, что теперь знает Он! Понятно, что печатать "многабукав" такому Асу будет влом, а вот трындеть в Эфир можно не напрягаясь!
Только вот выслушивать бред в течении десятков минут и в конце-концов понять, что сказать-то "Учителю" по сути было нечего...
-Сомнительное удовольствие.

В данном же случае - радоваться надо, что Автор не пошел по легкому и модному нынче пути, а изложил всё в виде статьи -гораздо более удобный вариант для всех могущих прочитать абзац текста быстрее, чем очередной "Создатель видеоуроков" разродится Мыслью о том: "что же все-таки делает эта кнопка"!

Все сразу перед глазами, ничего не надо искать, проматывать бедный ролик туда-сюда...

Очень краткий и емкий способ изложить действительно важные и ценные мысли. Спасибо! :)
Alex2D
UNец
 
Сообщения: 1
Зарегистрирован: 10 фев 2015, 20:15


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

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

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