lundi 7 janvier 2019

Design Pattern to handle UI commands

I am trying to implement a flexible, designer friendly UI for a UE4 game.

The UI is made of many widgets and a HUD class which acts as the mediator between the rest of the game and the UI itself. Communication between game, HUD, and widgets is implemented through events.

I am having problems identifying the correct pattern and / or implementation for handling events broadcast by UI buttons.

In the beginning I had buttons with very specific commands like "pause the game". I wrote a class to represent a generic command, and then I made a subclass for each desired behaviour. I then used the Visitor pattern to handle the event without knowing what it is specifically.

A pseudo code example of the setup would be:

button.onClicked(...)
{
    ...
    commandObject.broadcast();
    ...
}

commandObject.broadcast()
{
    ...
    eventInstance.broadcast(this); // hud listens for this call
    ...
}

hudInstance.visit(Command * command)
{
    ...
    command.acceptVisitor(this);
    ...
}

eventInstance.acceptVisitor(HUD * hud)
{
    ...
    hud.doSomething();
    ...
}

Then I had to add buttons with more dynamic commands, like "open the context menu for target object". So I had to go back and implement an interface IHasUITarget, with get/set methods for the UI Target. So now I store a pointer to the target (which I take from a IHasUITarget object such as a widget) in a TargetedCommand object (which is a child of Command)I then pass the target when accepting the HUD visitor.

button.onClicked(...)
{
    ...
    commandObject.broadcast();
    ...
}

commandObject.broadcast()
{
    ...
    eventInstance.broadcast(this); // hud listens for this call
    ...
}

hudInstance.visit(Command * command)
{
    ...
    command.acceptVisitor(this);
    ...
}

eventInstance.acceptVisitor(HUD * hud)
{
    ...
    hud.doSomethingWithTarget(target); // got it using getUITarget() when initializing the command properties
    ...
}

Now I have to add support for commands like "assign task x to target object". Which means adding another parameter, with an interface etc.

I have problems with this mostly because I feel like I might paint myself into a corner down the road, and also because I'd like to enforce the correct Command choice when the designer adds a button to a widget and picks the Command associated with it: the designer interface used to pick the desired Command filters by class, and the class is defined inside the header of the object which contains it (in this case, a button). If I want to restrict acceptable commands, I'd have to make different types of buttons: one accepting Command objects, one accepting CommandChildA, another accepting CommandChildB, etc. On the other hand, if I don't restrict commands I'd have to check that the Command object to which the designer assigned the context menu event actually implements IHasUITarget, before calling getUITarget on it. Similarly, if I wanted to initialize the Command properties I'd have to check if Command implements the appropriate interface, before making a call such as commandObject.setSomeProperty(Something * s). So either I won't be able to code to interfaces, or I will have to add a whole set of class variables which might or might not be used.

Maybe I am trying to use the wrong pattern. What would you recommend to solve this problem?

Thank you!

Aucun commentaire:

Enregistrer un commentaire