Обсуждение архитектуры игрового приложения - предложения и варианты
— Здравствуйте, это сайт по MyLittlePony?
— Какие паттерны мне использовать при разработке приложения?
Собственно, есть вполне конкретная задача, над которой я рву бороду уже неделю.
Дано: простая игра, назовём её «Пони и пчела». Двумерная аркада, вид сверху, стрелочное управление, прям в духе начала 90-х :-)
Пони от пчелы убегает в пределах экранных координат, пчела следует за ней неутомимо с равной скоростью, как T-800.
Догоняет — жалит в круп, после N покусаний, игра заканчивается.
Соль игры — в бонусах, которые выпадают на игровой экран, и поедая которые, пони получает различные преимущества.
Например, бонус «скорость» — собрав его, пони ускоряется и отрывается от пчелы.
Бонус «неуязвимость» — пони на время становится пофиг на пчелу, словно после применения IDDQD.
Бонус «невидимость» — пчела на время перестаёт «видеть» пони и начинает двигаться хаотично.
И т.д.
Описание проблемы и вопрос по архитектуре под катом
Вот так выглядит простейшее решение:
Главная проблема — что видов бонусов может быть много, десятки, сотни видов.
И по сути, каждый введенный новый бонус требует помимо собственного кода действия бонуса, еще вносить исправления в код либо пчелы, либо пони (например, для реализации неуязвимости, придется явно вводить это в код пони или пчелы, а роль класса бонуса сведется только к разовой процедуре «вкл»).
Таким образом, лобовое решение приводит к тому, что число связей между бонусами и пони/пчелой растет линейно вместе с ростом числа бонусов.
Это ни разу не круто.
К тому же, в идеале, разработкой бонусов могут заниматься вообще другие люди, которые не могут и не должны править основные классы пони и пчелы.
Другой вариант реализации — когда бонусы становятся системой команд и управление игрой/рендером выполняется через последовательное выполнение всех активных бонусов, которые действуют в данный момент. Активно ускорение — оно изменит положение, активно бессмертие — откатит здоровье к базе, активна невидимость — случайно изменит координаты пчелы и т.д.
Плюс очевиден — для добавления бонуса, его нужно просто закодировать, и не лезть в код к пчеле и пони, к тому же, число связей снижено до минимума.
Минус — в один прекрасный момент, действие двух бонусов окажется конфликтным и конвеер команд разрушится.
Третий и наиболее годный (имхо) вариант — введение дополнительных объектов-менеджеров, которые управляют пони и пчелой, но каждый только в рамках своего типа действия. Один менеджер по движению, один по взаимодействию с пчелой, один для отрисовки и т.д. При этом, каждый бонус закрепляется за одним или нескольким менеджерами (например, бонус «Заморозка пчелы» обращается к менеджеру по движению (меняет скорость) и по отрисовке (добавляет лёд)) — и обращается не к пчеле или к пони, а к менеджерам, а те уже работают со своими объектами.
Плюсы тут тоже понятны — сотня связей разбивается на десять по десять, при введении нового бонуса пишем код для него и если нужно — только в тот менеджер, с которым бонус связан.
Минусы — общее усложнение архитектуры.
Еще варианты?
PS: Все эти приёмы имеют свои названия среди паттернов разработки, но я намеренно их не привожу, дабы не толкать вас по имеющимся путям. Возможно, чистый взгляд сможет породить чистое же решение.
— Какие паттерны мне использовать при разработке приложения?
Собственно, есть вполне конкретная задача, над которой я рву бороду уже неделю.
Дано: простая игра, назовём её «Пони и пчела». Двумерная аркада, вид сверху, стрелочное управление, прям в духе начала 90-х :-)
Пони от пчелы убегает в пределах экранных координат, пчела следует за ней неутомимо с равной скоростью, как T-800.
Догоняет — жалит в круп, после N покусаний, игра заканчивается.
Соль игры — в бонусах, которые выпадают на игровой экран, и поедая которые, пони получает различные преимущества.
Например, бонус «скорость» — собрав его, пони ускоряется и отрывается от пчелы.
Бонус «неуязвимость» — пони на время становится пофиг на пчелу, словно после применения IDDQD.
Бонус «невидимость» — пчела на время перестаёт «видеть» пони и начинает двигаться хаотично.
И т.д.
Описание проблемы и вопрос по архитектуре под катом
Вот так выглядит простейшее решение:
Главная проблема — что видов бонусов может быть много, десятки, сотни видов.
И по сути, каждый введенный новый бонус требует помимо собственного кода действия бонуса, еще вносить исправления в код либо пчелы, либо пони (например, для реализации неуязвимости, придется явно вводить это в код пони или пчелы, а роль класса бонуса сведется только к разовой процедуре «вкл»).
Таким образом, лобовое решение приводит к тому, что число связей между бонусами и пони/пчелой растет линейно вместе с ростом числа бонусов.
Это ни разу не круто.
К тому же, в идеале, разработкой бонусов могут заниматься вообще другие люди, которые не могут и не должны править основные классы пони и пчелы.
Другой вариант реализации — когда бонусы становятся системой команд и управление игрой/рендером выполняется через последовательное выполнение всех активных бонусов, которые действуют в данный момент. Активно ускорение — оно изменит положение, активно бессмертие — откатит здоровье к базе, активна невидимость — случайно изменит координаты пчелы и т.д.
Плюс очевиден — для добавления бонуса, его нужно просто закодировать, и не лезть в код к пчеле и пони, к тому же, число связей снижено до минимума.
Минус — в один прекрасный момент, действие двух бонусов окажется конфликтным и конвеер команд разрушится.
Третий и наиболее годный (имхо) вариант — введение дополнительных объектов-менеджеров, которые управляют пони и пчелой, но каждый только в рамках своего типа действия. Один менеджер по движению, один по взаимодействию с пчелой, один для отрисовки и т.д. При этом, каждый бонус закрепляется за одним или нескольким менеджерами (например, бонус «Заморозка пчелы» обращается к менеджеру по движению (меняет скорость) и по отрисовке (добавляет лёд)) — и обращается не к пчеле или к пони, а к менеджерам, а те уже работают со своими объектами.
Плюсы тут тоже понятны — сотня связей разбивается на десять по десять, при введении нового бонуса пишем код для него и если нужно — только в тот менеджер, с которым бонус связан.
Минусы — общее усложнение архитектуры.
Еще варианты?
PS: Все эти приёмы имеют свои названия среди паттернов разработки, но я намеренно их не привожу, дабы не толкать вас по имеющимся путям. Возможно, чистый взгляд сможет породить чистое же решение.
153 комментария
Лично моя практика показывает, что первый вариант (где все просто пишется в лоб прямо в коде реализации затрагиваемых вещей) идеален для малых-средних объемов (бонусов не более 10-15). Возможно какие-то наиболее распространенные эффекты (вроде увеличения скорости) можно частично привести ко второму варианту — в наружу вынести, например, базовую скорость движения, и каждый тик уточнять реальную скорость, которая так же торчит снаружи.
Если их количество грозит превысить второй десяток…
Окей, псевдокод на TSДекораторы как паттерн отпадают — грозит превратить все в трэш.
Третий вариант с менеджерами — по сути ты просто все говно перекладываешь из одного места в другое.
Если очень грубо, применяя ООП
Эффекты написаны отдельными классами, они явно накладываются на сущность путем добавления в ее массив (или другую фигню подобного рода), а сущность каждый тик применяет эффект. Тонкости могут варьироваться по ситуации, но я бы сейчас это написал так.
Некоторая проблема будет с невидимостью и прочей штукой, когда реальную разницу от эффекта ловит другая сторона. В GBS у меня это решалось «тегами», которые учитывались (бы) ИИ (или тем, что он тыкает) второй стороны. Второй вариант — раздувать интерфейс эффекта (или базовый класс, чтобы не писать заглушки), добавляя больше рычагов под разные случаи (по-моему он тоже используется в GBS). Первый вариант звучит более мерзко, но по-моему он более правильный, если ту же самую невидимость учитывать в какой-то общей функции поиска.
Хрен знает, есть ли у такой структуры какой-то паттерн.
Спасибо, вариант неплохой, обдумаю.
Это нормально и не костыль, в айзеке есть сочетания, которые, видимо, запрограммированы явно, иначе бы они просто не работали.
Первое и самое основное: нужно ли, на самом деле, такое колличество бонусов?
Есть ли возможность присвоить персонажам приватные переменные и через них менять их состояния в зависимости от бонуса? (я так делал, когда пытался в бонусы)
По условиям задачи, возможно вплоть до 100 типов бонусов совершенно разных действий.
Так и делается в большинстве случаев, это у меня первый вариант.
Но это имеет определенные архитектурные проблемы, поиск путей преодоления которых — цель моего исследования.
Даже если они и будут так делать, что мешает добавить к каждой команде поле, в котором указано, за что она отвечает, и если в очереди две команды, которые имеют одинаковое такое поле, то работать будет только первая/последняя/сильнейшая из них, а остальные удаляются?
Результат зависит от порядка применения, хотя команда остановки должна иметь приоритет над командой хаотичного движения.
Ввести ранг команды можно, но тогда придется держать в голове связи каждой команды с каждой, сверяя при добавлении нового бонуса его ранг с остальными.
Любая программа должна быть детерминированной, ещё до своего написания.
Где-то должен существовать документ, в котором должны быть прописаны исходы всех конфликтов бонусов — один отменяет другой, или применяется какой-то третий бонус. Или ещё что.
Далее делаем Вариант 2, но вместо СделатьВсё() вызываем ПреобразоватьСписокАктивныхБонусовТакЧтобыНеБылоКонфликтующихИспользуяЗнанияИзДиздока().
А потом для этого красивого списка бонусов вызываем СделатьВсё().
Звучит хорошо, но уже при 20 бонусах мы получаем огромный клубок зависимостей, и попытка добавить новый бонус приведет к необходимости пересмотреть всю архитектуру.
С одной стороны, это неплохо, с другой — разработчик бонусов будет страдать.
Это работа геймдизайнера, ему за это платят. За что-то же им платят ведь.
Было бы, конечно, очень круто, но нет, это так работает только с очень простыми программками. Любое серьезное приложение будет постоянно модифицироваться, а супер-серьезное (вроде достаточно сложной игры) еще и практически невозможно прописать досконально, учтя все возможные варианты.
В том и проблема. В идеальной архитектуре, if должен быть только для ветвления «да/нет», остальное должно решаться полиморфизмом и виртуальными методами.
Что ты подразумеваешь под конфлитным бонусом? Типа, один увеличивает лечение цели на 100%, а второй запрещает цели лечиться вообще? Ну, тут логика очень простая — какой бонус последним применяет свой эффект, тот и победитель.
В большинстве реализаций систем событий получаем гонку, то есть результат будет зависеть от чистого рандома. А такого не должно быть.
Всегда ли этот порядок будет сохраняться?
Будет ли первой функция А или функция Б?
Не обязательно нужна многопоточность, чтобы сымитировать гонку.
Ещё пример: одновременное применение бонуса, ограничивающего движение определённой скоростью, и бонуса, увеличивающего скорость.
суммеумножении всех множителей получается вполне логичный ноль, как бонусы ни крути) Со скоростью аналогичноНаглядное представление множителей от Образовача
А гигантский несуществующий галаго?
И самое главное, этого ли хотел автор? Ведь звуковик мог, например, повесить на всех гигантских существ звук грохота при ходьбе. И потом игрок с удивлением обнаруживает подгрохочивающего к нему галаго, который ему даже до пупка не дотягивает.
Но наверно в обоих случаях будет просто очень маленьким.
У каждого объекта есть компонент наложенных эффектов, который представляет из себя, собственно, список наложенных эффектов. Когда игрок жрет бонус, в этот список добавляется новый эффект.
В самих эффектах логики нет. Там есть тип эффекта и его значение, например, «скорость +2» или «скорость = 0».
Логика же есть в менеджере эффектов. Который каждый такт проходит по списку эффектов, применяет их к объекту, выкидывает прошедшие и т.д. Мы просто берем и перед этим проходом добавляем еще один проход, который разбирает такие конфликты.
Как именно — это уже творческое решение. Например, при наличии эффекта, который приравнивает скорость к нулю, остальные эффекты на скорость не учитываются. Или же скорость сначала становится равна нулю, а затем к ней применяются остальные эффекты на скорость. И т.д.
Но суть в том, что решать эту проблему должны не сами эффекты, а их менеджер, который по своей сути видит их все и создан, чтобы ими управлять.
Да, примерно похожее я и предложил:
То есть, допиленный третий вариант из моего поста?
NTFS, не делай так. Очень велик риск заблудиться в своем же коде через пару недель.
И не собирался. Если одному объекту нужен полный доступ к другому объекту (за исключением вопросов сериализации) — то пора пересматривать архитектуру.
Они вроде, как я понял, отличаются только внешним видом и интеллектом.
Так может стоит их сделать одним и тем же видом объектов?
Тогда связей, как минимум будет меньше.
«Движимая сущность» < — «Программный интерфейс управления сущностью» < — «Считыватель ввода игрока»
Пчела:
«Движимая сущность» < — «Программный интерфейс управления сущностью» < — «Искусственный интеллект»
Такое не прокатит?
У них у обоих есть некий контроллер поведения. Разница исключительно в том, что у пчелки это ИИ (то есть, набор скриптов), а у поньки это клава. Все, что нужно, это дать им однотипный элемент с функцией «начать движение», но у пчелки сделать внутри него обращение к скриптам, а у поньки — опрос клавиатуры.
Ну давай посмотрим, что общего у пони и пчелы.
1) Система координат на экране [X;Y] (в мире, если игра не одноэкранная).
2) Система вывода (и то, спрайт уходит в графическую библиотеку, непосредственно в логике игры прямого обращения к графике/рендеру нет).
Всё? Всё.
Даже скорости там по-разному обрабатываются — у одной задаются точным значением, у другой вычисляются по синусам/косинусам, дабы преследовать пони.
Иначе есть риск передать пчелу как пони в какой-то метод и долго страдать потом.
Мне не приходит в голову ни один сценарий, где эти сущности могли бы являться аргументом, и чтобы при этом было критично различие в том, что одна из них управляется игроком.
Если надо, могу описать, как внятно делается ее реализация.
Но спасибо, что напомнил.
А если… Сделать объекты пони и пчела, так сказать, мета-объектами. Суть: это контейнер + локальная система сообщений + менеджер бонусов. Контейнер хранит простые объекты-куски, отвечающие за одну простую задачу каждый (обработка кнопок, движение, таймеры, звук, отрисовка, ещё что-то), система сообщений связывает их (нажал кнопку — обработчик объекта «пони» сгенерировал сообщение — оно перехватилось заинтересованными объектами-кусками — есть реакция). При взятии бонуса дефолтный (или предыдущий) объект-кусок, на который влияет бонус, на лету подменяется способным реализовать этот бонус. Менеджер следит за временем действия бонуса, производит переключения в своём объекте и обрабатывает коллизии/конфликты бонусов. Влияние на другой объект (пони на пчелу и наоборот) можно провести через систему сообщений, дав ей возможность межобъектного общения.
Преимущество, как мне видится в разделении реализации. Отдельно — основной класс, отдельно — объекты-куски, дающие эффекты, отдельно — обработка взаимодействия бонусов. Минус — в многослойной структуре, которую ещё надо реализовать и отладить.
Просто видится взаимодействие таких мета-объектов на нескольких уровнях. Привычное с движком и через движок, внутреннее между компонентами, горизонтальное между системами сообщений разных объектов, горизонтальное между менеджерами бонусов и компонентов (возможно туннелем через систему сообщений, но всё же). Как бы не наткнуться в один прекрасный момент на загадку, какая из черепашек пиздит.
Иными словами, системы не могут работать одновременно. Они работают последовательно, и в этом, собственно, заключены простота и изящество конструкции. Логировать ее тоже очень легко, ты сможешь сразу понять, какая именно система выдает проблему и на основе каких данных.
В общем, там нужно некоторое время, чтобы впилить в концепт, но он реально значительно проще, чем типичное ООП.
В каком-нибудь Unity делалось бы именно так, только эти компоненты были бы не в менеджерах, а в самих управляемых объектах, и они обращались бы к общему состоянию игры, чтобы понять, какие бонусы сейчас действуют.
Минус — обращение к состоянию игры происходит каждый кадр (ну, если не мудрить с кешированием), плюсы — код тупой как валенок и его не очень много.
Оптимизация на данном этапе меня не беспокоит, при гигагерцовых процессорах и 4-8 Гб ОЗУ на типовом ПК можно делать как есть.
Эх, не бывать мне программистом.
Отсутствие личной гостиницы в Сочи и дома в Туапсеобщая кривизна лобового решения.Просто собираю умные мысли в потоке.
Я бы на чем-то типа политик и их списков делал, которые в свою очередь в древовидные струкутры строятся.
для начала базовый список всех типов объектов что у тебя в игре могут быть — пони, пчелы, деревья, камни — все что хоть как-то может взаимодействовать. Потом такой же список для всех возможных их свойств (бонусы также свойства)- здоровье, мана, скорость, сила, вес, цвет… Потом начинаешь объединять в группы, какие тебе нужны, и эти группы привязывать к объектам. Скажем группа «базовая» имеет цвет и вес. От нее цепляем группу «пони» — добавляем здоровье, силу и скорость, еще выше группа «единороги» — добавляем ману. Потом если вдруг понадобитя уберкамень с маной — не вопрос, создаем группу «уберкамни», зацепленную от «базовая» + «мана»
Если любишь бдсм, можешь это прямо на дельфях/плюсах сделать… но православней сразу скриптвое что-то встроить…
Делаю UI на Qt+OpenGL, логику основных объектов на C++, а бонусы, конечно, будут жить в QtScript на JS-подобном языке. Это снимает часть проблем с расширением и поддержкой, но все равно вопрос, кто и что должен дергать, пока не решен.
Потом гляди на этот список и думай, а вперлись ли тебе эти десятки человекочасов, когда ты будешь писать все вот эти обертки к расширениям, а потом еще раз писать и сами бонусы. Еще и на скриптовом языке (задай себе второй вопрос, будет ли их кто-то кроме тебя писать), который тоже должен что-то дергать.
В постановке — бонусов может быть over9000 и писать их может кто угодно.
В принципе, результат исследования в виде «это невозможно, пнх» — тоже принимается. Но хотелось бы решить задачу.
Я обычно отвечаю «Это возможно, но вы не захотите столько ждать реализации».
Понятия не имею, как происходит внедрение скриптовых языков, и нужно ли как-то прописывать область видимости.
В Qt уже встроен божественный механизм интеграции JavaScript, когда можно вызывать нативные методы как скриптовые.
Зачем эта камасутра? Проще тупо написать список комплектующих, а при старте программы считать из файла прототипы, где указано, какие из этих комплектующих и с какими параметрами будут у каждого типового объекта. Ну и потом при необходимости создать объект просто копировать прототип.
Вся вот эта группировка нужна при традиционном ООП. А с компонентами она тебе зачем?
(Вот и ещё один повод объединить классы пчелы и пони — а потом ещё так же можно будет туда ввести других акторов, там, не знаю, чейнджлингов, бризи,
озабоченных барановнет, лол, это из другой игры и т.п.)1. Юниту инстанцируется узел с кодом эффекта
2. Всё
И да, юниты при этом имеют общую базу, которая слушает сигналы потомков, вроде «стой здесь»/«иди туда». Просто персонажу игрока выдаётся контроллер, который слушает пользовательский ввод, а ботам выдаются контроллеры, которые мониторят сцену.
В твоём случае бонусы похожи на арена-эффекты, но, поскольку действуют они избирательно, разумнее вешать инстанс бонуса на каждого юнита отдельно.
Реализовать их обработку можно, например, как цепочку мидлвар: каждая получает состояние и возвращает его в изменённом виде.
А потом десяток кубических зебр насмерть вешают ваши древние ведроиды.
гов..особенностях движка.То есть, как бы я делал: допустим, у пчелы есть методы Spawn (вызывается при создании пчелы), Pick (вызывается при встрече со съедобным бонусом), Chase (вызывается каждый тик и перемещает пчелу по конкретному алгоритму), Attack (вызывается при контакте хитбоксов пчелы и пони), Die (вызывается при уничтожении пчелы); у пони Spawn, Pick, Move (вызывается каждый тик и перемещает пони по командам с клавы), Die. С каждым стейтом связан упорядоченный список обработчиков (например, они отрисовывают их на экране, меняют их свойства — координаты, здоровье, статические характеристики — скорость там и т.п.). У каждого бонуса есть набор обработчиков на какие-либо из этих методов. Когда бонус берётся — обработчик метода Pick добавляет его в список (если, конечно, обработчик этого метода не отключен другим бонусом — например, бонус «IDCLIP» не позволяет подбирать другие бонусы — см. ниже) и удаляет конфликтующие с ним (у каждого бонуса должен быть список конфиликтующих). Причём обработчики бонусов могут влиять друг на друга в зависимости от порядка подбора.
Например, рассмотрим такие бонусы:
Мы видим, что если сначала взять лекарство, а потом яд, то пони будет отравлен, т.к. обработчик из Poison будет вызываться после оного из Medicine; если же наоборот, то лекарство вылечит пони (или пчелу), поскольку обработчик из Medicine будет вызываться позже, и новое здоровье останется тем же, которым было. А вот взятие бонуса «IDDQD», во-первых, удалит весь яд из организма пони, т.к. он конфликтует с ним, а во-вторых, при попытке взять яд, будет обламывать это взятие (т.е. добавление обработчиков в списки, при этом всё равно убирая взятый яд с земли).
Можно добавить также к каждому бонусу параметр Duration, по истечении которого после Pick все его обработчики будут удаляться из списков.
P.S. возможно, это всё какой-то уже давно поименованный паттерн программирования, но я с ним встретился, когда писал моды для DOOM и не знаю его названия =)
Инквизиция уже вызвана. Молитесь своим еретическим богам. Екстерминатус неизбежен.
Теперь о совершенно другом.
Есть КоньЛетающий (Конь) {
//Здесь поля коня, описывающие все его внутренние состояния, включая механизмы для
//фунциклирования бонусов. Например:
//Есть бонус «Суперскорость» — дающий РДэш-класс ускорение. Следовательно должно быть
//поле «множитель скорости», имеющее значение по умолчанию 1. Бонус влияющий на скорость
//увеличивает это поле при получении бонуса, и уменьшает на туже величину при снятии.
…
МножительСкорости: число;//Используется для расчета скорости движения персонажа
//TODO — перенести в Персонаж, чтоб Пчолы тоже могли использовать бонус СуперСкорость.
…
//Список действующих бонусов. Они добавляются в этот список при подборе и исключаются
//при снятии
…
СписокАктБонусов: СписокОбьектов;
…
}
Есть БонусСуперскорость(Бонус) {
Процедура АктивацияБонуса; МойХост.МножительСкорости++;
Процедура СнятиеБонуса; МойХост.МножительСкорости--;
}
p.s. Вопросы, ответы, жалобы и угрозы пишите на [email protected]
//И по сути, каждый введенный новый бонус требует помимо собственного кода действия бонуса, еще вносить исправления в код либо пчелы, либо пони (например, для
Есть Персонаж (Существо){...}
Есть Существо (ИгровойОбьект) {
…
Хиты: число;
…
Неуязвим: логическое;
Процедура ПолучитьУрон(аХитовСнято: число) {
Если Нет(Неуязвим) Тогда
Хиты:=Хиты-аХитовСнято; }
…
}
Есть БонусНеуязвимость(Бонус) {
Процедура АктивацияБонуса; { МойХост.Неуязвим:=Правда;}
Процедура Тик;{ МойХост.Неуязвим:=Правда; }
//исправление бага№2077100500 — при наложенном заклинании ПьянойБлакДжакВсеПох,
//снятие вызывает нарушение заклинания/бонуса
Процедура СнятиеБонуса; МойХост.Неуязвим:=Ложь;
}
Примеры выше годные, но по сути, лобовое решение.
Потому что
требует наличия поля «Неузвим» в классе хоста. А его могло исходно и не быть — не предполагали, что объект может быть неуязвимым.
МойХост.Неуязвим можно делать и не полем класса, но тебе нужно предусмотреть у самого Хоста, что возможно состояние параметров «защита» и еще чего-то там, чтобы урон не проходил совсем. То есть, чтобы бонус мог что-то сделать, тебе в любом случае нужно предусмотреть, чтобы Хост это в принципе мог осуществить. Грубо говоря, Хост все-таки должен уметь все. Единственное место, где решаются все конфликты, единственный клубок, лежащий в строго одном месте.
Это не значит, что бонусы беспомощны. Ты волен писать, например, произвольный ИИ в коде бонуса, тебе просто нужно предусмотреть у Хоста, чтобы «думать» за него мог кто-то другой.
Но если ты будешь разбрасывать реализации ровным слоем между Хостом и бонусами, готовься к тому, что разные сочетания этих бонусов (и разных Хостов, раз уж ты решил по-разному писать игрока и пчел) будут (обязательно будут, неважно, как ты все напишешь, мы так устроены) стрелять багами, и физический разброс кода между системами никак не упростит твою задачу.
Допустим, бонус, который дает неуязвимость и какой-нибудь бонус, который по какой-то причине может убить тебя вот прямо сейчас. Если все решается на уровне хоста, то мы элементарно либо не даем сдохнуть при неуязвимости, либо все-таки даем, либо еще что-то делаем. Если на уровне бонуса, то тебе нужно в каком-то из этих двух ребят добавлять проверку на его «противоположность». И это их только 2, а если 20? В каждый прописывать каждого? Чот такое. Антипаттерн прям.
Меня прельщает компонентная модель, которую рекламировал оратор выше, в принципе, она как раз это сможет решить. Если правильно применить, конечно.
Поле imba реально так и называлось
Компиляция завершиться с ошибкой.
Технически нет, если делать подобные статусы добавляемым элементом.
// //исправление бага№2077100500 — при наложенном заклинании ПьянойБлакДжакВсеПох,
// //снятие вызывает нарушение заклинания/бонуса
— есличо это был контрпример, не делайте так никогда.
Можно через командную консоль даже выползти за текстуры, наглядно видно будет, что радужная дрянь с целью затащить в своё карманное измерение только так и ползёт, и спасу от неё нет ;)
А жабистские извраты с ООП-паттернами здесь помогут примерно никак, если стоит цель запилить сколь-либо осмысленную базовую игру, а не очередной мегарасширяемый майнтест, где модами можно творить чо угодно, зато самой игры толком нет, бгг. Как единый™ обработчик команд поможет, какие команды он будет выполнять? Куда они должны вклиниваться, в каком порядке (это важно!)? Как разруливать конфликтные ситуации, когда один бонус должен изменять или корректировать поведение другого (самый цимес — не зная о его существовании!)? Да никак, хрен вам, а не красивая архитектура :P
АЪАЪАЪАЪАЪА?
Не говоря уж о том, что влияние на поведение бонусов может выходить за рамки глобально видимых параметров.
Так пропиши их по умолчанию дурик.
На основе факта наличия того бонуса НА который влияют.
Ну, если руки из жопы растут, то тут уже никакая самая совершенная и продуманная архитектура не поможет.
Классы же, дурик!
Это специфический случай, который подходит для ситуаций когда принцип действия прочих бонусов не важен, например «сбросить все бонусы» или «увеличить/уменьшить время действия наложенных эффектов» в таком духе.
Очень даже важен. Вот призывает один бонус пчёл-соратников, например, а другой бонус возьмёт да случайно их во враждебных превратит, ибо ассумит, что пчела одна и может быть только враждебной. Штоделотб?
Спойлер
Не вижу проблем.
Архитектура говнокод, первый бонус должен чистить конкретных пчел, ни на что не оглядываясь (кроме того случая, когда ему сказали «не чисти»).
Не вижу ни одной причины, чтобы бонус 2 не работал во время бонуса 1, иначе балансно говно.
Уточняю: не стоит задача «сочетания бонусов должны вести себя строго конкретным образом». Стоит задача «сочетание бонусов должно вести себя одинаково независимо от того, в каком порядке они применены».
Это нормально для игр с сотнями бонусов. Разнообразное поведение с интересными ситуациями — то, что от них ожидается.
Интерпретируй это, как удвоение длительности бонуса 2. Что рискует в некоторых условиях сделать его бесконечным. Но даже если и надо — ниже уже написали, как.
А ты думал, что в сказку попал?) Нет, конкретное взаимодействие между конкретными бонусами должно быть только в рамках исключения, система должна быть создана так, чтобы суметь 99% разрулить сама. Иначе ты будешь все время прописывать тысячи сценариев.
Глюки — отклонения от исходного плана.
Я тебе баланс поясняю, а не за логику.
Ага, и в моем саааааааамом первом комментарии уже дана архитектура, которая позволит тебе чекать другие эффекты, но при этом не позволит манипулировать ими. Ваще хз, что вы тут обсуждаете.
Спроси тех, кто в айзека играет.
Если ты сам себе не хозяин, то я ничем не помогу.
Вообще-то ещё в исходном условии задачи указано:
1. Это относится к активации бонуса, а не к его работе
2. Нет формализации того, как бонусы живут и истекают
Но допустим.
Рассмотрим кейс в рамках реализации через цепочку мидлвар.
Напомню, реализация заключается в том, что у каждого нита в игре есть состояние и список активных бонусов.
На каждом кадре логики (физики) берётся текущее состояние (которое включает направление движения, скорость и пр. параметры) и последовательно скармливается методу (например) `process` бонуса. Метод принимает состояние и возвращает его модифицированный вариант (в соответствии со своей логикой). После чего новое состояние скармливается следующему бонусу. Итоговое состояние применяется к юниту.
Истекание бонусов реализуем как их внутренннее состояние. То есть бонус знает. на сколько кадров его осталось и при каждой активации может уменьшить свой срок жизни или нет. Как удаляются истёкшие бонусы — не рассматриваем.
Итак, что мы имеем. Оба бонуса модифицируют damage resistance. Один присваивает ему значение 1, другой, какое-то значение меньше 1, но больше 0.
Значит, в логику таких бонусов следует добавить проверку: если DR, который он даёт, меньше того, которое уже есть, значит нужно вернуть не модифицированное состояние и не уменьшать свой срок жизни.
Что, если, например, Бонус2 существует в двух видах: один даёт DR=0.7, другой DR=0.3? Объединить их имеет смысл, при этом если первый вид уже активен, то DR будет выше порога.
Также Бонус1 может иметь спадающий эффект, и в таком случае нельзя точно определить по DR, активен он или нет — начальное значение знает только Бонус1. Также одновременное применение такого спадающего бонуса может привести к вышеописанному АЪАЪАЪАЪА, если бонусы меняют DR абсолютным образом, и к зашкаливанию, если относительным.