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

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

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

Сообщение lawson 22 дек 2014, 23:15

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

Менеджер способностей.

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

Как и в таких играх как StarCraft или WarCraft способностями могут обладать только объекты определенного вида: это герои,
разные npс включая здания. Тоесть у всех этих объектов есть некий менеджер которые дает понять игровому миру что этот объект
может использовать(содержать) способности. Такой менеджер придется сделать и нам. Ясно что этот менеджер в нашем случае будет
компонентом у объекта(GameObject). Теперь нужно поразмыслить что будет из себя представлять этот менеджер и какими свойствами
он будет обладать:
- 1) Очевидно что этот менеджер будет в виде списка(массива) или как еще угодно хранить в себе способности.
- 2) Ясно что нужно будет через какието методы добавлять и удалять эти способности.
Для начала достаточно. Давайте разберем каждое свойство.
- 1) Менеджер должен будет содержать два отдельных списка(массива) или один общий список(массив) где будут храниться все
способности, это не должно вызвать сложность при реализации.
- 2) Здесь нам нужен будет метод через который мы собираемся добавлять способность и метод через который удалять ненужную
способность. Хоть эти методы и банальные, но лично мне пришлось долго и мучительно продумывать логику как же будут работать
способности в менеджере. Я попробую объяснить вам почему чтобы у вас этого не произошло.

И так представим что мы создали способность, потом добавили в нее компоненты, заполнили эти компоненты любыми значениями, и
вот в принципе у нас готова способность которая содержит компоненты. Теперь попробуем эту способность мысленно добавить в
менеджер, менеджер принимает способность и в зависимости от ее типа (активная\пассивная) кидает ее в тот или иной список
(массив). С точки зрения пассивной способности, эта способность не имеет какого вида активации - тоесть пользователь не может
ее активировать он может ее только добавить или удалить в\из менеджера, тогда получается что пассивная способность должна
начать работать сразу же после добавления в менеджер. Ясно, мы засовываем пассивную способность в менеджер и сразу же после
этого АКТИВИРУЕМ ее.

АКТИВАЦИЯ - активация способности, будь то пассивная или активная, представляет собой последовательность действий в которых
мы будем разбирать эту способность на компоненты, дальше получив компонент мы согласно "цели" значения компонента должны
будем куда то это значение присвоить(будь то здоровье или броня), но здесь и возникает проблема! КУДА менеджер будет
присваивать значение полученное из компонента? - да у компонента есть цель, мы можем сказать что такое то значение
использовать так то, но менеджер не отвечает за ТОЧНУЮ реализацию компонента, менеджер лишь обладает двумя свойства(что
выше).
Давайте представим еще что: допустим мы хотим создать способность которая будет увеличивать скорость бега, но эту
способность мы дадим объекту "здание", логично что здание перемещаться не может, но менеджеру то все равно он лишь отвечает
за хранение способностей, из этого следует что для того чтобы обратиться к свойству указанное в "цели" компонента это свойство
должно СУЩЕСТВОВАТЬ у объекта! - получается одного менеджера мало чтобы объект мог работать со способностями. Из этого
следует что нужен еще некий компонент у объекта который бы точно мог отвечать за свойства этого объекта. Тоесть, опять же
возвращаясь к методу добавления способности, когда мы разберем способность на компоненты и возьмем их значение мы прост
отошлем эти значения компоненту который уже будет отвечать за то что с этими значениями делать и куда их присваивать.
Этот компонент я назвал AbilityBihaviour. Этот компонент будет давать понять менеджеру что несмотря на то что есть
возможность хранить, получать и удалять способности есть также возможность присваивать(изменять) свойства объекта, этому
будет свидетельствовать компонент AbilityBihaviour на объект(GameObject).

Как это будет работать:

Допустим объект имеет менеджер для контроля способностей - это дает нам понять что в этот объект можно вкладывать
способности, что мы и делаем, дальше менеджер получает способности и смотрит если на этом объекте(GameObject) есть компонент
АbilityBihaviour значит менеджер принимает решение не просто разложить способность но еще и есть куда присвоить значения
разложенных компонентов, и тогда он просто один за другим отсылает полученные значения в AbilityBihaviour, а уже в
AbilityBihaviour сам пользователь будет решать как и куда присваивать полученные значения и "цели" помогут ему в этом.

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

Ну а теперь вернемся к программированию.
Начнем с компонента AbilityBihaviour, создайте где нибудь файл с его именем AbilityBihaviour в скриптах т к он будет
наследоваться от MonoBehaviour хоть и будет абстрактным.
Синтаксис:
Используется csharp
public abstract class AbilityBihaviour : MonoBehaviour {

}

Готово. Почему именно я обозначил этот класс как абстрактный я расскажу чуть ниже.
Теперь давайте создадим наш менеджер который будет хранить и работать со способностями, я назвал его SpellManager и он тоже
будет наследоваться от MonoBehaviour поэтому называем скрипт таким же именем как и название класса.
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {

}

Как видите я пометил этот класс аттрибутом DisallowMultipleComponent, как я рассказывал в предыдущих частях, этот аттрибут НЕ
позволяет иметь объекту больше одного данного компонента.
Заметьте что это класс я не помечал как абстрактный или запечатанный, т к он уже будет иметь ДОСТАТОЧНУЮ РЕАЛИЗАЦИЮ чтобы
полноценно работать со способностями, поэтому пользователь может дальше унаследовать этот класс и добавить или изменить какие
либо свойства класс SpellManager
Теперь добавим несколько методов в этот класс.
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {

 protected virtual void Awake() {

 }

 protected virtual void Start() {

 }

}

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

И так теперь у нас есть два компонента, теперь я создам статический класс SpellManagerData который будет хранить нужные мне
базы статических данных для работы с менеджером. Странно я раньше пытался использовать как можно меньше разных баз и
использовал только одну общую, а теперь для каждой области работ создавал отдельную - не знаю плохо это или нет, но уже входит в
привычку.
Синтаксис:
Используется csharp
public static class SpellManagerData {

}

Теперь мне нужно создать события которые будут давать понять когда мы добавили или удалили способность, это будет у меня
простым перечислением.
Синтаксис:
Используется csharp
public static class SpellManagerData {

 public enum AbilitySendEvent {
   OnAbilityAdded,
   OnAbilityUsed,
   OnAbilityRemoved
  }

}

Думаю здесь все понятно, событие OnAbilityAdded будет вызываться когда мы добавим способность в менеджер и т д.
Теперь вернемся к компоненту SpellManager и добавим в него одно свойство
objectSpellBihaviour - это свойство будет содержать ссылку на компонент AbilityBihaviour
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {

 public AbilityBihaviour objectSpellBihaviour { get; private set;}

 protected virtual void Awake() {
  this.objectSpellBihaviour = this.GetComponent<AbilityBihaviour>();
 }

 protected virtual void Start() {

 }

}

Как видите в методе Awake мы будет брать у объекта, на котором находится компонент SpellManager, компонент AbilityBihaviour.
Теперь, как я говорил ранее, менеджеру нужно будет отсылать события в компонент AbilityBihaviour о добавлении или удалении
способности. Опять же напоминаю мы это делаем т к менеджеру все равно может ли объект изменять свои свойства согласно
полученным способностям или нет, ему главное принимать их, удалять и тд.

Я создал метод SendAbilityEvent который будет отсылать события по всему объекту о том что менеджер получил\удалил
способность.
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {

 public AbilityBihaviour objectSpellBihaviour { get; private set;}

 protected virtual void Awake() {
  this.objectSpellBihaviour = this.GetComponent<AbilityBihaviour>();
 }

 protected virtual void Start() {

 }

 protected void SendAbilityEvent(SpellManagerData.AbilitySendEvent e, AbilityObject ability) {
  this.SendMessage(e.ToString(), ability, SendMessageOptions.DontRequireReceiver);
 }

}

Я использовал обычный SendMessage чтобы слать сообщения, но это еще не конечный вид метода, т к сообщение SendMessage
отошлется только в случае когда объект не имеет компонент AbilityBihaviour. Но если все таки объект имеет этот компонент
тогда мы будет слать сообщения напрямую в этот компонент. Поэтому для начала нам нужно будет создать методы - получатели, и
согласно перечислению событий - их будет всего три для начала.
Синтаксис:
Используется csharp
public abstract class AbilityBihaviour : MonoBehaviour {

 public virtual void OnAbilityAdded(AbilityObject ability) {
  Debug.Log("Character: " + this.name + " has got new ability: " + ability.name);
 }
               
 public virtual void OnAbilityUsed(AbilityObject ability) {
  Debug.Log("Character: " + this.name + " used ability: " + ability.name);
 }
               
 public virtual void OnAbilityRemoved(AbilityObject ability) {
  Debug.Log("Character: " + this.name + " has removed ability: " + ability.name);
 }

}

Немного комментариев: как вы видите я сделал эти методы не абстарктными а виртуальными, объясню почему: когда я первый раз
делал эту систему я сделал эти методы абстрактными и поэтому в наследующихся классах мне всегда приходилось их перегружать,
но потом когда я все чаще начинал пользоваться системой способностей я заметил что эти методы я очень редко использую -
практически никогда, и я тогда решил зачем мне их каждый раз перегружать пусть это сам пользователь решит нужно ли ему получить
их новую реализацию или нет - т к хоть эти события и важны, они не играют конкретной роли в конечном результате.
Дальше в этот же класс мы добавим ссылку на компонент SpellManager
Синтаксис:
Используется csharp
public abstract class AbilityBihaviour : MonoBehaviour {

 private SpellManager objectManager = null;
 
 public SpellManager spellManager {
  get {
   if (this.objectManager == null) {
    this.objectManager = this.gameObject.GetComponent<SpellManager>();
    if (this.objectManager == null) this.objectManager = this.gameObject.AddComponent<SpellManager>();
   }
   return this.objectManager;
  }
 }

 public virtual void OnAbilityAdded(AbilityObject ability) {
  Debug.Log("Character: " + this.name + " has got new ability: " + ability.name);
 }
               
 public virtual void OnAbilityUsed(AbilityObject ability) {
  Debug.Log("Character: " + this.name + " used ability: " + ability.name);
 }
               
 public virtual void OnAbilityRemoved(AbilityObject ability) {
  Debug.Log("Character: " + this.name + " has removed ability: " + ability.name);
 }

}

Готово. Теперь мы имеем обратную связь между этими двумя компонентами.
Я также добавил ее одну вещь, которая мне немного в будующем облегчила доступ к компоненту SpellManager - я расширил класс
GameObject одним методом. Работает это так:
Синтаксис:
Используется csharp
public static class SpellManagerData {

 public static SpellManager SpellManager(this GameObject gameObject) {
  return gameObject.GetComponent<SpellManager>();
 }

 public enum AbilitySendEvent {
   OnAbilityAdded,
   OnAbilityUsed,
   OnAbilityRemoved
  }

}

Теперь когда я захочу допустим взять компонент SpellManager у GameObject мне не нужно будет писать
Синтаксис:
Используется csharp
SpellManager manager = GameObject.GetComponent<SpellManager>()

я просто напишу
Синтаксис:
Используется csharp
SpellManager manager = GameObject.SpellManager();

и получу свой компонент.

Так теперь вернемся к компоненту SpellManager и методу где мы отсылаем сообщения напрямую в компонент AbilityBihaviour,
добавим в него немного действий.
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {

 public AbilityBihaviour objectSpellBihaviour { get; private set;}

 protected virtual void Awake() {
  this.objectSpellBihaviour = this.GetComponent<AbilityBihaviour>();
 }

 protected virtual void Start() {

 }

 protected void SendAbilityEvent(SpellManagerData.AbilitySendEvent e, AbilityObject ability) {
  if (this.objectSpellBihaviour == null) {
   this.SendMessage(e.ToString(), ability, SendMessageOptions.DontRequireReceiver);
  } else {
   switch(e) {
    case SpellManagerData.AbilitySendEvent.OnAbilityAdded: this.objectSpellBihaviour.OnAbilityAdded(ability);
    break;
    case SpellManagerData.AbilitySendEvent.OnAbilityRemoved: this.objectSpellBihaviour.OnAbilityRemoved(ability);
    break;
    case SpellManagerData.AbilitySendEvent.OnAbilityUsed: this.objectSpellBihaviour.OnAbilityUsed(ability);
    break;
   }
  }
 }

}

Теперь мы проверяем, есть ли у нас ссылка на компонент AbilityBihaviour и если есть то отсылаем сообщения к нему напрямую
иначе пользуемся старым добрым SendMessage.

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

Заключение: в данной части я пытался показать как и где будет содержать объект свои способности, через какие области будет
управлять ими и собирать из них данные.
pic13.png

Как видно на картинке теперь мы имеем некую зависимость, она показывает что несмотря на то что объект может обладать
менеджером способностей это не дает ответ на то что этот менеджер может использовать както свои способности, но компонент
AbilityBihaviour имеет зависимость от менеджера, и не может без него существовать в отличие от того же менеджера.

В следующей части я продолжу работу с этими двумя компонентами.

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

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

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

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

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