Обзор:
Во многих играх с RPG-подобной статистикой предусмотрены «положительные эффекты» для персонажей, начиная от простого «Нанести 25% дополнительного урона» до более сложных вещей, таких как «Наносить 15 урона обратно атакующим при попадании».
Специфика каждого типа баффа на самом деле не актуальна. Я ищу (предположительно объектно-ориентированный) способ обработки произвольных эффектов.
Подробности:
В моем конкретном случае у меня есть несколько персонажей в пошаговом боевом окружении, поэтому я предполагал, что баффы будут привязаны к таким событиям, как «OnTurnStart», «OnReceiveDamage» и т. Д. Возможно, каждый бафф является подклассом основного абстрактного класса Buff, где перегружены только соответствующие события. Тогда у каждого персонажа может быть свой вектор положительных эффектов.
Имеет ли это решение смысл? Я, конечно, вижу, что нужны десятки типов событий, похоже, что создание нового подкласса для каждого баффа является излишним, и, похоже, он не допускает каких-либо "взаимодействий" баффов. То есть, если бы я хотел применить ограничение на усиление урона, чтобы даже если у вас было 10 различных баффов, каждый из которых дает 25% дополнительного урона, вы наносите только 100% дополнительного вместо 250% дополнительного.
И есть более сложные ситуации, которые в идеале я мог бы контролировать. Я уверен, что каждый может придумать примеры того, как более сложные любители могут потенциально взаимодействовать друг с другом таким образом, который я, как разработчик игры, может и не захотеть.
Как относительно неопытный программист C ++ (я обычно использовал C во встроенных системах), я чувствую, что мое решение упрощено и, вероятно, не в полной мере использует объектно-ориентированный язык.
Мысли? Кто-нибудь здесь разрабатывал довольно надежную систему баффов раньше?
Изменить: Относительно ответа (ов):
Я выбрал ответ, основанный в первую очередь на хорошей детализации и четком ответе на вопрос, который я задал, но чтение ответов дало мне некоторое понимание.
Возможно неудивительно, что различные системы или измененные системы, кажется, лучше подходят для определенных ситуаций. Какая система лучше всего подойдет для моей игры, зависит от типов, дисперсии и количества положительных эффектов, которые я собираюсь применить.
Для такой игры, как Diablo 3 (упоминается ниже), где почти любой бит экипировки может изменить силу баффа, баффы - это просто статистика персонажей, и система всегда кажется хорошей идеей, когда это возможно.
Для пошаговой ситуации, в которой я нахожусь, подход на основе событий может быть более подходящим.
В любом случае, я все еще надеюсь, что кто-то придет с необычной магической пулей "ОО", которая позволит мне применить +2 к расстоянию перемещения за бафф хода , нанести 50% урона обратно баффу атакующего , и автоматически телепортироваться к соседней плитке при атаке из 3 или более плиток прочь любителя в одной системе , не поворачивая +5 силы бафф в свой собственный подкласс.
Я думаю, что самым близким является ответ, который я отметил, но слово все еще открыто. Спасибо всем за вклад.
источник
Ответы:
Это сложный вопрос, потому что вы говорите о нескольких разных вещах, которые (в наши дни) объединяются в «баффы»:
Я всегда реализую первый со списком активных эффектов для определенного персонажа. Удаление из списка, будь то на основе продолжительности или явно, довольно тривиально, поэтому я не буду здесь останавливаться на этом. Каждый эффект содержит список модификаторов атрибутов и может применять его к базовому значению путем простого умножения.
Затем я обертываю его функциями для доступа к измененным атрибутам. например.:
Это позволяет достаточно легко применять мультипликативные эффекты. Если вам также нужны аддитивные эффекты, решите, в каком порядке вы собираетесь их применять (возможно, аддитивные в последнюю очередь), и дважды просмотрите список. (Я бы, вероятно, имел отдельные списки модификаторов в Effect, один для мультипликативного, один для аддитивного).
Значение критерия состоит в том, чтобы позволить вам реализовать «+ 20% против нежити» - установите значение UNDEAD для эффекта и передайте значение UNDEAD только
get_current_attribute_value()
при расчете броска урона против врага-нежити.Между прочим, у меня не было бы соблазна попробовать написать систему, которая применяет и отменяет применение значений непосредственно к значению базового атрибута - конечный результат состоит в том, что ваши атрибуты с большой вероятностью отклонятся от предполагаемого значения из-за ошибки. (Например, если вы умножаете что-то на 2, но затем ограничиваете это, когда вы снова разделите это на 2, это будет ниже, чем началось.)
Что касается основанных на событиях эффектов, таких как «Нанести 15 урона обратно атакующим при попадании», вы можете добавить методы для класса «Эффект» для этого. Но если вы хотите различного и произвольного поведения (например, некоторые эффекты для вышеуказанного события могут отражать нанесенный ущерб, некоторые могут вылечить вас, это может вас телепортировать случайно, что угодно), вам понадобятся пользовательские функции или классы для их обработки. Вы можете назначить функции обработчикам событий для эффекта, затем вы можете просто вызвать обработчики событий для любых активных эффектов.
Очевидно, что ваш класс Effect будет иметь обработчик события для каждого типа события, и вы можете назначить функции обработчика столько, сколько вам нужно в каждом случае. Вам не нужно создавать подкласс Effect, поскольку каждый из них определяется составом модификаторов атрибутов и обработчиков событий, которые он содержит. (Возможно, оно также будет содержать имя, продолжительность и т. Д.)
источник
В игре, над которой я работал с другом для класса, мы создали систему баффов / дебаффов, когда пользователь попадает в ловушку с высокой травой и ускоряющимися тайлами, а что нет, а также с некоторыми незначительными вещами, такими как кровотечения и яды.
Идея была проста, и хотя мы применили ее в Python, она была довольно эффективной.
В основном, вот как это было:
Теперь о том, как на самом деле применять баффы из мира, - другая история. Вот моя пища для размышлений.
источник
Я не уверен, читаете ли вы это до сих пор, но я долго боролся с подобной проблемой.
Я разработал множество различных типов систем воздействия. Я кратко расскажу о них сейчас. Это все основано на моем опыте. Я не утверждаю, что знаю все ответы.
Статические модификаторы
Этот тип системы в основном полагается на простые целые числа для определения любых модификаций. Например, от +100 до Макс. HP, +10 к атаке и так далее. Эта система также может обрабатывать проценты. Вам просто нужно убедиться, что укладка не выходит из-под контроля.
Я никогда не кэшировал сгенерированные значения для системы такого типа. Например, если бы я хотел показать максимальное здоровье чего-либо, я бы сгенерировал значение на месте. Это предотвратило склонность к ошибкам и облегчило понимание для всех участников.
(Я работаю на Java, поэтому ниже следует Java, но он должен работать с некоторыми модификациями для других языков). Эту систему можно легко сделать, используя перечисления для типов модификации, а затем целые числа. Конечный результат может быть помещен в какую-то коллекцию, которая имеет пары упорядоченных ключей и значений. Это будет быстрый поиск и расчеты, поэтому производительность очень хорошая.
В целом, это работает очень хорошо с простыми статическими модификаторами Тем не менее, код должен существовать в надлежащих местах для используемых модификаторов: getAttack, getMaxHP, getMeleeDamage и так далее и так далее.
Там, где этот метод не работает (для меня) очень сложное взаимодействие между любителями. Нет реального простого способа взаимодействия, кроме как немного поднять его. У него есть несколько простых возможностей взаимодействия. Чтобы сделать это, вы должны внести изменения в способ хранения статических модификаторов. Вместо использования enum в качестве ключа, вы используете String. Эта строка будет именем Enum + дополнительная переменная. 9 раз из 10 дополнительная переменная не используется, поэтому вы по-прежнему сохраняете имя перечисления в качестве ключа.
Давайте сделаем быстрый пример: если вы хотите иметь возможность изменять урон против нежити, у вас может быть упорядоченная пара, подобная этой: (DAMAGE_Undead, 10) DAMAGE - это Enum, а Undead - дополнительная переменная. Поэтому во время боя вы можете сделать что-то вроде:
В любом случае, это работает довольно хорошо и быстро. Но он терпит неудачу при сложных взаимодействиях и наличии «специального» кода везде. Например, рассмотрим ситуацию с «25% шансом телепортироваться при смерти». Это «довольно» сложный вопрос. Вышеуказанная система может справиться с этим, но не легко, так как вам необходимо следующее:
Так что это подводит меня к следующему:
Ультимативная комплексная система баффов
Однажды я попытался написать 2D-MMORPG самостоятельно. Это была ужасная ошибка, но я многому научился!
Я переписал систему аффектов 3 раза. Первый использовал менее мощный вариант из вышеперечисленного. Вторым было то, о чем я собираюсь поговорить.
Эта система имела ряд классов для каждой модификации, поэтому такие вещи, как: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. У меня был миллион таких парней - даже такие вещи, как TeleportOnDeath.
У моих классов были вещи, которые делали следующее:
Применить и удалить объяснить сами (хотя для таких вещей, как проценты, эффект будет отслеживать, насколько он увеличил HP, чтобы убедиться, что когда эффект исчезает, он только удалит добавленную сумму. Это было с ошибками, lol, и Мне потребовалось много времени, чтобы убедиться, что это правильно. У меня все еще не было хорошего чувства по этому поводу.).
Метод checkForInteraction был ужасно сложным фрагментом кода. В каждом из классов аффектов (т. Е. ChangeHP) он будет иметь код, чтобы определить, должен ли он быть изменен входным аффектом. Так, например, если у вас было что-то вроде ....
Метод checkForInteraction будет обрабатывать все эти эффекты. Для этого нужно было проверить каждое влияние на ВСЕХ игроков поблизости! Это потому, что тип аффектов, с которыми я сталкивался у нескольких игроков на протяжении области. Это означает, что в коде НИКОГДА не было каких-либо специальных утверждений, подобных приведенным выше - «если мы только что умерли, мы должны проверить телепорт при смерти». Эта система будет автоматически обрабатывать его правильно в нужное время.
Попытка написать эту систему заняла у меня около 2 месяцев и несколько раз заставила голову взорваться. ОДНАКО, он был ДЕЙСТВИТЕЛЬНО мощным и мог делать безумное количество вещей - особенно если учесть следующие два факта для способностей в моей игре: 1. У них были целевые диапазоны (то есть: одиночный, сам, только группа, PB AE self , PB AE target, целевой AE и т. Д.). 2. Способности могут иметь более 1 влияния на них.
Как я уже упоминал выше, это была 2-ая из 3-х аффектных систем для этой игры. Почему я отошел от этого?
У этой системы была худшая производительность, которую я когда-либо видел! Это было ужасно медленно, так как нужно было так много проверять каждую происходящую вещь. Я пытался улучшить его, но счел это неудачей.
Итак, мы подошли к моей третьей версии (и другому типу баффов):
Комплексный аффект-класс с обработчиками
Так что это в значительной степени комбинация первых двух: у нас могут быть статические переменные в классе Affect, который содержит множество функций и дополнительных данных. Затем просто вызовите обработчики (для меня, скорее, некоторые статические служебные методы вместо подклассов для конкретных действий. Но я уверен, что вы можете использовать подклассы для действий, если вы тоже этого хотите), когда мы хотим что-то сделать.
Класс Affect будет содержать все сочные полезные вещи, такие как целевые типы, продолжительность, количество использований, шанс выполнения и так далее, и так далее.
Мы все равно должны были бы добавить специальные коды для обработки ситуаций, например, телепортации после смерти. Нам все равно придется проверять это в боевом коде вручную, а затем, если он существует, мы получим список эффектов. Этот список аффектов содержит все применяемые в настоящее время аффекты на игрока, который имел дело с телепортацией после смерти. Затем мы просто посмотрели бы на каждый из них и проверили, выполнялся ли он и был ли он успешным (остановимся на первом успешном). Если все прошло успешно, мы бы просто позвонили обработчику, чтобы позаботиться об этом.
Взаимодействие может быть сделано, если вы тоже хотите. Нужно просто написать код, чтобы искать конкретные баффы для игроков и т. Д. Поскольку он имеет хорошую производительность (см. Ниже), он должен быть достаточно эффективным для этого. Просто нужны более сложные обработчики и так далее.
Таким образом, она имеет высокую производительность первой системы и все еще очень сложна, как вторая (но не так много). В Java, по крайней мере, вы можете сделать несколько хитрых вещей, чтобы получить производительность почти первой в большинстве случаев (например, наличие карты enum ( http://docs.oracle.com/javase/6/docs/api/java) /util/EnumMap.html ) с Enums в качестве ключей и ArrayList воздействий в качестве значений. Это позволяет увидеть, есть ли у вас быстрые эффекты [поскольку список будет равен 0, или на карте не будет перечисления], и не иметь постоянно перебирать списки аффектов игрока без всякой причины. Я не возражаю перебирать аффекты, если они нам нужны в данный момент. Я оптимизирую позже, если это станет проблемой).
В настоящее время я заново открываю (переписываю игру на Java вместо базы кода FastROM, в которой она была изначально), мой MUD, который закончился в 2005 году, и недавно я столкнулся с тем, как я хочу реализовать свою систему баффов? Я собираюсь использовать эту систему, потому что она хорошо работала в моей предыдущей неудачной игре.
Что ж, надеюсь, кто-нибудь где-нибудь найдет некоторые из этих идей полезными.
источник
Различный класс (или адресуемая функция) для каждого баффа не является избыточным, если поведение этих баффов отличается друг от друга. Одно было бы иметь баффы + 10% или + 20% (что, конечно, было бы лучше представить в виде двух объектов одного класса), другое - реализовывать совершенно разные эффекты, которые в любом случае требовали бы пользовательского кода. Тем не менее, я считаю, что лучше иметь стандартные способы настройки игровой логики, а не позволять каждому баффу делать то, что ему нравится (и, возможно, мешать друг другу непредвиденными способами, нарушая баланс игры).
Я бы предложил разделить каждый «цикл атаки» на этапы, где каждый шаг имеет базовое значение, упорядоченный список модификаций, которые могут быть применены к этому значению (возможно, ограничен), и окончательный предел. Каждая модификация имеет преобразование идентичности по умолчанию и может зависеть от нуля или более баффов / дебаффов. Специфика каждой модификации будет зависеть от примененного шага. Как реализовать этот цикл, зависит от вас (включая опцию архитектуры, управляемой событиями, как вы уже обсуждали).
Одним из примеров цикла атаки может быть:
Важно отметить, что чем раньше в цикле применяется бафф, тем больший эффект он будет иметь в результате . Поэтому, если вы хотите более «тактический» бой (где умение игрока важнее уровня персонажа), создайте много баффов / дебаффов на базовых характеристиках. Если вы хотите более «сбалансированный» бой (где уровень важнее - важно в MMOG-играх ограничивать скорость прогресса), используйте баффы / дебаффы только в конце цикла.
Различие между «Модификациями» и «Баффами», о которых я упоминал ранее, имеет цель: решения о правилах и балансе могут быть применены к первым, поэтому любые изменения в них не должны отражаться в изменениях в каждом классе последних. OTOH, количество и виды положительных эффектов ограничены только вашим воображением, поскольку каждый из них может выражать желаемое поведение, не принимая во внимание любое возможное взаимодействие между ними и другими (или даже вообще существование других).
Итак, отвечая на вопрос: не создавайте класс для каждого Баффа, а по одному для каждой (типа) Модификации, и привязывайте Модификацию к циклу атаки, а не к персонажу. Баффы могут быть просто списком (модификация, ключ, значение) кортежей, и вы можете применить бафф к персонажу, просто добавив / удалив его в набор баффов персонажа. Это также уменьшает окно для ошибки, так как статистика персонажа вообще не должна изменяться при применении баффов (так что меньше риск восстановить статистику до неправильного значения после истечения баффа).
источник
Я не знаю, читаете ли вы это по-прежнему, но вот как я это делаю сейчас (код основан на UE4 и C ++). Обдумав проблему в течение более двух недель (!!), я наконец нашел это:
http://gamedevelopment.tutsplus.com/tutorials/using-the-composite-design-pattern-for-an-rpg-attributes-system--gamedev-243
И я подумал, что инкапсуляция одного атрибута в классе / структуре не так уж плоха. Имейте в виду, однако, что я пользуюсь действительно большим преимуществом встроенной системы отражения кода UE4, поэтому без некоторых переделок это может не подойти везде.
В любом случае, я начал с упаковки атрибута в одну структуру:
Это еще не закончено, но основная идея состоит в том, что эта структура отслеживает свое внутреннее состояние. Атрибуты могут быть изменены только с помощью эффектов. Попытки изменить их напрямую небезопасны и не доступны для дизайнеров. Я предполагаю, что все, что может взаимодействовать с атрибутами - это Effect. В том числе плоские бонусы от предметов. Когда новый предмет экипирован, создается новый эффект (вместе с дескриптором), который добавляется на выделенную карту, которая обрабатывает бонусы бесконечной продолжительности (те, которые игрок должен удалить вручную). Когда применяется новый эффект, создается новый дескриптор для него (дескриптор просто int, обернутый структурой), а затем этот дескриптор передается как средство взаимодействия с этим эффектом, а также отслеживается, если эффект все еще активен. Когда эффект удален, его дескриптор транслируется на все заинтересованные объекты,
Действительно важной частью этого является TMap (TMap - хешированная карта). FGAModifier - это очень простая структура:
Содержит тип модификации:
И Значение, которое является окончательным рассчитанным значением, которое мы собираемся применить к атрибуту.
Мы добавляем новый эффект, используя простую функцию, а затем вызываем:
Предполагается, что эта функция пересчитывает весь стек бонусов, каждый раз, когда эффект добавляется или удаляется. Функция еще не закончена (как вы можете видеть), но вы можете получить общее представление.
Моя самая большая проблема сейчас связана с обработкой атрибута Damaging / Healing (без перерасчета всего стека), я думаю, что это несколько решено, но все равно требуется больше тестов, чтобы быть на 100%.
В любом случае атрибуты определяются следующим образом (+ нереальные макросы, здесь опущены):
и т.п.
Также я не уверен на 100% в обработке CurrentValue атрибута, но он должен работать. Они так и есть сейчас.
В любом случае, я надеюсь, что это спасет кеш некоторых людей, не уверенный, является ли это лучшим или даже хорошим решением, но мне это нравится больше, чем отслеживание эффектов независимо от атрибутов. В этом случае гораздо проще сделать отслеживание каждого атрибута своим собственным состоянием, и оно должно быть менее подвержено ошибкам. В сущности, существует только одна точка отказа - довольно короткий и простой класс.
источник
Я работал над небольшой MMO, и все предметы, способности, баффы и т. Д. Имели «эффекты». Эффектом был класс, в котором были переменные для AddDefense, InstantDamage, HealHP и т. Д. Силы, предметы и т. Д. Будут обрабатывать длительность этого эффекта.
Когда вы накладываете силу или надеваете предмет, он будет применять эффект к персонажу в течение указанного времени. Тогда основная атака и т. Д. Расчеты будут учитывать примененные эффекты.
Например, у вас есть бафф, который добавляет защиту. Для этого баффа будут как минимум EffectID и Duration. При приведении его он будет применять EffectID к персонажу в течение указанной продолжительности.
Другой пример для элемента, будет иметь те же поля. Но длительность будет бесконечной или до тех пор, пока эффект не будет снят, снимая предмет с персонажа.
Этот метод позволяет перебирать список эффектов, которые в настоящее время применяются.
Надеюсь, я объяснил этот метод достаточно четко.
источник
Я использую ScriptableOjects как баффы / заклинания / таланты
используя UnityEngine; using System.Collections.Generic;
public enum BuffType {Buff, Debuff} [System.Serializable] открытый класс BuffStat {public Stat Stat = Stat.Strength; public float ModValueInPercent = 0.1f; }
BuffModul:
источник
Это был актуальный вопрос для меня. У меня есть одна идея об этом.
Buff
список и логику обновления для баффов.Buff
класса.Таким образом, может быть легко добавить новую статистику игрока, без изменений в логике
Buff
подклассов.источник
Я знаю, что это довольно старый, но он был связан в более новом сообщении, и у меня есть некоторые мысли по этому поводу, которыми я хотел бы поделиться. К сожалению, в данный момент у меня нет с собой заметок, поэтому я постараюсь дать общий обзор того, о чем я говорю, и я отредактирую детали и пример кода, когда они будут перед меня.
Во-первых, я думаю, что с точки зрения дизайна большинство людей слишком зациклены на том, какие типы баффов могут быть созданы и как они применяются, и забывают об основных принципах объектно-ориентированного программирования.
Что я имею в виду? Неважно, является ли что-то баффом или дебаффом, они оба являются модификаторами, которые просто влияют на что- то положительное или отрицательное. Коду не важно, что есть что. В этом отношении в конечном счете не имеет значения, добавляет ли что-то статистику или умножает ее, это просто разные операторы, и опять-таки, в коде все равно, какой именно.
Так куда я иду с этим? Создание хорошего (читай: простого, изящного) класса баффа / дебаффа не так уж и сложно, сложно разработать системы, которые рассчитывают и поддерживают состояние игры.
Если бы я проектировал систему баффов / дебаффов, вот несколько вещей, которые я бы рассмотрел:
Некоторые особенности того, что типы баффов / дебаффов должны содержать:
Это только начало, но оттуда вы просто определяете, что вы хотите, и действуете в соответствии с вашим обычным игровым состоянием. Например, скажем, вы хотели создать проклятый предмет, который уменьшает скорость передвижения ...
Пока я поместил правильные типы на место, просто создать запись баффа, которая говорит:
И так далее, и когда я создаю бафф, я просто назначаю ему BuffType of Curse, а все остальное зависит от движка ...
источник