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


18

Рассмотрим карточную игру, такую ​​как Hearthstone .

Существуют сотни карт, которые делают самые разные вещи, некоторые из которых уникальны даже для одной карты! Например, есть карта (называемая Ноздорму), которая уменьшает ход игрока до 15 секунд!

Когда у вас есть такое большое количество потенциальных эффектов, как вы избегаете магических чисел и одноразовых проверок всего кода? Как избежать метода "Check_Nozdormu_In_Play" в классе PlayerTurnTime? И как можно организовать код таким образом, чтобы, когда вы добавляете еще больше эффектов, вам не нужно реорганизовывать базовые системы для поддержки того, что им никогда раньше не приходилось поддерживать?


Это действительно проблема с производительностью? Я имею в виду, что вы можете делать сумасшедшие вещи с современными процессорами почти
мгновенно

11
Кто сказал что-нибудь о проблемах производительности? Основная проблема, которую я вижу, заключается в том, что постоянно приходится корректировать весь код каждый раз, когда вы делаете новую карту.
Джоккинг

2
поэтому добавьте язык сценариев и сценарий каждой карты.
Яри ​​Комппа

1
Нет времени, чтобы сделать правильный ответ, но вместо того, чтобы, например, проверять Ноздорму и 15-секундную настройку внутри кода класса «PlayerTurnTime», который обрабатывает ходы игрока, вы можете кодировать класс «PlayerTurnTime», чтобы вызвать [class-, если вы хотите ] функция поставляется снаружи в определенных точках. Тогда код карты Ноздорму (и все другие карты, которые должны влиять на ту же позицию) может реализовать функцию для этой корректировки и внедрить эту функцию в класс PlayerTurnTime, когда это необходимо. Возможно, было бы полезно прочитать о шаблоне стратегии и внедрении зависимостей из классической книги «Шаблоны проектирования»
Peteris

2
В какой - то момент я должен задаться вопросом , при добавлении Времнной проверки соответствующих битов кода является самым простым решением.
user253751

Ответы:


12

Вы изучили системы компонентов сущностей и стратегии обмена сообщениями о событиях?

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

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

Затем вы просто убедитесь, что в вашей игре нет магических чисел по умолчанию. Убедитесь, что все, что можно изменить, является переменной, управляемой данными, а не жестко заданными значениями по умолчанию с переменными, используемыми для любых исключений.

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


2
«Убедитесь, что все, что можно изменить, является переменной, управляемой данными, а не жестко закодированными значениями по умолчанию с переменными, используемыми для любых исключений». - О, мне это довольно нравится. Это очень помогает, я думаю!
Мечтатель Соболь

Не могли бы вы уточнить, «применить их постоянные эффекты»? Разве подписка на turnStarted с последующим изменением значения длины не сделает код неисправимым и, что еще хуже, приведет к противоречивым результатам (при взаимодействии между похожими эффектами)?
Wondra

Только для подписчиков, которые предполагают любой промежуток времени. Вы должны тщательно моделировать. Может быть хорошо, чтобы текущее время хода отличалось от времени хода игрока. PTT будет проверен, чтобы создать новый ход. CTT можно проверить по карточкам. Если эффект должен увеличить текущее время, пользовательский интерфейс таймера должен, естественно, следовать его примеру, если он не имеет состояния.
RobStone

Чтобы лучше ответить на вопрос. Ничто иное не хранит время поворота или что-то основанное на этом. Всегда проверяйте это.
RobStone

11

RobStone находится на правильном пути, но я хотел уточнить, поскольку именно это я и сделал, когда писал «Dungeon Ho!», Roguelike, в котором была очень сложная система эффектов для оружия и заклинаний.

К каждой карте должен быть прикреплен набор эффектов, определенных таким образом, чтобы она могла указывать, что это за эффект, на что он нацелен, как и на какой срок. Например, эффект «урона противника» может выглядеть примерно так;

Effect type: deal damage (enumeration, string, what-have-you)
Effect amount: 20
Source: my weapon
Target: opponent
Effect Cost: 20
Cost Type: Mana

Затем, когда эффект срабатывает, имейте общую процедуру обработки эффекта. Как идиот, я использовал огромный оператор case / switch:

switch (effect_type)
{
     case DAMAGE:

     break;
}

Но гораздо лучший и более модульный способ сделать это - через полиморфизм. Создайте класс Effect, который охватывает все эти данные, создайте подкласс для каждого типа эффекта, а затем переопределите этот класс для метода onExecute (), специфичного для этого класса.

class Effect
{
    Object source;
    int amount;

    public void onExecute(Object target)
    {
          // Do nothing
    }
}

class DamageEffect extends Effect
{
    public void onExecute(Object target)
    {
          target.health -= amount;
    }
}

Таким образом, у нас будет базовый класс Effect, а затем класс DamageEffect с методом onExecute (), поэтому в нашем коде обработки мы просто перейдем;

Effect effect = card.getActiveEffect();

effect.onExecute();

Чтобы понять, что находится в игре, нужно создать вектор / массив / связанный список / и т. Д. активных эффектов (типа Effect, базовый класс), прикрепленных к любому объекту (включая игровое поле / «игру»), поэтому вместо того, чтобы проверять, находится ли конкретный эффект в игре, вы просто просматриваете все эффекты, прикрепленные к объект (ы), и пусть они выполняются. Если эффект не прикреплен к объекту, он не находится в игре.

Effect effect;

for (int o = 0; o < objects.length; o++)
{
    for (int e = 0; e < objects[o].effects.length; e++)
    {
         effect = objects[o].effects[e];

         effect.onExecute();
    }
}

Именно так я и сделал. Прелесть здесь в том, что у вас есть система, управляемая данными, и вы можете довольно легко настроить логику для каждого эффекта. Обычно вам необходимо выполнить некоторую проверку условий в логике выполнения эффекта, но она все же намного более согласована, поскольку эти проверки предназначены только для рассматриваемого эффекта.
Manabreak

1

Я предложу несколько предложений. Некоторые из них противоречат друг другу. Но, возможно, некоторые из них полезны.

Рассмотрите списки против флагов

Вы можете перебирать мир и проверять флаг на каждом предмете, чтобы решить, стоит ли делать флаг-вещь. Или вы можете сохранить список только тех предметов, которые должны делать пометку.

Рассмотрим списки и перечисления

Вы можете продолжать добавлять логические поля в свой класс элементов isAThis и isAThat. Или вы можете иметь список строк или перечислимых элементов, таких как {«isAThis», «isAThat»} или {IS_A_THIS, IS_A_THAT}. Таким образом, вы можете добавлять новые в перечисление (или строковые значения) без добавления полей. Не то чтобы с добавлением полей что-то было не так ...

Рассмотрим функциональные указатели

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

Рассмотреть объекты

Некоторые люди предпочитают подходы на основе данных, сценариев или компонентов. Но стоит рассмотреть и старомодную иерархию объектов. Базовый класс должен принять такие действия, как «разыграть эту карту для фазы B хода» или что-то еще. Тогда каждый вид карты может переопределить и ответить соответствующим образом. Возможно, есть и объект игрока, и игровой объект, поэтому игра может делать что-то вроде if (player-> isAllowedToPlay ()) {do the play…}.

Рассмотреть возможность отладки

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

В конце концов, рефакторинг: рассмотрим модульные тесты

Независимо от того, насколько вы обобщаете свою архитектуру, вы сможете представить вещи, которые она не охватывает. Тогда вам придется рефакторинг. Может немного, может много.

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

void test1()
{
   Game game;
   game.addThis();
   game.setupThat(); // use primary or backdoor API to get game to known state

   game.playCard(something something).

   int x = game.getSomeInternalState;
   assertEquals(“did it do what we wanted?”, x, 23); // fail if x isn’t 23
}

Как видите, поддержание стабильности этих вызовов API верхнего уровня для игры (или игрока, карты и т. Д.) Является ключом к стратегии модульного тестирования.


0

Вместо того, чтобы думать о каждой карте в отдельности, начните думать в терминах категорий эффектов, и карты содержат одну или несколько из этих категорий. Например, чтобы вычислить количество времени в ходу, вы можете просмотреть все карты в игре и проверить категорию «манипулировать продолжительностью хода» каждой карты, которая содержит эту категорию. Затем каждая карта увеличивает или уменьшает длительность хода в соответствии с выбранными вами правилами.

По сути, это мини-компонентная система, где каждый «карточный» объект является просто контейнером для набора компонентов эффектов.


Поскольку карты - и будущие карты тоже - могут делать практически все, я ожидаю, что каждая карта будет иметь сценарий. Тем не менее, я почти уверен, что это не реальная проблема с производительностью ..
Яри ​​Комппа

4
в соответствии с основными комментариями: никто (кроме вас) ничего не сказал о проблемах производительности. Что касается полного сценария в качестве альтернативы, уточните это в ответе.
Джоккинг
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.