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. Dismiss Notice

Did i understand decoupling?

Discussion in 'General Discussion' started by Chris_St, Jun 27, 2023.

  1. Chris_St

    Chris_St

    Joined:
    Apr 25, 2018
    Posts:
    19
    Hi.
    I'm following a tutorial on youtube while also working on my own game.
    He makes a pokemon game and after every video i use what i've learned to make mine, which will be like Final Fantasy.
    As he creates the battlescreen, the ui and the battle system someone in the comments mentioned it would be better to decouple the ui and use events as it would be easier to manage if you change something from the ui.
    So i was thinking a lot about why and how and wanted to present you my idea of doing it and if you would approve.

    His BattleSystem.cs starts like this:

    Code (CSharp):
    1. public class BattleSystem : MonoBehaviour
    2. {
    3.  
    4.     [SerializeField] private BattleUnit playerUnit;
    5.     [SerializeField] private BattleUnit enemyUnit;
    6.     [SerializeField] private BattleHud playerHud;
    7.     [SerializeField] private BattleHud enemyHud;
    8.     [SerializeField] private BattleDialogBox battleDialogBox;
    9.     .
    10.     .
    11.     .
    So he gets all the ui-elements and then works with them in this class.
    My idea is to delete this and put BattleSystem into the ui elements.
    (sorry btw for my terminology with "get" and "put" etc)
    Then make various event actions in BattleSystem and let the ui elements listen and react.
    It somehow seems better to me, but i'm not 100% sure why exactly

    Thanks in advance for your help!
     
  2. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    the system which directs UI can still operate by listening for events.

    the benefit of keeping UI dumb is that if you change around your systems or events, they won't break. So it's just less stuff to maintain in an evolving project.

    If a single system is responsible for listening to events and updating UI, it makes life a little easier because you wont have to go hunting when you have a bug where UI is not updating correctly. You'll know that its handled in the UI system.

    Of course you might split a UI system into smaller parts, like "Enemy related UI" , "player related UI", and so on, if you prefer.
     
    Chris_St likes this.
  3. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    521
    I tend to design around the idea that "business objects", the BattleSystem in this case should exist and be able to run even if there was no UI. It doesn't have to interact via events but that is one solution. By separating the UI you have a battle system that can be tested via code that doesn't require interacting with the UI to test.
     
    Chris_St likes this.
  4. Voronoi

    Voronoi

    Joined:
    Jul 2, 2012
    Posts:
    571
    The UI systems I've done always turn into a big mess. I like the concept of keeping the "UI dumb" but can you describe what you mean by that exactly? @tleylan seems to be suggesting the same thing.

    What I am imagining is a screen UI for health, score, etc. that is completely unaware of the game and could be copied/reused into a new game with very little effort if it's 'dumb' like that. Likewise, a scene UI that floats over characters and enemies in the game.

    My question is where/how do you hook up the actual textMesh components to the game manager? Is it drag and drop into the manager? Or a generic Event that is part of the 'dumb UI'? Do you put that on the textMesh or on a parent object for the entire UI?

    I know it's kind of a basic question, but honestly I feel I've never really solved it conveniently and in a way that is reusable.
     
  5. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,317
    You kinda need to figure out what are you trying to achieve with this and WHY it is better. You need to understand and not feel.

    If you move logic into discrete elements, it'll be spread out all over the place, while with his approach all UI is handled in the same spot. Neither approach is inherently better, however, because there are different types of UI. Something like a clock display can be decoupled from pretty much everything. And something very involved may work better with a manager class.
     
    CodeRonnie and Chris_St like this.
  6. Chris_St

    Chris_St

    Joined:
    Apr 25, 2018
    Posts:
    19
    So it's like what i had in mind. I'll make a "BattleUI" which is responsible for updating HP/Mana and maybe some text and it simply listens to events. And (most of) these events will be in my "BattleSystem" which manages turns and damage calculation etc.

    I'll keep that in mind. I still have to learn a lot but i like the concept of a standalone BattleSystem which you can test via code.

    Thanks for the input. You're right, i have to understand, and i'm sure that comes with more experience.
    While i'm, lets say, an advanced beginner from what i have learned about game development so far
    i often have problems with basic stuff. That's why i also started the Harvard CS50 course two weeks ago.
     
  7. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    521
    These days my approach is to make the clock a subscriber to the "time" publisher and I dumb down the clock to be a displayer of the message it gets.
     
    Chris_St likes this.
  8. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    What you've described sounds about like what I mean.
    UI doesn't know about any other game objects, it only has whatever functions it needs to do it's own things, like animations, show or hide elements within it, etc.

    This way it can easily be reused and also not break if something that communicates with them changes. The main idea is like the widgets are like a little pushboard. they have buttons that a person can press but they are just dumb electronics with no ability of their own to do anything.

    Unfortunately I haven't used unity in ages and my current process was refined outside of unity, so I can only give some general principles.

    The way I set things up in my most recent projects is that a UI manager manager class knows about each widget. It listens for events from an abstract class that acts simply as an event relay. Meaning any game objects in the world call events on it, which manager classes listen for. So there is like this one abstract class that everybody knows about but it doesn't know about anybody.

    So UI manager knows that an event like "player damaged" happened and then from that can just update any widgets by directing them what to do. Like, OnPlayerDamaged -> GetPlayerHealthWidget (if its valid) -> PlayHealthDecreaseAnim.

    You could break the hard link between manager and widgets of course but I haven't found that to be necessary.

    So reuseable widgets would be ones that share common functionality like a radio box with multi select buttons, or a button that holds a bool for "is selected" and changes colors when selected, but no widgets ever subscribe directly to events, or know about any classes whatsoever. They are just like an etch-a-sketch really - they have some buttons you can push and then they have some display updated from that.

    The intial idea for "dumb UI" I got from a unity programmer though. He helped me refactor towards this paradigm. I previously had widgets listening for events and knowing about other classes, and it was causing a lot of issues. Cause for instance, imagine you have a reference to some pickup item and in order to have it go from the ground into players inventory, there is like 5 different widgets involved in the process. It gets problematic fast when you basically have to pass this reference through so many hands. Very easy to make a bug but just mentally trying to keep track of it all uses way too much energy.

    So a manager can just hold the reference once, and then tell widgets what to display. A widget might need to send some sort of message about decision player made. That is where reuseable widgets come in handy. A button which can report true or false might be used to say "player wants to drop the item". But all the button knows is to report true or false on its pressed event. The manager knows how to interpret this and knows about the item in question.

    Naturally you might not want the UI manager doing anything other than reporting, so it might be that inventory system fires event to say "we are interacting with an item", UI manager hears this and decides to create "pickup item widget", and then listens for that button press on the widget. When that is pressed, then we fire an event like "player decided to pickup item". Now the inventory system can respond to that. So all these systems are working together but they don't know about each other. Communications are fire and forget. It is easy then to add new listeners in a modular way. So like if you want a sound effect to play with player picks up the item, that can happen over in its own component. A sound manager or w/e. Doesn't need to tangle with other code and cause confusion about class responsibilities.

    And to get a full picture of what any event in the game entails we can search that event and just see which managers are listening to it, and who is calling it. So pretty easy to get an overview of the scope of communications without really having to look through classes individually or trying to remember who talks to who.

    Like this, the UI manager is just like an intermediary between other systems and the UI. It's mostly just a way to funnel communications down to a single point to make life easier for the human.

    Somebody can correct me if this is wrong, but if a textmesh component is created at runtime, my way would be to store reference to it in the UI manager class. Or if it belongs to a wrapper class that has various functions to operate on it, store reference to that wrapper class.

    So in the UI manager class you'd have a reference to all the widgets like, "players health bar," "ammo counter," etc. There are methods to control when these are loaded but I think that differs quite a bit between engines so I won't bother mentioning anything about that. But with a UI heavy game it probably becomes necessary to control loading of widgets at some point so that this manager doesn't force a bunch of stuff that won't be needed to load.

    The primary way I measure that this sort of paradigm is "working" is that if I go an focus on art for 2 months and come back to the code, if a basic communication bug pops up, it should only take me like 5 minutes max to solve it. If I have to dig through fifty widgets for 3 hours to me that is not maintainable architecture so I try to find a way to consolidate communications from many to one.
     
    Last edited: Jun 27, 2023
    CodeRonnie, Chris_St and Voronoi like this.
  9. Chris_St

    Chris_St

    Joined:
    Apr 25, 2018
    Posts:
    19
    Wow, thanks a lot for this in-depth answer. It's now clear to me that i was definitely on the right track.
     
  10. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    dont take my word for anything.

    theres at least a few people here who could probably explain in much more succinct and clear way - I believe what I am doing falls pretty squarely into a well known design pattern but I never remember names of things. Pretty sure tylelan is describing very similar idea as well.
     
    Chris_St likes this.