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

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

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

Сообщение lawson 05 дек 2014, 20:44

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

я удалил прошлый вариант второй части т к она была слишком неуклюжо написана - но я исправлюсь :)

Компоненты

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

Далее немного теории из редакторов в других играх.
pic1.png

На картинке изображена часть редактора способностей игры WarCraft, и видно что способность состоит из уймы свойств, но если
присмотреться то можно заметить что любое свойство состоит из двух частей: 1) Имя свойства, 2) Тип самого свойства. Это
дает понять что все эти строки которые изображены на картинке являются просто ОБЪЕКТОМ в нашем случае КОМПОНЕНТОМ, значит
можно просто создать компонент который бы содержал в себе эти две переменные, но если с переменной "Имя свойства" все
понятно что это строка, то как быть с "тип самого свойства", ведь как я упоминал в прошлой части, это может быть и строка и
число и даже объект. Поэтому я продолжил развивать иерархию компонентов.

Теперь мне нужен УНИВЕРСАЛЬНЫЙ компонент в который я мог бы записать любой тип значения - следовательно этот компонент
будет использовать ПРЕОБРАЗОВАНИЕ.
Приступим к программной части, я назвал этот новый класс AbilityComponentValue, он будет наследоваться от AbilityComponent
Синтаксис:
Используется csharp
public class AbilityComponentValue<T> : AbilityComponent {
 
}

Этот класс поддерживает преобразование любого типа T поэтому оно указано в <> таких скобках.
Теперь наполним этот класс свойствами:
value - это переменная данных которое будет содержать значение
valueName - название этой переменной(маленькое описание).
И добавим в этот класс публичный конструктор.

Вот как будет он в итоге выглядеть.
Синтаксис:
Используется csharp
public class AbilityComponentValue<T> : AbilityComponent {
               
 public T value;
 public string valueName = "";
               
 public AbilityComponentValue(string name) {
  this.name = name;
 }
       
}

Готово, теперь это УНИВЕРСАЛЬНЫЙ компонент AbilityComponentValue будет играть роль объекта с неопределенным типом данных.

Возвращаюсь опять к теории.

И так мы создали компонент который может содержать любое значение которое мы зададим, но возвращаясь к редактору что на
картинке сверху мы увидим что у каждого компонента есть строка "Тип" перед названием самого компонента - это служит для
того чтобы определить с каким ТИПОМ данных мы работаем в компоненте, тоесть какой тип значения содержит этот компонент,
строку или число и тд. Это говорит о том что все эти типы представляют собой ШАБЛОНЫ уже с определенным типом(число,
строка...объект) и способность как бы приобретает компонент с заранее известным типом данных которые будет содержать этот
компонент. Я не могу запихнуть в способность компонент с НЕОПРЕДЕЛЕННЫМ типом данных.
Возвращаясь к редакторам из игр можно заметить что каждая определенная способность уже заранее имеет определенные компоненты
в себе которые нельзя удалить или изменить их тип, вот чего я хотел избежать в этой системе поэтому я и назвал ее как
УНИВЕРСАЛЬНАЯ, т к пользователь должен сам решать каким компонентом будет обладать способность, поэтому пользователю
придется создать шаблон компонента с ОПРЕДЕЛЕННЫМ типом который он потом запихнет в способность. Эти шаблоны бы
основывались на УНИВЕРСАЛЬНОМ компоненте AbilityComponentValue, поэтому теперь я перехожу к следующему пункту.

ШАБЛОНЫ

И так мы создали УНИВЕРСАЛЬНЫЙ компонент AbilityComponentValue и на его базе будем создавать нужные нам шаблоны компонентов
с определенным типом данных. Я создал четыре основных шаблона те что буду чаще всего использовать, эти шаблоны будут
содержать такие базовые типы как "float", "int", "string" и "UnityEngine.Object".
Вот как они выглядят.
Синтаксис:
Используется csharp
public class AbilityFloatComponent : AbilityComponentValue<float> {
         
 public AbilityFloatComponent(string name) : base(name) {}
         
}
       
public class AbilityIntComponent : AbilityComponentValue<int> {
               
 public AbilityIntComponent(string name) : base(name) {}
               
}
       
public class AbilityStringComponent : AbilityComponentValue<string> {
               
 public AbilityStringComponent(string name) : base(name) {}
               
}
       
public class AbilityObjectComponent : AbilityComponentValue<UnityEngine.Object> {
               
 public AbilityObjectComponent(string name) : base(name) {}
               
}

Эти шаблоны не содержать ни каких дополнительных свойств т к являются основными, но вы можете добавить в них любые свои
свойства. Также вы можете создать любой другой шаблон с определенным типом, допустим с типом "bool" или любым другим.

Опять к теории.

В редакторе способностей игры WarCraft можно заметить что почти у каждой есть параметр время, это параметр может выполнять
разные действия, допустим время которое будет действовать способность, или сколько раз сработает способность за определенное
колво времени, получается время там ПОСТОЯННЫЙ параметр, который мог вообще НЕ использоваться в способности но при этом он
обязательно присутствовал в наличии. Поначалу я тоже хотел определить время как постоянное свойство способности, и
получается что любая способность бы обладала этим параметром не зависимо от ее типа. Но здесь тогда появляется
недоразумение, допустим зачем мне параметр время в ПАССИВНОЙ способности, или допустим есть у меня активная
способность которая просто по нажатию открывает дверь - здесь мне не нужен параметр время абсолютно, и тогда я решил
сделать "время" тоже обычным компонентом и чтобы пользователь сам решал будет ли способность обладать этим параметром или
нет. И так раз "время" это компонент значит оно должно быть частью компонентной модели и наследоваться от типа
AbilityComponent, тогда мне нужно определить какими свойствами будет обладать время. И так для начала время конечно будет
обладать числовым свойством самого времени, и пусть у компонента "время" будет две инструкции как его применять.

Переходим к программной части.
Создадим компонент "вреся" я назвал его AbilityTimeComponent и оно будет наследоваться от шаблона AbilityFloatComponent что
даст точно понять с каким типом данных мы работаем.
Синтаксис:
Используется csharp
public class AbilityTimeComponent : AbilityFloatComponent {

}

Теперь наполним его свойством "time" и одним конструктором для начала.
Синтаксис:
Используется csharp
public class AbilityTimeComponent : AbilityFloatComponent {

 public float time {
  get { return base.value;}
  set { base.value = (value < 0f) ? 0f : value;}
 }

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

}

Теперь как я сказал ранее я хотел бы чтобы время обладало двумя инструкциями которые будут простыми enum(перечислениями)
Не много отвлекусь... Т к мне постоянно нужны будут всякие НЕЗАВИСИМЫЕ данные я создал базу данный в виде статического
класса под названием AbilitySettings.
И так теперь объявим enum с инструкциями в классе AbilitySettings.
Синтаксис:
Используется csharp
public static class AbilitySettings {

 public enum AbilityTimedType {
  Once,
  Repeat
 }

}

И добавлю это перечисление как свойство в компонент "время", также добавлю свойство типа "int" чтобы указать сколько раз я
буду использовать способность за определенное время.
Вот как будет выглядеть полный компонент "время".
Синтаксис:
Используется csharp
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 Mathf.Abs(this.value);}
  set { this.value = (value < 0f) ? 0f : value;}
 }

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

}

Готово, теперь у нас есть еще один компонент но этот компонент уже обладает определенной реализацией, тоесть в нем есть
определенные свойства помимо базовых. Но дальше я столкнулся с проблемой, по идеи любая способность может обладать кучей
компонентов разного типа, но компонент "время" - тоже ведь компонент и получается я могу добавить кучу таких компонентов
"время" в способность но тогда способность будет не исправной ведь в ней будет куча компонентов "время" и они могут
противоречить друг другу, допустим один компонент "время" будет указывать что способность будет работать 2 секунды а другой
компонент "время" будет указывать что способность будет работать 3 секунды и тогда получаем логическую ошибку. Поэтому мне
нужно было както определять какими компонентами будет обладать способность в только ЕДИНСТВЕННОМ экземпляре.
Я придумал только два способа решения этой проблемы: и вы можете выбрать любой - реализация от этого сильно не пострадает.
И так:
1) Вариант с определением типа компонента: способность при добавлении компонента будет определять что за тип добавляемого
компонента и есть этот тип компонента "время" то проверить есть ли у способности уже такой компонент и если есть то не
добавлять его еще раз.
2) Использовать аттрибут, (про эту фигню можно почитать на мсдн там же есть и примеры, но я не упоминал о них в начале
статьи "как обязательно к прочтению" т к я объясню по ходу зачем я их использую, да и аттрибуты я буду использовать без
сложных схем так что думаю вы сможите разобраться). И так для того чтобы пометить компонент "время" как особенный компонент
который может храниться в способности только в одном экземпляре я создам аттрибут DisableComponentMultiplyUsage который
будет работать по аналогии с аттрибутом DisallowMultiplyUsage в unity3D которым помечают компоненты которые присутствуют
только в ЕДИНСТВЕННОЙ экземпляре на объектах. На самом деле те кто часто работают с расширением редактора в unity3D часто
используют аттрибуты когда указывают какой компонент они расширяют [CustomEditor()]. Когда я буду добавлять компонент в
способность, способность проверит имеет ли компонент аттрибут DisableComponentMultiplyUsage и если есть то этот компонент
может быть только в ЕДИНСТВЕННО наличии у способности.

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

И так перейдем с аттрибуту DisableComponentMultiplyUsage, это будет простой аттрибут без каких либо свойств и методов, он
просто будет служить мне как флаг что способность имеет дело с "особым" компонентом.
Синтаксис:
Используется csharp
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class DisableComponentMultiplyUsage : Attribute {}

Готово, теперь пометим компонент время как "особый" компонент.
Синтаксис:
Используется 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 Mathf.Abs(this.value);}
  set { this.value = (value < 0f) ? 0f : value;}
 }

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

}

Готово, теперь компонент AbilityTimeComponent имеет аттрибут DisableComponentMultiplyUsage.

Ну вот и вся вторая часть касаемо компонетов.
Заключение: теперь когда я имею УНИВЕРСАЛЬНЫЙ компонент НЕОПРЕДЕЛЕННОГО типа я могу создавать какие угодно компоненты с любыми типа
значений внутри, хоть число, строка или текстура, т к сказать шаблоны.

В следующей части мы займемся наконец самими способностями.

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

Автор: этот хрен, он же llka, он же lawsonilka
У вас нет доступа для просмотра вложений в этом сообщении.
lawson
UNIверсал
 
Сообщения: 481
Зарегистрирован: 14 сен 2012, 21:20

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

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

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