Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

How do you handle UI with a lot of states/rules?

Discussion in 'General Discussion' started by Wils, Apr 3, 2020.

  1. Wils

    Wils

    Joined:
    Dec 4, 2012
    Posts:
    6
    In my game, I have a mission planning scene, you can
    1) Add/Remove waypoints
    2) View detailed information for enemy buildings
    3) Add/update objectives for waypoints/enemy buildings
    4) Move camera (using wasd)
    Etc.

    There will be a lot of rules where
    If you are doing X you cannot do Y.
    If you are doing X then left mouse button do A and not B.

    Currently I'm using state machine to handle all these rules, it's working fine, but I am curious about how people usually handle all these rules. Do they use state machines as well? Or what's a better solution for this?
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    It feels like you might be overcomplicating it.

    For example, "View detailed information for enemy buildings" amounts to:
    "If mouse is close to building, spawn building detail window, and when it goes far away from it, destroy it". I.e. no state machine is needed in this particualr case.

    For more complicated behavior where you REALLY can't mix things up, you're dealing with... Decorator pattern.
    https://en.wikipedia.org/wiki/Decorator_pattern

    OR, to be more specific, with a stack of decorator patterns.

    Meaning, you have a literal stack of objects that can handle UI, keypresses and mouse movement. And each time a user initiates a mode change, you push things on the stack.

    So, lets' say your "map behavior" decorator has methods like handleKeyboard(), handleMouse() and so on. You can make it so those methods return bool and if bool is false, it means that handling of this event can be passed further down to stack.

    So, you can end up with this kind of structure:

    Code (csharp):
    1.  
    2. [Waypoint Editor]
    3.             |
    4. [Building information]
    5.             |
    6. [Map Mover]
    7.             |
    8. [Map Display]      
    9.  
    Where "Waypoint Editor" would be on top of the stack.

    So. Yoru script will query stack, grab top most element, and call "handleKeyboard()/handleMouse() which will deal with input event.s If those methods return false, the script goes to the next element of the stack, and calls the same methods again. That goes on all the way till all elements are processed.

    This way new exclusive mode can selectively mask portions of underlying functionality. For example, while dealing with waypoint editing, you'd still be able to wsad move map, because "waypoint editor" would not handle those keypresses and will let them pass through.

    Does this make sense to you?
     
  3. xeon321

    xeon321

    Joined:
    Nov 10, 2016
    Posts:
    9
    Oh, my bad, I missed out on one of the other functionality. Say you want to avoid detection on enemy radar, you click on enemy radar to permanently display the detection radius so that you can add your waypoints "safely".
    So its like :
    on mouse enter -> display information
    on mouse click -> display information permanently

    Yeah, this part makes perfect sense to me.

    This part however, I am not quite getting it.

    For example, in my game I can also adjust the added waypoints position in 2 ways
    1) Drag to reposition them (Y axis is just the raycast hit position)
    2) Click to reposition them along Y axis (control the height)

    To enter #2 mode (reposition along Y axis), the flow is :
    Click on a waypoint -> Move your mouse up down to control the Y pos of the waypoint -> Click again (anywhere) to exit from the reposition mode

    In the #2 mode (reposition along Y axis) you can do :
    1) WASD control map/camera movement

    You cannot do :
    1) click on enemy buildings to display information
    2) add/remove waypoints

    So, does this mean that I just have to push the waypoint editor & map/camera movement to the stack?

    If so, I will need to separate Add/Remove, Reposition waypoints into different Decorators (so I can push the only needed Decorators to the stack)?
     
  4. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    YOu don't HAVE to implement your functionality in any specific way, decorators is one of the ways to go which can be elegant.

    TO answer your question. In situation where you edit waypoint and implement it with decorators the stack could look like this:

    * (topmost)Waypoint editor (handles current waypoint modification)
    * Map interaction handler (handles selection of waypoints).
    * Map Mover (responds to wsad).

    In this scenario, the waypoint always fully handles mouse clicks and never allows next "layer" of decorators to receive mouse events. meaning handleMouse() never returns false (as per my previous example), and as a result "map interaction handler" never receives mouse movement commands while waypoint editor is active.

    At the same time, the editor lets the key movement pass through, and map interaction handler does the same. Meaning wsad keypresses get it through editor, interaction handler an arrive at map mover.
    --------

    This is if you want to handle things using decorators.

    There's one more way to handle editor modes, unity specific. Rather than implementing stacks and interfaces, you coul simply spawn "waypoint editor" as a GUI element with invisible panel which covers entire map area and blocks interaction with GUI elements below it. This method can also be used to implement "modal" dialogues in unity. This way "decorator" becomes a game entity and blcking is handled through collision resolution.

    ---------

    A third way is... coroutines. This is technically a state-machine, but rather than having a headache of implementing states via enum, you run gui logic in a IEnumerator based equivalent of a green thread ( https://en.wikipedia.org/wiki/Green_threads ). In this case your gui logic will be handled in a coroutine based infinite loop:
    Code (csharp):
    1.  
    2. while true{
    3.     ///stuff
    4.     yield break;//next frame
    5. }
    6.  
    But, when you want to significantly alter behavior, rather than switching states manually, you call coroutine:
    Code (csharp):
    1.  
    2.     if (keysPressed){
    3.         yield return waypointEditorCoroutine();
    4.     }
    5.  
    Where waypointEditorCoroutine() will contain its own "infinite yield loop" which will end once the work is done.

    The advantage of this appraoch is that rather than forcing everything to be a decorator, you can split logic into subroutines/functions, for example, you could have "handleWsadMovement()", and then rather than using that as decorator and figuring out which events you must let through for it to continue working, you could call "handleWsadMovement()" in all "subroutines" where you nee wsad movement. Meaning
    Code (csharp):
    1.  
    2. //main loop
    3. while true{
    4.     ///stuff
    5.     if (needToEditWaypoint()){
    6.         yield return waypointEditorSubroutine();
    7.     }
    8.     handleWsadMovement();
    9.     yield return;
    10. }
    11.  
    12. ....
    13. IEnumerator waypointEditorSubroutine(){
    14.     while true{
    15.         ///stuff
    16.         handleWsadMovement();
    17.         yield return;
    18.     }
    19. }
    20.  
    In this case you're technically implementing decorators, but without obvious OOP, and decorators themselves are composed through composition of parts implemented as procedures.

    The only problem with coroutine based approach is that coroutines do not serialize meaning your script will not work with hot reload in unity.
    ------

    Something like that.
     
    Oelson and xeon321 like this.
  5. xeon321

    xeon321

    Joined:
    Nov 10, 2016
    Posts:
    9

    Awesome! Thanks for the explanation & being patience with me. I will try to use the decorator pattern.
    The coroutine way is interesting, that's what I am doing for my AI at the moment, just didn't think to apply to UI as well.