Что такое хороший дизайн для немодальных диалоговых окон? - PullRequest
4 голосов
/ 05 декабря 2011

В настоящее время я делаю игру на C ++.У меня есть мой основной цикл, во время которого вычисляется логика, затем рисуются спрайты и т. Д. Я хотел бы реализовать «диалоговые окна»: когда вы нажимаете прикосновение к NPC, диалоговое окно появляется в нижней части экрана ивы застыли до тех пор, пока не нажмете клавишу, НО игра продолжает работать (другие персонажи двигаются и т. д.), поэтому основной цикл все еще работает.Мне удалось сделать это очень хорошо: когда объект активирован, он отправляет сообщение в менеджер диалогов, который отображает текстовое окно, пока игра продолжает работать, так что это выглядит так:

object::OnActivated() {
GameManager::doWindow("some text");
//the game is not blocked here, the game continues to run normally and will display a window on the next frame
}

Теперь проблеманаступает, когда я хочу, чтобы какое-то действие по ассоциации произошло после завершения диалога, или, например, если вы выбрали «да» для вопроса.Я хотел бы иметь реализацию, которая будет работать так:

object::OnActivated() {
GameManager::doWindow("some text");
if(GameManager::Accepted()) addGold(100);
}

Проблема в том, что эта проверка и действия будут выполняться, как только будет создано событие окна, а НЕ при закрытии окна/ принято.Есть ли способ сделать это, сохранив связанное действие в функции OnActivation ()?Я понятия не имею, как сделать это правильно без использования указателей на функции, что заставило бы меня иметь определенную подпись для каждого метода, который я мог бы использовать в результате.спасибо

редактировать: я опубликовал награду, потому что я хотел бы знать, что является наиболее "каноническим" ответом на эту проблему.Я думаю, что это очень распространенная проблема (для многих приложений и всех современных игр), и я хотел бы иметь решение, которое было бы максимально гибким, поскольку на сегодняшний день я не могу перечислить все возможные «последствия»что диалог может вызвать.Дополнительная информация: - каждый диалог будет запускаться объектами, происходящими из общего класса «Entity» - разные объекты из одного и того же класса почти всегда будут иметь разные диалоги / действия, связанные с ними (например, все объекты NPC не будут иметьодни и те же диалоги) - меня не волнует перемещение "диалоговой логики" из метода OnActivation или даже вне класса Entity.Это произошло бы в любом случае, потому что я хотел бы иметь возможность добавлять «случайные» сценарии диалогов для каждого NPC, так что диалоги и т. Д. Будут храниться в другом месте - НО я хотел бы держать саму логику диалога как можно ближе дляединый диалог.В идеале я хотел бы иметь возможность сделать что-то вроде: "result = dialogWindow (" question? "); If (result) {...}".Я не уверен, что это возможно, хотя

Ответы [ 5 ]

3 голосов
/ 05 декабря 2011

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

Ответ на ваш вопрос:

"Есть ли способ сделать это, сохранив соответствующее действие в функции OnActivation ()?"

Скорее всего "Нет" .

Существует семейство проверенных и верных шаблонов для решения проблемы, которую вы описываете.Это семейство шаблонов - это различные шаблоны Model-View-XXX (MVC, MVP, Document-View и т. Д.). Основная предпосылка этих шаблонов состоит в том, что существует конструкция, обычно граф объектов, которая инкапсулирует текущее состояниесистема (Модель) и набор элементов пользовательского интерфейса (Представления), которые отображают это состояние для пользователя.Всякий раз, когда Модель меняет Виды меняются в соответствии с новым состоянием.Особенности того, как меняется модель и обновляются представления, устанавливают различные шаблоны в семействе, и какой из них использовать, зависит от особенностей обработки ввода для конкретной системы.MVC хорошо подходит для интернет-приложений и многих игр, основанных на цикле, потому что пользовательский ввод имеет единственную точку входа в систему.MVP, DV и MVVM (которые, как некоторые говорят, совпадают с MVP) лучше подходят для настольных приложений, где ввод поступает в активный элемент управления в графическом интерфейсе.

Недостатком использования этих шаблонов является код для созданияза представлением редко следует код для связанного действия, но преимущества намного перевешивают этот недостаток.

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

  1. Получить текущий обработчик ввода для обновления модели на основе пользовательского ввода, если таковой имеется (например, изменить позицию пользовательского спрайта).
  2. Обновите остальную часть модели, чтобы отразить другие элементы в игре.
  3. Обновите интерфейс на основе текущей модели

Когда пользователь нажимает перед NPC, по умолчаниюобработчик ввода изменяется для обработки ввода для определенного диалогового окна, и универсальное представление для диалогового окна отображает текст для пользователя.

Когда пользователь выбирает действие вдиалоговое окно, обработчик возвращается к стандартному обработчику ввода, а свойство диалогового окна возвращается к пустому.

Шаги 1 и 2 составляют контроллер в шаблоне MVC, а шаг 3 представляет собой обновление представления, не зависящее от события;и наоборот, вы можете использовать шаблон Observable-Observer и иметь события выброса модели, которые наблюдаются представлениями, которые соответственно изменяются.

2 голосов
/ 05 декабря 2011

Вы можете создать класс событий, который выполняет несколько предопределенных действий в зависимости от того, что вам нужно.Он будет иметь переменную экземпляра, которая содержит значение перечисления, например EVENT_ADD_GOLD.Он также имеет функцию Perform, которая проверяет переменную экземпляра и выполняет соответствующее действие.Другие действия могут быть добавлены по мере необходимости.

Хорошо, что вам нужна только одна переменная экземпляра для каждого типа.Например, value может относиться к количеству золота или урона.Значение определяется типом события.

В коде, который говорит: «Нам больше не нужно показывать диалог!»Вы можете вызвать Perform метод вашего Event объекта.Поскольку не имеет смысла иметь более одного события для этой цели одновременно, вы можете просто создать одну переменную экземпляра для хранения ссылки.

1 голос
/ 03 января 2012

Шаблон команды используется для инкапсуляции кода, который будет выполнен позже.

В вашей ситуации функция object::OnActivated() создаст соответствующий объект команды и сохранит его вбудет найден позже.Когда пользователь выбирает Да / Нет, команда может выполняться без кода, который должен знать, какой конкретный объект команды находится там.

Вот пример объекта команды «добавить золото»:

class DialogResponseCommand
{
public:
  virtual run() = 0;
};

class AddGoldCommand : public DialogResponseCommand
{
public:
  AddGoldCommand( int amount ) : amount(amount) {}
  virtual run()
  {
    if(GameManager::Accepted())
      addGold(amount);
  }

private:
  int amount;
};

Теперь, учитывая некоторое пространство для предстоящей команды:

shared_ptr<DialogResponseCommand> dialog_command;

Вы могли бы OnActivated() создать команду:

object::OnActivated() {
  GameManager::doWindow("some text");
  dialog_command = make_shared<AddGoldCommand>(100);
}

И когда пользовательнаконец делает выбор:

dialog_command->run();
1 голос
/ 28 декабря 2011

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

Вы не предоставили достаточно подробностей об игре, поэтому я могу 'Это действительно дает вам определенный способ решить эту проблему.Если для какого-либо данного объекта вам всегда нужно одно и то же действие, то вы можете просто предоставить метод OnAccepted.Примерно так:

object::OnActivated() {
    GameManager::doWindow(this, "some text"); // note I'm passing the object to the dialog manager
}

// the dialog manager calls this when the dialog box is accepted
void object::OnAccepted() {
    addGold(100);
}

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

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

Если вам нужно что-то очень сложное и у вас есть доступ к boost или реализации c ++ 11 с std::function и std::bind, то вы даже можете поддерживать обратные вызовы с произвольными аргументами.Идея состоит в том, что аргумент, переданный doWindow, является function объектом.Объект может обернуть обычную функцию или метод в некоторый объект, и, если есть дополнительные аргументы, они могут быть связаны с объектом функции, используя std::bind.

0 голосов
/ 03 января 2012

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

Теперь, чтобы реагировать на определенные «события», вы можете использовать обратные вызовы, или намного лучше наблюдатель (взгляните на сигнал / слот буста ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...