What is a good design for modeless dialog boxes?

I am currently making a C ++ game. I have my main loop, during which logic is computed, then sprites are drawn, etc. I would like to implement “dialog boxes”: when you press the touch in front of the NPC, a dialog box appears at the bottom of the screen and you freeze until you press the key, BUT the game will continue to work (other characters move, etc.), so the main the loop is still running. I managed to do this very well: when the object is activated, it sends a message to the dialog manager, which displays a text box during the game, so it looks like this:

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 } 

Now the problem arises when I want some kind of association action to take place after the dialogue is completed or, for example, if you chose yes to the question. I would like to have an implementation that will work as follows:

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

The problem is that this check and actions will be executed as soon as the window event is generated, and not when the window is closed / completed. Is there a way to do this while storing the associated action in the OnActivated () function? I have no idea how to do this correctly without using function pointers that would force me to have a specific signature for each method that I could use as a result. thanks

edit: I published the award because I would like to know what is the most “canonical” answer to this problem. I think this is a very common problem (for many applications and for all modern games), and I would like to have such a flexible solution, because today I can’t list all the possible “consequences” that the dialogue may cause. More information: - each dialogue is triggered by objects originating from the common "Entity" class - different objects of the same class almost always had different dialogs / actions associated with them (for example, all NPC objects would not have the same dialogs) - no need to move the "dialog logic" from the OnActivated method or even outside the Entity class. This will happen anyway, because I want to be able to add scripts of "random" dialogs for each NPC, so dialogs, etc. They will be stored in another place - BUT I would like the dialog logic itself to be as close as possible to one dialog window. Ideally, I would like to do something like: "result = dialogWindow (" question? "), If (result) {...}". I'm not sure if this is possible, though

+4
source share
5 answers

Command Pattern is used to encapsulate code that will be executed later.

In your situation, the function object::OnActivated() will create the appropriate command object and save it later. When the user selects Yes / No, the command can be run without code requiring you to know which object of the command exists.

Here is an example of an add gold command object:

 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; }; 

Now, given some memory for the upcoming team:

 shared_ptr<DialogResponseCommand> dialog_command; 

You could create the OnActivated() command to create the command:

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

And when the user finally makes a choice:

 dialog_command->run(); 
+1
source

It is difficult to give a concrete answer, since you did not indicate (or did not mark) the platform for which it is necessary, so I will write a general answer.

The answer to your question:

"Is there a way to do this by storing the associated action in the OnActivated () function?"

Most likely, this is "No."

There are many tried and true patterns to solve the problem you are describing. This family of templates is the various Model-View-XXX models (MVC, MVP, Document-View, etc.). The basic premise of these templates is that there is a construction, usually a graph of objects that encapsulates the current state of the system (The Model) and a set of user interface elements (The Views) that display this state to the user. Whenever a model changes Views changes to match a new state. The details of how the model changes and the views are updated, set different templates in the family separately, and the used ones depend on how the input is processed for a particular system. MVC is suitable for Internet applications and many loop-based games because user input has a single entry point into the system. MVP, DV, and MVVM (which some claim to be the same as MVP) are better suited for desktop applications where input is actively managed in the GUI.

The disadvantage of using these templates is that the code for creating the view is rarely accompanied by the code for the associated action, but the advantages far exceed this disadvantage.

In your case, your model should have a property for the dialog text and a property for storing the current input handler (state template). Your main loop will do the following:

  • Get the current input handler to update the model based on user input, if any (for example, change the position of the user sprite).
  • Update the rest of the Model to reflect other elements in the game.
  • UI update based on current model

When the user clicks in front of the NPC, the default input handler changes the input input value for the specific dialog box and the general view for the dialog box displays the text to the user.

When the user selects an action in the dialog box, the handler returns to the default input handler, and the property for the dialog returns to empty.

Steps 1 and 2 make up the Controller in the MVC template, and step 3 is an event-independent update; conversely, you can use the Observable-Observer pattern and have model throw events that are observed in views that change accordingly.

+3
source

You can create an event class that performs several predefined actions based on what you need. It will have an instance variable that contains the enumeration value, for example EVENT_ADD_GOLD . It will also have a Perform function that checks the instance variable and performs the corresponding action. Other actions can be added as needed.

Good thing you only need one instance variable for each type. For example, value may refer to the amount of gold or damage. The value is determined by the type of event.

In the code that says: "We no longer need to show the dialog!" You can call the Perform method of your Event object. Since at any moment it does not make sense to have more than one event for this purpose, you can simply make one instance variable to store the link.

+2
source

I think you are missing callbacks here . The solution to your problem is to provide a callback in the onActivated method. The dialog manager will call this function or callback method when the simulation dialog is accepted, and that is where you can execute the behavior you want.

You did not provide enough details about the game, so I can’t give you a final way to solve this problem. If for any given object you always want the same action, you can simply provide the OnAccepted method. Something like that:

 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); } 

The above assumes that all classes representing the objects belong to the same hierarchy, so that the OnAccepted method can be declared as a virtual function in the base class.

If this is an oversimplified approach, you can make it more thoughtful, but it will always have some callback data passed to the doWindow method, which the dialog manager can use to start the callback at the right time.

If you need something very complex and have access to boost or a C ++ 11 implementation with std::function and std::bind , then you can even support callbacks with arbitrary arguments. The idea is that the argument passed to doWindow is a function object. An object can wrap a regular function or method inside an object, and if there are additional arguments, they can be bound to the function object using std::bind .

+1
source

Whenever you are dealing with windows and widgets, it is always useful to use MVC , the presenter at the beginning, or any alternative.

Now, in order to respond to certain “events”, you can use callbacks or much better than the observer (look into the signal / gain slot ).

0
source

Source: https://habr.com/ru/post/1384621/


All Articles