Такие характеристики, как Сила, Интеллект, Ловкость, стали основными во многих играх. И когда мы получаем +5 силы от предмета, -10% от дебаффа и + 15% от таланта, хорошо иметь систему, которая систематизированно отслеживает все эти модификаторы.

Прежде чем начать, нужно отдать должное, моя реализация была в значительной степени вдохновлена ​​этим постом. Это руководство по Flash, в котором весь код написан на ActionScript, но в любом случае концепция великолепна.

Для основ этой системы нам нужен класс, представляющий Стат, с переменной для Базового значения Стат и список для хранения всех индивидуальных Модификаторов, которые были применены к этому Стат:

Нам также нужен класс для представления модификаторов характеристик:

Что такое readonly?
Когда переменная объявлена ​​как readonly, вы можете присвоить ей значение только во время ее объявления или внутри конструктора ее класса. В противном случае компилятор выдаст ошибку. Это предотвращает непреднамеренное выполнение таких действий, как:
statModifiers = null;

statModifiers = new List<StatModifier>();
Если в любой момент нам понадобится свежий пустой список, мы всегда можем просто вызвать statModifiers.Clear().

Обратите внимание, что ни один из этих классов не является производным от MonoBehaviour. Мы не будем прикреплять их к игровым объектам.

Хотя статистика обычно представляет собой целые числа, я использую float вместо int , потому что при применении процентных бонусов мы можем легко получить десятичные числа. Таким образом, статистика более точно отображает значение, и тогда мы можем делать любое округление, которое сочтем нужным. Кроме того, если нам действительно нужна статистика, которая не является целым числом, она уже учтена.

Теперь нам нужно иметь возможность добавлять и удалять модификаторы в / из статистики, вычислять окончательное значение статистики (с учетом всех этих модификаторов), а также получать окончательное значение. Добавим в класс CharacterStat следующее:

Если вы похожи на меня, то, вероятно, вас беспокоит тот факт, что мы звоним CalculateFinalValue() каждый раз, когда нам нужно значение статистики. Чтобы этого избежать, внесем следующие изменения:

Отлично, мы можем добавить к нашей статистике «плоские» модификаторы, но как насчет процентов? Хорошо, это означает, что существует как минимум 2 типа модификаторов статистики, давайте создадим enum для определения этих типов:

Нам нужно изменить наш StatModifier класс, чтобы учесть эти типы:

И измените наш CalculateFinaValue() method в классе CharacterStat, чтобы работать с каждым типом по-разному:

Почему такое странное вычисление процентов?
Допустим, у нас есть значение 20, и мы хотим добавить к нему + 10%.
Сначала мы можем вычислить много 10% умножается на 0,1:
20 * 0,1 = 2
Затем мы добавляем результат этого к исходному значению:
20 + 2 = 22

Имея это в виду, эту странную строку кода можно было бы записать так:
finalValue += finalValue * mod.Value;

Однако, поскольку исходное значение всегда равно 100%, и мы хотим добавить к нему 10%, легко увидеть, что это сделает его 110%.
Это означает, что мы можем сначала сложить проценты:
1 + 0,1 = 1,1
А затем умножьте исходное значение на результат:
20 * 1,1 = 22

Это работает и для отрицательных чисел: если мы хотим изменить на -10%, это означает, что у нас останется 90%, поэтому мы умножаем на 0,9.

Теперь мы можем работать с процентами, но наши модификаторы всегда применяются в том порядке, в котором они добавляются в список. Если у нас есть навык или талант, увеличивающие нашу силу на 15%, а затем мы экипируем предмет с силой +20 (после получения этого навыка), эти + 15% не будут применяться к только что экипированному предмету.

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

Для этого внесем следующие изменения в класс StatModifier:

Что, черт возьми, с этим конструктором?
В C # для вызова конструктора из другого конструктора вы, по сути, «расширяете» конструктор, который хотите вызвать.
В этом случае мы определили конструктор, которому нужны только value и type, затем он вызывает конструктор, которому также требуется order, но передает int представление type в качестве порядка по умолчанию.

Как работает (int)type?
В C # каждому элементу enum автоматически назначается индекс. По умолчанию первый элемент равен 0, второй - 1 и т. Д. Вы можете назначить собственный индекс, если хотите, но нам это не нужно… пока. Если вы наведете указатель мыши на элемент enum, вы увидите индекс этого элемента во всплывающей подсказке (по крайней мере, в Visual Studio).

Чтобы получить индекс элемента enum, мы просто приводим его к int.

С этими изменениями мы можем установить order для каждого модификатора, но если мы этого не сделаем, модификаторы flat будут применяться перед модификаторами процент. Таким образом, по умолчанию мы получаем наиболее распространенное поведение, но мы также можем делать другие вещи, например, принудительно применять специальный модификатор flat после всего остального.

Теперь нам нужен способ применения модификаторов в соответствии с их order при вычислении окончательного значения статистики. Самый простой способ сделать это - отсортировать список statModifiers всякий раз, когда мы добавляем новый модификатор. Таким образом, нам не нужно менять метод CalculateFinalValue(), потому что все уже будет в правильном порядке.

Как работают Sort() и CompareModifierOrder()?
Sort() - это метод C # для всех списков, который, как следует из названия, сортирует список. Критерии, которые он использует для сортировки списка, должны быть предоставлены нами в форме функции сравнения. Если мы не предоставляем функцию сравнения, она использует компаратор по умолчанию (что бы он ни делал).

Функция сравнения будет использоваться методом Sort() для сравнения пар объектов в списке. Для каждой пары объектов возможны 3 ситуации:

1. Первый объект a должен располагаться перед вторым объектом b. Функция возвращает отрицательное значение (-1).
2. Первый объект a должен располагаться после второго объекта b. Функция возвращает положительное значение (1).
3. Оба объекта равны по «приоритету». Функция возвращает 0.

Часть 1 на этом заканчивается, надеюсь, это было полезно. Вы можете перейти к части 2 здесь:



Я буду читать и отвечать на каждый комментарий, поэтому не стесняйтесь оставлять комментарии, если у вас есть какие-либо вопросы, предложения или отзывы.
До свидания!