Подскажите, принцип (архитектуру, структуру), как организовать объекты, свойства которых могут зависеть от разных факторов? Для примера возьмем ХитПоинты и броню public class Player { decimal _hitPoints decimal _armor public decimal HitPoints { get { return _hitPoints } set { _hitPoints = value } } public decimal Armor { get { return _armor } set { _armor = value } } } Итак, у игрока есть хитпоинты. Их количество может меняться посредством каких либо действий или состояний. Пример действий: игрок может сожрать булочку игрок может упасть игрок может покончить жизнь самоубийством игрока может атаковать другой игрок Пример состояния: у игрока есть постоянная регенерация на игрока повесили бафы / дебафы Все это может повлиять на количество хитпоинтов. Менять состояние в лоб public DecraseHitpoints(decimal value) { HitPoints -= value * [функция от брони] * [функция от состояния1] * [функция от состояния2] } проблематично, потому что добавив новую зависимость я замучаюсь все это поддерживать. Функции от состояния в данном случае - что угодно, например, баф - снижение получаемого урона на 50%. Короче обычные привычные подходы тут не подходят :) Нужна кардинально другая архитектура. Я представляю это себе примерно так. Класс имеет коллекцию действий и состояний. Действие должно быть направлено на изменение какого то свойства (или нескольких свойств) и описывать КАК эти свойства должны быть изменены. Опять же должна быть зависимость от состояния. Например, если в данный момент игрок неуязвим, урон вообще не должен проходить. Голова кругом идет уже. UPD: Ну да, при этом действие, нацеленное на изменение свойств, должно не просто учесть состояния, которые могут менять воздействие, но оно не должно знать обо всех состояниях, а только о воздействии состояния. То есть не нужно знать обо всех возможных бафах, нужно знать только о том, что на игрока наложены бафы, которые могут изменить зависимые величины (броню, количество урона непосредственно)...
Ответ Можно подглядеть как это сделано у других. Например, в Skyrim это делается через систему перков. К каждому персонажу привязывается динамический список перков, которые могут иметь разные точки входа. Точка входа - это игровое событие (например, получение урона). Перки обрабатывают событие в некотором порядке - и могут менять обрабатываемое значение (в случае получения урона - величину отнимаемого здоровья). У перка могут быть условия срабатывания (день/ночь, источник урона, надетые предметы и пр.).
Полностью копировать такую систему не стоит - просто потому что она была сделана для гейм-дизайнеров, у вас же отдельного гейм-дизайнера нет и вы можете вместо создания мегауниверсального движка просто писать код. Выглядеть это может таким образом: class BaseModifier { public virtual void ModifyIncomingDamage(ref decimal value, DamageEvent e) { } public virtual void ЧтоТоЕще(ref decimal value, КакойТоЕщеEvent e) { } } class ArmorModifier : BaseModifier { public override void ModifyIncomingDamage(ref decimal value, DamageEvent e) { value *= функцияОтБрони } } class ModifierCollection { private readonly List modifiers = new List() public void Add(BaseModifier m) => modifiers.Add(m) public void Remove(BaseModifier m) where T : BaseModifier => modifiers.Remove(m) public void Remove() where T : BaseModifier => Remove(modifiers.OfType().First()) public decimal ModifyIncomingDamage(decimal value, DamageEvent e) { foreach (var m in modifiers) m.ModifyIncomingDamage(ref value, e) return value } } class Character { public decimal HitPoints { get set } public ModifierCollection Modifiers { get } = new ModifierCollection() public void Hit(decimal value, DamageEvent e) => HitPoints -= Modifiers.ModifyIncomingDamage(value, e) }
Теперь про упомянутую вами регенерацию здоровья. Сама по себе регенерация - это не состояние, это величина. Точнее, это величина, определяемая "навешанными" на персонажа эффектами. Хранить ее можно двумя путями. Не хранить вообще, а при необходимости пробегаться по всем активным эффектам и суммировать их воздействия. Хранить как число, изменять при появлении/исчезновении эффекта. Второй вариант предпочтительнее (будет работать быстрее) - но требует большей аккуратности при реализации. Непосредственно реализация регенерации может быть достигнута двумя способами. По таймеру - периодически обновлять здоровье всех персонажей. Непрерывный вариант - обновлять здоровье при чтении или изменении состояния, в зависимости от прошедшего с момента прошлого обновления времени. Недостаток первого варианта - в том, что при слишком большом интервале обновления регенерация станет заметно рваной, а слишком низкий интервал нагрузит процессор впустую. Недостаток второго варианта - надо писать много кода. Плюс может быть проблема с умениями, которые делают текущую регенерацию здоровья зависимой от текущего значения здоровья (пример такой вредной способности - пассивка мурлока в "Жизни на арене"). Поэтому обычно не заморачиваются и обновляют здоровье по таймеру. А чтобы полоска здоровья не дергалась прыжками - в интерфейсе делаются плавные переходы. В итоге, регенерация здоровья может быть реализована примерно так: class Character { public decimal HitPoints { get set } public decimal HitPointsRegen { get set } public ModifierCollection Modifiers { get } = new ModifierCollection() public void OnUpdate(decimal interval) { HitPoints += Modifiers.ModifyRegen(HitPointsRegen, this) * interval } } Можно также запоминать модифицированное значение регенерации здоровья для ускорения: class Character { public decimal HitPoints { get set } private decimnal _hitPointsRegen, _hitPointsRegenModified public decimal HitPointsRegen { get { return _hitPointsRegen } set { _hitPointsRegen = value _hitPointsRegenModified = Modifiers.ModifyRegen(HitPointsRegen, this) } } public ModifierCollection Modifiers { get } = new ModifierCollection () public Character() { Modifiers.ModifiersUpdated += OnModifiersUpdated } protected void OnModifiersUpdated() { HitPointsRegen = HitPointsRegen // Обновление зависимых значений } public void OnUpdate(decimal interval) { HitPoints += _hitPointsRegenModified * interval } }