Обсуждение архитектуры игрового приложения - предложения и варианты
— Здравствуйте, это сайт по MyLittlePony?
— Какие паттерны мне использовать при разработке приложения?
Собственно, есть вполне конкретная задача, над которой я рву бороду уже неделю.
Дано: простая игра, назовём её «Пони и пчела». Двумерная аркада, вид сверху, стрелочное управление, прям в духе начала 90-х :-)
Пони от пчелы убегает в пределах экранных координат, пчела следует за ней неутомимо с равной скоростью, как T-800.
Догоняет — жалит в круп, после N покусаний, игра заканчивается.
Соль игры — в бонусах, которые выпадают на игровой экран, и поедая которые, пони получает различные преимущества.
Например, бонус «скорость» — собрав его, пони ускоряется и отрывается от пчелы.
Бонус «неуязвимость» — пони на время становится пофиг на пчелу, словно после применения IDDQD.
Бонус «невидимость» — пчела на время перестаёт «видеть» пони и начинает двигаться хаотично.
И т.д.
Описание проблемы и вопрос по архитектуре под катом
Вот так выглядит простейшее решение:

Главная проблема — что видов бонусов может быть много, десятки, сотни видов.
И по сути, каждый введенный новый бонус требует помимо собственного кода действия бонуса, еще вносить исправления в код либо пчелы, либо пони (например, для реализации неуязвимости, придется явно вводить это в код пони или пчелы, а роль класса бонуса сведется только к разовой процедуре «вкл»).
Таким образом, лобовое решение приводит к тому, что число связей между бонусами и пони/пчелой растет линейно вместе с ростом числа бонусов.
Это ни разу не круто.
К тому же, в идеале, разработкой бонусов могут заниматься вообще другие люди, которые не могут и не должны править основные классы пони и пчелы.
Другой вариант реализации — когда бонусы становятся системой команд и управление игрой/рендером выполняется через последовательное выполнение всех активных бонусов, которые действуют в данный момент. Активно ускорение — оно изменит положение, активно бессмертие — откатит здоровье к базе, активна невидимость — случайно изменит координаты пчелы и т.д.

Плюс очевиден — для добавления бонуса, его нужно просто закодировать, и не лезть в код к пчеле и пони, к тому же, число связей снижено до минимума.
Минус — в один прекрасный момент, действие двух бонусов окажется конфликтным и конвеер команд разрушится.
Третий и наиболее годный (имхо) вариант — введение дополнительных объектов-менеджеров, которые управляют пони и пчелой, но каждый только в рамках своего типа действия. Один менеджер по движению, один по взаимодействию с пчелой, один для отрисовки и т.д. При этом, каждый бонус закрепляется за одним или нескольким менеджерами (например, бонус «Заморозка пчелы» обращается к менеджеру по движению (меняет скорость) и по отрисовке (добавляет лёд)) — и обращается не к пчеле или к пони, а к менеджерам, а те уже работают со своими объектами.

Плюсы тут тоже понятны — сотня связей разбивается на десять по десять, при введении нового бонуса пишем код для него и если нужно — только в тот менеджер, с которым бонус связан.
Минусы — общее усложнение архитектуры.
Еще варианты?
PS: Все эти приёмы имеют свои названия среди паттернов разработки, но я намеренно их не привожу, дабы не толкать вас по имеющимся путям. Возможно, чистый взгляд сможет породить чистое же решение.
— Какие паттерны мне использовать при разработке приложения?
Собственно, есть вполне конкретная задача, над которой я рву бороду уже неделю.
Дано: простая игра, назовём её «Пони и пчела». Двумерная аркада, вид сверху, стрелочное управление, прям в духе начала 90-х :-)
Пони от пчелы убегает в пределах экранных координат, пчела следует за ней неутомимо с равной скоростью, как T-800.
Догоняет — жалит в круп, после N покусаний, игра заканчивается.
Соль игры — в бонусах, которые выпадают на игровой экран, и поедая которые, пони получает различные преимущества.
Например, бонус «скорость» — собрав его, пони ускоряется и отрывается от пчелы.
Бонус «неуязвимость» — пони на время становится пофиг на пчелу, словно после применения IDDQD.
Бонус «невидимость» — пчела на время перестаёт «видеть» пони и начинает двигаться хаотично.
И т.д.
Описание проблемы и вопрос по архитектуре под катом
Вот так выглядит простейшее решение:

Главная проблема — что видов бонусов может быть много, десятки, сотни видов.
И по сути, каждый введенный новый бонус требует помимо собственного кода действия бонуса, еще вносить исправления в код либо пчелы, либо пони (например, для реализации неуязвимости, придется явно вводить это в код пони или пчелы, а роль класса бонуса сведется только к разовой процедуре «вкл»).
Таким образом, лобовое решение приводит к тому, что число связей между бонусами и пони/пчелой растет линейно вместе с ростом числа бонусов.
Это ни разу не круто.
К тому же, в идеале, разработкой бонусов могут заниматься вообще другие люди, которые не могут и не должны править основные классы пони и пчелы.
Другой вариант реализации — когда бонусы становятся системой команд и управление игрой/рендером выполняется через последовательное выполнение всех активных бонусов, которые действуют в данный момент. Активно ускорение — оно изменит положение, активно бессмертие — откатит здоровье к базе, активна невидимость — случайно изменит координаты пчелы и т.д.

Плюс очевиден — для добавления бонуса, его нужно просто закодировать, и не лезть в код к пчеле и пони, к тому же, число связей снижено до минимума.
Минус — в один прекрасный момент, действие двух бонусов окажется конфликтным и конвеер команд разрушится.
Третий и наиболее годный (имхо) вариант — введение дополнительных объектов-менеджеров, которые управляют пони и пчелой, но каждый только в рамках своего типа действия. Один менеджер по движению, один по взаимодействию с пчелой, один для отрисовки и т.д. При этом, каждый бонус закрепляется за одним или нескольким менеджерами (например, бонус «Заморозка пчелы» обращается к менеджеру по движению (меняет скорость) и по отрисовке (добавляет лёд)) — и обращается не к пчеле или к пони, а к менеджерам, а те уже работают со своими объектами.

Плюсы тут тоже понятны — сотня связей разбивается на десять по десять, при введении нового бонуса пишем код для него и если нужно — только в тот менеджер, с которым бонус связан.
Минусы — общее усложнение архитектуры.
Еще варианты?
PS: Все эти приёмы имеют свои названия среди паттернов разработки, но я намеренно их не привожу, дабы не толкать вас по имеющимся путям. Возможно, чистый взгляд сможет породить чистое же решение.
153 комментария
Лично моя практика показывает, что первый вариант (где все просто пишется в лоб прямо в коде реализации затрагиваемых вещей) идеален для малых-средних объемов (бонусов не более 10-15). Возможно какие-то наиболее распространенные эффекты (вроде увеличения скорости) можно частично привести ко второму варианту — в наружу вынести, например, базовую скорость движения, и каждый тик уточнять реальную скорость, которая так же торчит снаружи.
Если их количество грозит превысить второй десяток…
Окей, псевдокод на TSДекораторы как паттерн отпадают — грозит превратить все в трэш.
Третий вариант с менеджерами — по сути ты просто все говно перекладываешь из одного места в другое.
Если очень грубо, применяя ООП
class EffectableEntity { effects: Effects[]; // или Set<Effects> если они не могут дублироваться update() { this.effects.forEach(effect => effect.apply(this)); } } interface Effect { apply(entity: EffectableEntity): void; }Эффекты написаны отдельными классами, они явно накладываются на сущность путем добавления в ее массив (или другую фигню подобного рода), а сущность каждый тик применяет эффект. Тонкости могут варьироваться по ситуации, но я бы сейчас это написал так.
Некоторая проблема будет с невидимостью и прочей штукой, когда реальную разницу от эффекта ловит другая сторона. В 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» не позволяет подбирать другие бонусы — см. ниже) и удаляет конфликтующие с ним (у каждого бонуса должен быть список конфиликтующих). Причём обработчики бонусов могут влиять друг на друга в зависимости от порядка подбора.
Например, рассмотрим такие бонусы:
Bonus "Medicine" { Pony::Move: health_next = nealth_now Bee::Chase: health_next = nealth_now } Bonus "Poison" { Pony::Move: health_next = nealth_now - 1 // just for fun: Bees are tolerant to this poison, so no Bee::Chase handler } Bonus "IDDQD" { Conflicts = {"Poison"} *::Pick: if (pick_object == "Poison") { Fail_Pick(leave_on_ground = false) } } Bonus "IDCLIP" { *::Pick: Fail_Pick(leave_on_ground = true) }Мы видим, что если сначала взять лекарство, а потом яд, то пони будет отравлен, т.к. обработчик из Poison будет вызываться после оного из Medicine; если же наоборот, то лекарство вылечит пони (или пчелу), поскольку обработчик из Medicine будет вызываться позже, и новое здоровье останется тем же, которым было. А вот взятие бонуса «IDDQD», во-первых, удалит весь яд из организма пони, т.к. он конфликтует с ним, а во-вторых, при попытке взять яд, будет обламывать это взятие (т.е. добавление обработчиков в списки, при этом всё равно убирая взятый яд с земли).
Можно добавить также к каждому бонусу параметр Duration, по истечении которого после Pick все его обработчики будут удаляться из списков.
P.S. возможно, это всё какой-то уже давно поименованный паттерн программирования, но я с ним встретился, когда писал моды для DOOM и не знаю его названия =)
Инквизиция уже вызвана. Молитесь своим еретическим богам. Екстерминатус неизбежен.
Теперь о совершенно другом.
Есть КоньЛетающий (Конь) {
//Здесь поля коня, описывающие все его внутренние состояния, включая механизмы для
//фунциклирования бонусов. Например:
//Есть бонус «Суперскорость» — дающий РДэш-класс ускорение. Следовательно должно быть
//поле «множитель скорости», имеющее значение по умолчанию 1. Бонус влияющий на скорость
//увеличивает это поле при получении бонуса, и уменьшает на туже величину при снятии.
…
МножительСкорости: число;//Используется для расчета скорости движения персонажа
//TODO — перенести в Персонаж, чтоб Пчолы тоже могли использовать бонус СуперСкорость.
…
//Список действующих бонусов. Они добавляются в этот список при подборе и исключаются
//при снятии
…
СписокАктБонусов: СписокОбьектов;
…
}
Есть БонусСуперскорость(Бонус) {
Процедура АктивацияБонуса; МойХост.МножительСкорости++;
Процедура СнятиеБонуса; МойХост.МножительСкорости--;
}
p.s. Вопросы, ответы, жалобы и угрозы пишите на sales@koniipcholy.com
//И по сути, каждый введенный новый бонус требует помимо собственного кода действия бонуса, еще вносить исправления в код либо пчелы, либо пони (например, для
Есть Персонаж (Существо){...}
Есть Существо (ИгровойОбьект) {
…
Хиты: число;
…
Неуязвим: логическое;
Процедура ПолучитьУрон(аХитовСнято: число) {
Если Нет(Неуязвим) Тогда
Хиты:=Хиты-аХитовСнято; }
…
}
Есть БонусНеуязвимость(Бонус) {
Процедура АктивацияБонуса; { МойХост.Неуязвим:=Правда;}
Процедура Тик;{ МойХост.Неуязвим:=Правда; }
//исправление бага№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 абсолютным образом, и к зашкаливанию, если относительным.