什么是无模式对话窗口的好设计?

时间:2011-12-04 23:41:23

标签: c++ oop

我目前正在制作一款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);
}

问题是这个检查&创建窗口事件后,将立即执行操作,而不是在窗口关闭/接受时执行。有没有办法做到这一点,同时在OnActivated()函数中保持相关的操作?我不知道如何在不使用函数指针的情况下正确地执行此操作,这将迫使我为我可以使用的每个方法都有一定的签名。 感谢

编辑:我发布了一笔赏金,因为我想知道这个问题最“规范”的答案是什么。我想这是一个非常普遍的问题(适用于很多应用程序和每个现代游戏),            我希望有一个尽可能灵活的解决方案,因为我无法在今天列出对话框可能引发的所有可能“后果”。            更多信息:          - 每个对话框将由派生自公共“实体”类的对象触发          - 来自同一类的不同对象几乎总是会有不同的对话框/操作(例如,所有NPC对象都没有相同的对话框)          - 我不关心将“对话逻辑”移出OnActivated方法,甚至不在Entity类之外。无论如何都会发生这种情况,因为我希望能够为每个NPC添加“随机”对话框方案,因此对话框等将存储在别处          - 但我想保持对话逻辑本身尽可能接近单个对话框。理想情况下,我希望能够做类似的事情:“result = dialogWindow(”question?“); if(result){...}”。我不确定它是否可能

5 个答案:

答案 0 :(得分:3)

很难给出一个具体的答案,因为你没有指定(或标记)这个平台,所以我会写一个通用答案。

你的问题的答案:

“有没有办法做到这一点,同时在OnActivated()函数中保持相关的操作?”

很可能“不”

有一系列经过验证的真实模式可以解决您描述的问题。这个模式系列是各种Model-View-XXX模式(MVC,MVP,Document-View等) 这些模式的基本前提是有一个构造,通常是对象图,它封装了系统的当前状态(模型)和一组用户界面元素(视图),向用户显示这种状态。 每当模型更改时,视图更改以匹配新状态。模型如何变化以及视图如何更新的细节设置了家庭中的不同模式,以及使用哪种模式取决于特定系统如何处理输入的细节。 MVC非常适合互联网应用程序和许多基于循环的游戏,因为用户输入只有一个进入系统的点。 MVP,DV和MVVM(有些人认为与MVP相同)更适合桌面应用程序,其中输入转到GUI中的活动控件。

使用这些模式的缺点是创建视图的代码很少跟随关联操作的代码,但其好处远大于这个缺点。

在您的情况下,您的模型应该具有对话文本的属性和存储当前输入处理程序的属性(状态模式)。 您的主循环将执行以下操作:

  1. 获取当前输入处理程序以根据用户输入更新模型(如果有)(例如更改用户精灵的位置)。
  2. 更新模型的其余部分以反映游戏中的其他元素。
  3. 根据当前模型更新UI
  4. 当用户在NPC前面按下时,默认输入处理程序将更改为处理触发的特定对话框的输入,对话框的泛型视图显示文本给用户。

    当用户在对话框中选择操作时,处理程序将恢复为默认输入处理程序,对话框的属性将返回为空。

    步骤1和2构成MVC模式中的Controller,步骤3是非事件驱动的视图更新;相反,您可以使用Observable-Observer模式,并使视图观察到的模型抛出事件发生相应的变化。

答案 1 :(得分:2)

您可以创建一个事件类,根据您的需要执行一些预定义的操作。它将有一个实例变量,其中包含类似EVENT_ADD_GOLD的枚举值。它还有一个Perform函数,用于检查实例变量并执行适当的操作。可以根据需要添加其他行动。

关于这一点的好处是你只需要为每种类型提供一个实例变量。例如,value可以指金额或损坏金额。含义由事件类型决定。

在代码中说“我们不再需要显示对话框了!”您可以调用Perform对象的Event方法。由于在任何时候为此目的而拥有多个事件没有意义,您可以只创建一个实例变量来保存引用。

答案 2 :(得分:1)

我认为你在这里缺少的是回调。您的问题的解决方案是在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方法,对话管理器可以用它来在适当的时候触发回调。 / p>

如果您需要非常复杂的东西并且可以访问具有std::functionstd::bind的boost或c ++ 11实现,那么您甚至可以支持具有任意参数的回调。我们的想法是传递给doWindow的参数是function对象。该对象可以包含常规函数或某个对象内的方法,如果有其他参数,则可以使用std::bind将它们绑定到函数对象中。

答案 3 :(得分:1)

Command Pattern用于封装稍后要执行的代码。

在您的情况下,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();

答案 4 :(得分:0)

每当您处理窗口和窗口小部件时,最好使用the MVCpresenter first或任何替代方法。

现在,为了对某些“事件”作出反应,您可以使用回调,或者更好的方式the observer(请查看boost's signal/slot)。