Как я могу разработать множество различных типов атак, которые можно комбинировать?

17

Я делаю 2D-игру сверху вниз и хочу иметь много разных типов атак. Я хотел бы сделать атаки очень гибкими и совместимыми, как работает «Привязка Исаака». Вот список всех предметов коллекционирования в игре . Чтобы найти хороший пример, давайте посмотрим на предмет Spoon Bender .

Ложка Бендера дает Исааку возможность стрелять в слезы.

Если вы посмотрите на раздел «Синергия», то увидите, что его можно комбинировать с другими предметами коллекционирования для получения интересных, но интуитивно понятных эффектов. Например, если он сочетается с «Внутренним глазом» , это «позволит Исааку сделать несколько выстрелов в самонаводящихся местах одновременно». Это имеет смысл, потому что Внутренний Глаз

Дает Исааку тройной выстрел

Какая хорошая архитектура для дизайна таких вещей? Вот решение грубой силы:

if not spoon bender and not the inner eye then ...
if spoon bender and not the inner eye then ...
if not spoon bender and the inner eye then ...
if spoon bender and the inner eye then ...

Но это очень быстро выйдет из-под контроля. Какой лучший способ спроектировать такую ​​систему?

Даниэль Каплан
источник
Лично я просто сохранил бы все экипированные предметы в списке и дал бы им реализовать какой-то общий интерфейс, который переносит объект, который вы изменяете, от объекта к объекту. «для каждого предмета изменить запланированную атаку», чтобы один предмет мог дублировать количество снарядов, один мог добавить его оттенок и изменить урон (поэтому, если один предмет сделал красные болты, а другой сделал желтым, у вас будет оранжевая атака после того, как оба модифицируют атаку) , Вы также можете просто иметь один общий класс предметов, у которого есть параметры, чтобы решить, как он изменяет запланированную атаку.
Бенджамин Дэнджер Джонсон
2
Я хотел бы отметить, что Кирби 64 использовал грубую силу и запрограммировал различные эффекты для всех возможных комбинаций способностей, так что это выполнимо.
Кевин

Ответы:

16

Вам совершенно не нужно вручную кодировать комбинации. Вместо этого вы можете сосредоточиться на свойствах, которые дает вам каждый элемент. Например, пункт А устанавливает Projectile=Fireball,Targetting=Homing. Предмет B устанавливает FireMode=ArcShot,Count=3. ArcShotЛогика отвечает за отправку из Countчисла Projectileэлементов в дуге.

Эти два элемента можно комбинировать с любыми другими элементами, которые свободно изменяют эти (или другие) свойства. Если вы добавите новый тип снаряда, он будет автоматически работать с ним ArcShot, а если вы добавите новый режим стрельбы, он автоматически будет работать с Fireballснарядами. Аналогично, Targettingэто свойство, которое устанавливает контроллер для снарядов, в то же время FireModeсоздает снаряды, поэтому их можно легко и тривиально комбинировать в любой комбинации, что имеет смысл.

Вы также можете установить зависимости свойств и тому подобное. Например, ArcShotтребует, чтобы у вас был поставщик Projectile(который может быть просто по умолчанию). Вы можете установить приоритеты так, чтобы, если у вас есть два активных элемента, которые предоставляют Projectileкод, знал, какой из них использовать. Или вы можете предоставить пользовательский интерфейс, чтобы позволить пользователю выбирать тип используемого снаряда, или просто потребовать от игрока снять высокоприоритетные предметы, которые ему не нужны, или использовать самый последний предмет и т. Д. Вы можете дополнительно разрешить систему несовместимостей. Например, так, что два предмета, которые просто модифицируются, Projectileне могут быть оснащены одновременно.

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

Шон Миддледич
источник
Небольшая гнида, но у вас, похоже, нет правильных свойств для примеров, которые вы используете. «Spoon Bender» добавляет самонаведения, а «Inner Eye» добавляет тройной выстрел. Ни добавить дуги, и оба слезы. Если вы используете произвольные свойства в своем примере для абстрагирования дизайна, было бы легче читать, если бы они не были введены в заблуждение. Я бы предпочел «Предмет А» и «Предмет Б» этому.
Daniel Kaplan
1
@tieTYT: Я обобщил имена и расширил пример, включив в него режимы прицеливания в дополнение к типам снарядов и режимам стрельбы. Никогда не играл в BoI более нескольких минут, поэтому у меня нет таких имен, как другие. :)
Шон Миддледич
Мне кажется, что сложная часть заключается в определении категорий свойств. Например: Targetting один, FireMode другой, Count может быть третьим ... или нет. Возможно, 3 снаряда могут быть огненным шаром, а 5 - гранатами, хотя это одно оружие.
Даниэль Каплан
@tieTYT: абсолютно. Конечно, вы можете расширить систему, допуская специальные комбинации или логику, но это должно быть скорее исключением, чем правилом. Оптимизировать для общего случая, а не углового случая.
Шон Мидлдич
Вот отличное видео о том, почему вы не правы в отношении процедурного программирования youtu.be/QM1iUe6IofM , а также способ описания данных, который вы описываете, отображает любые блоки if / else в отдельные наборы данных, по сути, ничего не делая для уменьшения количества особых сценариев, которые вам нужно запрограммировать, как вы должны запрограммировать их всех ...
RenaissanceProgrammer
8

Если вы используете язык ООП, это звучит как хорошее место для использования шаблона Decorator . Если вы хотите изменить способ атаки, просто украсьте ее соответствующим дополнением.

Crude c ++ Пример:

class AttackBehaviour
{
    /* other code */
    virtual void Attack(double angle);
};

class TearAttack: public AttackBehaviour
{
    /* other code */
    void Attack(double angle);
};

class TripleAttack: public AttackBehaviour
{
    /* other code */
    AttackBehaviour* baseAttackBehaviour;
    void Attack(double angle);
};

void TripleAttack::Attack(angle)
{
    baseAttackBehaviour->Attack(angle-30);
    baseAttackBehaviour->Attack(angle);
    baseAttackBehaviour->Attack(angle+30);
}

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

Морская миля
источник
Если я хочу изменить анимацию, почему это не для меня? Кроме того, что если я работаю на более функциональном языке? Вы знаете шаблон дизайна, который подходит для них?
Даниэль Каплан
Он более жесткий, потому что модификатор всегда будет вызывать Attackметод объекта, который он агрегирует. TripleAttackКласс не должен знать о TearAttackклассе. Если бы это было правдой, это привело бы к таким же головным болям, как и к else-ifблоку. Это означает, что любые анимации слез должны находиться внутри TearAttackBehaviourобъекта. Этот объект не знает (и не должен знать), что он был украшен TripleAttackобъектом. Результатом является то, что 3 анимации слезы идут независимо, потому что они независимы.
NauticalMile
Я с трудом объясняю это словами, если кто-то еще хочет нанести удар, будь моим гостем.
NauticalMile
Что касается реализации этого на более функциональном языке, я немного подумаю над этим и исправлю свой ответ, когда буду готов.
NauticalMile
1

Как фанат Биндинга Исаака, я тоже задавался вопросом, как сделать что-то подобное. Система в игре достаточно надежна, когда возникающее поведение возникает из-за комбинации эффектов (один, который приходит мне в голову, это получение зеркала, изгиб ложки, а некоторые усилители дальности приводят к закрученной, самонаводящейся слезе вокруг Исаака, стиль Магнето ). Огромное количество из них сделает блок «если» нецелесообразным.

Мой вывод заключается в том, что Исаак и его слезы - это две сущности в центре массивной Структуры Компонент-сущность . У сущностей есть некоторые базовые характеристики (скорость движения, жизнь, радиус действия, урон, спрайт и т. Д.), И каждый компонент должен иметь модификатор статистики и глагол.

В коде у Исаака и его слез будет каждый список, который будет содержать элементы интерфейса. Исаак будет иметь список вещей, которые подписываются на интерфейс IsaacMutator, и его слезы tearMutator. IsaacMutator будет иметь функции для изменения здоровья, скорости, диапазона, внешности и некоторых специальных глаголов Айзека. TearMutator был бы похожим. Один раз за игровой цикл Исаак будет проходить через все имеющиеся у него IsaacMutators, и все живые слезы тоже. Если следовать вашему английскому примеру, это будет выглядеть так:

Isaac has IsaacMutators:
--spoonbender which gives no stat change and: Tears are made homing
--MeatEater which give +1 health, +1 damage and: nothing
--MagicMirror which gives no stat change and: Tears are made reflecting

Tears have tearMutators:
--(depends on MeatEater) +1 damage and: nothing
--(depends on MagicMirror) no stat change and: +1 vector towards isaac
--(depnds on spoonbender) no stat change and: +1 vector towards enemytype

и так далее. Поскольку типы являются аддитивными, вы можете складывать, добавлять и удалять содержимое вашего сердца.

Kirbinator
источник
-5

Я думаю, что ваш путь работает лучше всего. Каждый из этих элементов дает условие: если их использовать вместе, они создают разные условия, тогда вам фактически потребуются все 3 возможных условия.

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

if spoon bender and the inner eye then new spoon bender inner eye

if spoon bender inner eye then ...
RenaissanceProgrammer
источник