Reputation: 7994
I'm currently making a C++ game. I have my main loop during which the logic is computed then the sprites etc are drawn I would like to implement "dialog windows" : when you press a touch in front of a NPC, a dialog window pops up in the bottom of the screen and you are frozen until you press a key, BUT the game continues to run (the other characters are moving, etc) so the main loop is still runing. I managed to do it just fine: when an object is activated, it sends a message to a dialog manager that display the text window while the game keeps running, so it looks like :
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 comes when I want some association action to happen after the dialog is over, or for instance if you selected "yes" to a question. I'd like to have an implementation that'd work like this:
object::OnActivated() {
GameManager::doWindow("some text");
if(GameManager::Accepted()) addGold(100);
}
The problem is that this check & actions will be performed as soon as the window event is created, NOT when the window is closed / acepted. Is there any way to do this, while keeping the associated action in the OnActivated() function? I have no idea how to do this properly without using function pointers, which would force me to have a certain signature for every method I could use as a result. thanks
edit: I posted a bounty because I'd like to know what is the most "canonical" answer to this problem. I guess this is a very common issue (for a lot of applications and every modern games), and I'd like to have a solution that is as flexible as possible, since I cannot list as of today all of the possible "consequences" that a dialog could trigger. More info: - each dialog would be triggered by objects deriving from a common "Entity" class - different objects from the same class would almost always have different dialog/actions associated to them (for instance, all of the NPCs objects would not have the same dialogs) - I don't care about moving the "dialog logic" out of the OnActivated method or even outside of the Entity class. This would happen anyway because I'd like to have the ability to add "random" dialogs scenarios for each NPC, so the dialogs etc would be stored elsewhere - BUT I'd like to keep the dialog logic itself as close as possible for a single dialog. Ideally I'd like to be able to do something like : " result = dialogWindow("question?"); if (result) { ... }". I'm not sure it's possible though
Upvotes: 4
Views: 563
Reputation: 63775
The Command Pattern is used to encapsulate code to be executed at a later time.
In your situation, the object::OnActivated()
function would create an appropriate command object and store it to be found later. When Yes/No is selected by the user, the command may be run without the code needing to know what particular command object happens to be there.
Here's 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 storage for an upcoming command:
shared_ptr<DialogResponseCommand> dialog_command;
You could have your OnActivated()
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();
Upvotes: 1
Reputation: 64223
Whenever you deal with windows and widgets, it is always good to use the MVC, presenter first or any alternative.
Now, to react to certain "events", you can use callbacks, or a way much better the observer (take a look into boost's signal/slot).
Upvotes: 0
Reputation: 67509
I think what you are missing here is callbacks. The solution to your problem is to provide a callback in the onActivated
method. The dialog manager will call this callback function or method when the modeless dialog is accepted, and that is where you can execute the behavior that you want.
You did not provide enough specifics about the game, so I can't really give you a definitive way to solve this. If for any given object you will always want the same action, then you could simply provide a method OnAccepted
. Something like this:
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 the classes that represent 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 a too simplistic approach you can make it more elaborated, but it will always have some callback data passed into the doWindow
method that the dialog manager can use to trigger the callback at the proper time.
If you need something very sophisticated and have access to boost or a c++11 implementation that has 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. The object can wrap a regular function, or a method inside some object, and if there are additional arguments they can be bound into the function object using std::bind
.
Upvotes: 0
Reputation: 9007
It's hard to give a concrete answer as you did not specify (or tag) the platform this is for, so I'll write a generic answer.
The answer to your question:
"Is there any way to do this, while keeping the associated action in the OnActivated() function?"
Is most probably "No".
There is a family of tried and true patterns to solve the problem you describe. This family of patterns are the various Model-View-XXX patterns (MVC, MVP, Document-View, etc.) The basic premise of these patterns is that there is a construct, 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 the Model changes the Views change to match the new state. The particulars of how the model changes and the views are updated sets the different patterns in the family apart and which one to use depends on the particulars of how input is handled for a particular system. MVC is a good match for internet applications and many games based on a loop because user input has a single point of entry into the system. MVP, DV and MVVM (which some say is the same as MVP) are better suited to desktop apps where input goes to the active control in the GUI.
The downside of using these patterns is that code for creating the view is rarely followed by the code for the associated action, but the benefits far outweigh this drawback.
In your case your model should have a property for the dialog text and a property to store the current input handler (a state pattern). Your main loop would do the following:
When the user presses in front of an NPC, the default input handler changes to handle input for the specific dialog triggered and a generic view for the dialog displays the text to the user.
When the user selects the action in the dialog, the handler reverts to the default input handler and the property for the dialog returns to empty.
Steps 1 and 2 constitute the Controller in the MVC pattern and step 3 is a non event-driven view update; conversely you can use the Observable-Observer pattern and have the model throw events that are observed by the views which change accordingly.
Upvotes: 3
Reputation: 86240
You could create an event class which does a few predefined actions based on what you need. It would have an instance variable that holds an enum value like EVENT_ADD_GOLD
. It would also have a Perform
function that checks the instance variable and does an appropriate action. Other actions could be added on a per need basis.
The good thing about this is you only need one instance variable for each type. For example value
could refer to an amount of gold or damage. The meaning is determined by the event type.
In the code that says "We don't need to show the dialog anymore!" you can call the Perform
method of your Event
object. Since it doesn't make sense to have more than one event for this purpose at any one time, you can just make one instance variable to hold a reference.
Upvotes: 2