Способности. Активная.
И так в прошлый раз я рассмотрел создание ПАССИВНОЙ способности, теперь перехожу ко второму типа способностей - АКТИВНОЙ.
Для АКТИВНОЙ способности тоже нужно будет создать базовый класс ПУСТЫШКУ который будет наследоваться от главной базовой
способности AbilityObject, я назвал этот класс ActiveAbility с одним публичным конструктором как и в случае PassiveAbility,
только теперь конструктор будет задавать тип способности как Active.
Синтаксис:
Используется csharp
public class ActiveAbility : AbilityObject {
public ActiveAbility(string name) : base(AbilitySettings.AbilityType.Active, name) {}
}
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) {}
}
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) {}
}
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();
}
}
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.
На этом я не заканчиваю работу со способностями т к мы кучу раз еще вернемся к ним чтобы добавить те или иные свойства и
методы, но с основами этих двух типов способностей мы закончили.
Теперь я хотел бы немного вернуться к компонентам, в частности к данным которые могут они содержать. Поэтому для начала
немного теории.
И так теперь когда мы создали способности которые могут добавлять компоненты, которые в свою очередь содержат разные типы
данных, нужно определиться какую роль будут выполнять эти самые компоненты в способности.
Как видно на изображения показано собственно иерархия обычной способности, которая содержит в себе компоненты.
Эти компоненты могут быть простыми ШАБЛОНАМИ с конкретными типами данных вроде "float", "string" или "Texture2D", а могут
уже реализовывать какойто процесс в себе на примере компонента AbilityTimeComponent, ну а что на счет тех компонентов у
который неизвестна заранее реализация, как способность узнает что за компонент она получила - для этого мы создали шаблоны
во второй части статьи и пользователь сам может определить какую функцию будет выполнять любой компонент, но как это узнает
способность!? - вернемся немного ко второй части статьи, а именно к редактору способностей из игры WarCraft.
Как видно на картинке, как я уже упаминал во второй части, у каждого компонента в способности есть два свойства, это
название значения, и само значение. Но на самом деле там три свойства, просто третье свойство запечатано в самом
компоненте. Это третье свойство определяет реализацию компонента. На изображении перед свойством "название значения" мы
видим ТИП этого значения - только оно представлено как ПОСТОЯННОЕ значение - которое нельзя уже поменять у компонента, в
данном случае я говорю о свойстве "Art" или "Sound" у способности что на картинке. Это третье свойство заранее дает понятие
о том с какой реализацией компонента мы имеем дело - но главное не запутаться что это свойство НЕ определяет какое значение
мы можем задать компоненту, у пользователя все также есть возможность устанавливать любое значение в компонент на свое
усмотрение. Объясню на примере: допустим я хочу чтобы способность имела компонент который увеличивает здоровье на 100 ед. -
я знаю заранее что лучше всего мне использовать уже созданный ШАБЛОН который содержит в себе тип значения "float", далее
способность получает этот компонент и когда персонаж приобритет способность, способность начнет перебор всех компонентов у
себя и сможет по этому третьему свойству определить с какой целью этот компонент присутствует в способности - в моем случае
с целью увеличить здоровье, далее способность обращается к нужному параметру у персонажа и увеличивает ему здоровье.
И так что я имею: теперь когда я буду использовать допустим ШАБЛОН компонента с типом "float" я также могу и задать ему
направление куда способности использовать значение этого компонента. Надо теперь задать это третье свойство каждому
компоненту - поэтому я в базовом абстрактном классе AbilityComponent добавил еще одно свойство enum(перечисление) - целей,
я назвал это ComponentValueTarget и добавил в статический класс данных AbilitySettings.
Синтаксис:
Используется csharp
public static class AbilitySettings {
public enum ComponentValueTarget {
}
}
public enum ComponentValueTarget {
}
}
И так теперь я могу задать ЛЮБЫЕ цели которые я захочу которым будут следовать компоненты.
Для начала я задал такие цели:
Special - это универсальная цель - которая в будущем не будет иметь точной реализации - эта цель как бы скажет способности:
"можешь делать с моим значение что хочешь", что поможет пользователю самому выбрать УНИКАЛЬНУЮ реализацию - ту которую
невозможно предугадать заранее, к примеру если я захочу чтобы если игрок приобретает какуюто способность - персонаж умирал.
Health - ну тут понятно что целью этого компонента будет здоровье персонажа.
Armor - тут тоже, целью будет защита персонажа и тд. Дальше вы можете задать любые другие цели, будь то эффект или звук - здесь у вас полная свобода.
Теперь мне нужно было задать цели для компонентов КОНКРЕТНОЙ реализации вроде AbilityTimeComponent, для этого компонента я
задал цель Time, чтобы способность знала какая цель у компонента "время".
Вот как в моем случае выглядит это перечисление.
Синтаксис:
Используется csharp
public static class AbilitySettings {
public enum ComponentValueTarget {
Special,
Health,
Armor,
Time
}
}
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;
}
}
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();
}
}
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) {}
}
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) {}
}
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;
}
}
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
}
[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
}
[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;
}
}
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;
}
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());
Debug.Log(component.GetType());
И посмотрите как тип выведется в дебаг, если тот который указан в "цели", в моем случае "Time" - должен вывести тип
компонента AbilityTimeComponent, то значит все работает верно.
Заключение: теперь с помощью "целей" я могу создавать любые ШАБЛОНЫ любых компонентов, просто пометив ту или иную "цель"
нужным мне типом компонента. Также и с "целями" я могу создать цель "Звук" которая будет представлять компонент с типом значения "Sound" и тд.
На этом все в четвертой части статьи. В следующий раз я расскажу как же собирать нужные данные из компонентов и наконец
перейдем к редактору. Попробуем для начала сделать что то похожее на это
Скрытый текст:
Следующая часть
автор: этот хрен, он же llka, он же lawsonilka