Загрузка
В отличие от системы сохранения способностей и их компонентов, которая была разбита на две части из за большого объема информации,
система загрузки будет намного меньше и проще т к мы все максимально подготовили для удобства использования системы способностей.
Во первых, как видно на картинке, использовать скриптовые объекты также просто как и любые другие ассеты в проекте - достаточно только
объявить переменную или массив(список) этих объектов и можно запросто добавлять их в компоненты объекта.
На рисунке изображен список в котором будут находится способности объекта при старте, это сделано по аналогии с такими играми как
StarCraft и warCraft, где персонаж мог уже заранее обладать нужными ему способностями без их загрузки.
Для этого перейдем в класс SpellManager и объявим там список способностей.
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
}
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
}
Все просто. Теперь при старте будет достаточно их добавить объекту с помощью метода AddAbility
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
protected virtual void Start() {
foreach(AbilityObject ability in this.Abilities) {
if (ability != null) this.AddAbility(ability);
}
}
}
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
protected virtual void Start() {
foreach(AbilityObject ability in this.Abilities) {
if (ability != null) this.AddAbility(ability);
}
}
}
Готово.
Но, допустим если нам нужно будет именно загрузить способность из ресурсов в процессе игры(рантайме) нам необходимо будет написать
соответствующий метод. Я назвал этот метод LoadAbility, добавил его в класс SpellManager и сделал его статическим.
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
protected virtual void Start() {
foreach(AbilityObject ability in this.Abilities) {
if (ability != null) this.AddAbility(ability);
}
}
public static AbilityObject LoadAbility(string name) {
string path = AbilitySettings.SPELL_RESOURCES_FOLDER + name + "/" + name;
AbilityObject ability = Resources.Load(path, typeof(AbilityObject)) as AbilityObject;
return ability;
}
}
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
protected virtual void Start() {
foreach(AbilityObject ability in this.Abilities) {
if (ability != null) this.AddAbility(ability);
}
}
public static AbilityObject LoadAbility(string name) {
string path = AbilitySettings.SPELL_RESOURCES_FOLDER + name + "/" + name;
AbilityObject ability = Resources.Load(path, typeof(AbilityObject)) as AbilityObject;
return ability;
}
}
Здесь все тоже очень просто. В метод LoadAbility передаю имя способности, и создаю путь к этой способности, потом просто загружаю ее
через метод Resources.Load и возвращаю способность.
Дальше, я для удобства использования менеджера способностей, добавил еще один метод AddAbility только теперь этот метод будет брать
параметр "имя" способности, выглядит это так.
Синтаксис:
Используется csharp
[DisallowMultipleComponent]
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
protected virtual void Start() {
foreach(AbilityObject ability in this.Abilities) {
if (ability != null) this.AddAbility(ability);
}
}
public static AbilityObject LoadAbility(string name) {
string path = AbilitySettings.SPELL_RESOURCES_FOLDER + name + "/" + name;
AbilityObject ability = Resources.Load(path, typeof(AbilityObject)) as AbilityObject;
return ability;
}
public void AddAbility(string name) {
AbilityObject ability = LoadAbility(name);
if (ability != null) AddAbility(ability);
}
}
public class SpellManager : MonoBehaviour {
[Header("Put object abilities into this list")]
public List<AbilityObject> Abilities = new List<AbilityObject>();
protected virtual void Start() {
foreach(AbilityObject ability in this.Abilities) {
if (ability != null) this.AddAbility(ability);
}
}
public static AbilityObject LoadAbility(string name) {
string path = AbilitySettings.SPELL_RESOURCES_FOLDER + name + "/" + name;
AbilityObject ability = Resources.Load(path, typeof(AbilityObject)) as AbilityObject;
return ability;
}
public void AddAbility(string name) {
AbilityObject ability = LoadAbility(name);
if (ability != null) AddAbility(ability);
}
}
Здесь тоже все просто. Я загружаю способность через статический метод LoadAbility и если способность была загружена - просто добавляю ее в
менеджер способностей.
Вот в общем то и все что я хотел сказать о системе загрузки способностей.
Теперь я думаю вам стало более понятен выбор в пользу скриптовых объектов в отличие от сериализации, архивирования и других способов
сохранения\загрузки способностей которые нам бы пришлось использовать.
Активные способности и их обертки.
Видео
На этом видео я показал как создавать и как работают АКТИВНЫЕ способности.
Обычно когда я рассказывал о том или ином принципе работы способностей, я обычно брался объяснять это на пассивных способностях - это
очевидно т к их легче контроллировать. В этот же раз мы закончим всю логику активных способностей. Мы уже разобрали их активацию и как
они работают в связке с временным компонентом, теперь мы займемся временем их перезагрузки(cooldown).
Давайте вспомним про обертки способностей, а именно об классе ActiveAbilityData который содержит в себе только АКТИВНЫЕ способности.
Этот класс также будет отвечать за время перезагрузки способности, для этого нам понадобится одна переменная и пара свойств, а также
некоторые знания арифметики
Для начала объявим в этом классе метод StartCooldown который будет вызываться когда способность активируется(используется), и одно
свойство.
Синтаксис:
Используется csharp
public sealed class ActiveAbilityData : AbilityData {
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
}
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
}
Когда мы вызовим метод StartCooldown в свойство startTime запишется время(Time.time) вызова этого метода, это время даст понять когда произошла активация способности, а дальше мы уже сможем исходя из этого определиться сколько нужно будет ждать пока способность перезагрузится.
Для определения всех этих условий нам нужно будет добавить три свойства(по сути методов).
timeLeft - свойство будет показывать сколько времени осталось ждать до завершения перезагрузки способности
complete - процентное свойство, также будет показывать на сколько процентов выполнилась перезагрузка.
ready - будет показывать готова ли способность для повторного использования.
Первым разберем свойство timeLeft
Синтаксис:
Используется csharp
public sealed class ActiveAbilityData : AbilityData {
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
public float timeLeft {
get {
float result = (this.startTime > 0f) ? Time.time - this.startTime : this.ability.cooldown;
return this.ability.cooldown - Mathf.Clamp(result, 0f, this.ability.cooldown);
}
}
}
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
public float timeLeft {
get {
float result = (this.startTime > 0f) ? Time.time - this.startTime : this.ability.cooldown;
return this.ability.cooldown - Mathf.Clamp(result, 0f, this.ability.cooldown);
}
}
}
Здесь отсчитываю точный интервал времени между активацией способности и ее полной перезагрузкой.
Теперь свойство complete
Синтаксис:
Используется csharp
public sealed class ActiveAbilityData : AbilityData {
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
public float timeLeft {
get {
float result = (this.startTime > 0f) ? Time.time - this.startTime : this.ability.cooldown;
return this.ability.cooldown - Mathf.Clamp(result, 0f, this.ability.cooldown);
}
}
public int complete {
get {
int result = 100 - Mathf.RoundToInt(this.timeLeft / this.ability.cooldown * 100);
return result;
}
}
}
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
public float timeLeft {
get {
float result = (this.startTime > 0f) ? Time.time - this.startTime : this.ability.cooldown;
return this.ability.cooldown - Mathf.Clamp(result, 0f, this.ability.cooldown);
}
}
public int complete {
get {
int result = 100 - Mathf.RoundToInt(this.timeLeft / this.ability.cooldown * 100);
return result;
}
}
}
здесь тоже все просто. Я нахожу разницу между временем что осталось способности перезагружаться и полным временем перезагрузки
способности.
И закончим свойством ready
Синтаксис:
Используется csharp
public sealed class ActiveAbilityData : AbilityData {
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
public float timeLeft {
get {
float result = (this.startTime > 0f) ? Time.time - this.startTime : this.ability.cooldown;
return this.ability.cooldown - Mathf.Clamp(result, 0f, this.ability.cooldown);
}
}
public int complete {
get {
int result = 100 - Mathf.RoundToInt(this.timeLeft / this.ability.cooldown * 100);
return result;
}
}
public bool ready {
get { return this.complete >= 100;}
}
}
private float startTime = 0f;
public void StartCooldown() {
this.startTime = Time.time;
}
public ActiveAbilityData(AbilityObject ability) : base(ability) {}
public new ActiveAbility ability {
get { return base.ability as ActiveAbility;}
}
public float timeLeft {
get {
float result = (this.startTime > 0f) ? Time.time - this.startTime : this.ability.cooldown;
return this.ability.cooldown - Mathf.Clamp(result, 0f, this.ability.cooldown);
}
}
public int complete {
get {
int result = 100 - Mathf.RoundToInt(this.timeLeft / this.ability.cooldown * 100);
return result;
}
}
public bool ready {
get { return this.complete >= 100;}
}
}
Здесь я просто смотрю: если процент готовности способности равен или выше 100%(процентов) - значит способность готова к использованию.
Ну вот и все, теперь у нас есть целых три свойтсва показывающих готовность АКТИВНОЙ способности, осталось только вызвать метод
StartCooldown в методе InitializeAbility класса SpellManager и там же сделать проверку на готовность способности в методе UseAbility.
Синтаксис:
Используется csharp
private IEnumerator InitializeAbility(AbilityData data) {
if (data.isUsing) {
this.StartCoroutine(FinalizeAbility(data));
yield return new WaitForFixedUpdate();
}
data.isUsing = true;
if (data.ability.type == AbilitySettings.AbilityType.Active) {
ActiveAbilityData aData = data as ActiveAbilityData;
aData.StartCooldown();
}
IAbilityComponentable compSys = data.ability as IAbilityComponentable;
if (compSys == null) yield break;
foreach(AbilityComponent component in compSys.components) {
if (component.CheckForAttribute<DisableComponentMultiplyUsage>())
SpecialComponentExpansion(component, data);
else
SendComponentEvent(SpellManagerData.ComponentSendEvent.OnComponentExpansion, component);
yield return new WaitForFixedUpdate();
}
}
if (data.isUsing) {
this.StartCoroutine(FinalizeAbility(data));
yield return new WaitForFixedUpdate();
}
data.isUsing = true;
if (data.ability.type == AbilitySettings.AbilityType.Active) {
ActiveAbilityData aData = data as ActiveAbilityData;
aData.StartCooldown();
}
IAbilityComponentable compSys = data.ability as IAbilityComponentable;
if (compSys == null) yield break;
foreach(AbilityComponent component in compSys.components) {
if (component.CheckForAttribute<DisableComponentMultiplyUsage>())
SpecialComponentExpansion(component, data);
else
SendComponentEvent(SpellManagerData.ComponentSendEvent.OnComponentExpansion, component);
yield return new WaitForFixedUpdate();
}
}
Как вы видите, после установления свойства isUsing в значение true, я проверяю: если способность активная - то вызываю метод
StartCooldown для начала перезагрузки способности.
Теперь идем в метод UseAbility где выставим проверку на готовность способности.
Синтаксис:
Используется csharp
private void UseAbility(AbilityData data) {
if (data.ability.type == AbilitySettings.AbilityType.Active) {
ActiveAbilityData aData = data as ActiveAbilityData;
if (aData.ready == false) return;
}
SendAbilityEvent(SpellManagerData.AbilitySendEvent.OnAbilityUsed, data.ability);
this.StartCoroutine(InitializeAbility(data));
}
if (data.ability.type == AbilitySettings.AbilityType.Active) {
ActiveAbilityData aData = data as ActiveAbilityData;
if (aData.ready == false) return;
}
SendAbilityEvent(SpellManagerData.AbilitySendEvent.OnAbilityUsed, data.ability);
this.StartCoroutine(InitializeAbility(data));
}
Здесь я перед началом использования способности проверяю: если способность АКТИВНАЯ значит нужно проверить не перезагружается ли она в
данный момент и если это так то просто прекращаю выполнение метода UseAbility пока способность не перезагрузится.
Вот в общем это и все, о результатах работы вы можете посмотреть видео.
Также я добавил приложение в виде пака куда попала полная система способностей, а также сцена для тестирования, чтобы вы могли
посмотреть и исправить свои ошибки если таковы были допущены в процессе работы. Также система которая была написанная в процессе этой статьи немного отличается от той что в паке мелкими деталями - которые в принципе не меняют суть ее работы, а просто упрощают ее использование. Об этих
мелких детальках я хотел бы рассказать в другой части, уже не части статьи а просто "дополненией" к ней.
Заключение: ну вот и закончили мы создание системы способностей
Скрытый текст:
Если в общем - со стороны посмотреть на всю эту систему, то она конечно мало чем напоминает такие громоздкие системы тех же способностей
в таких играх как StarCraft и куда уж поскромнее чем в WarCraft. Эта система в основном направлена на роботу с конкретными объектами
которые ими обладают, а также обладают малой интерактивностью. Но как я упоминал еще в первой части статьи, я не старался создать
аналогичную систему на unity я делал это исключительно для этой игры, где не было
необходимости в такой сложной системе. Но я думаю благодаря такому старту, если у вас появится желание, вы сможете добавить или изменить
или даже создать свою систему более универсальную, которая будет отвечать вашим требованиям и желаниям.
Ну вот и все, на этом я хочу закончить знакомство с системой способностей на движке unity3D, ровно за 15 частей - фухх, всего хорошего и удачи в разработке.
Часть о "мелких деталях" выйдет завтра в дополнение к 15 части.
Используемые статьи
Скрытый текст:
автор: этот хрен, он же llka, он же lawsonilka