Search Unity

Some existential architecture thoughts... (Managers vs Components vs Common Sense)

Discussion in 'Getting Started' started by smallstep, Mar 17, 2018.

  1. smallstep

    smallstep

    Joined:
    Jan 25, 2016
    Posts:
    34
    Let's say that we have a scene with a button. When you click the button the scene should change to the next one. So there is a behaviour "ChangeScene" that could be written inside a method somewhere.

    The question is, to whom this behaviour should belong?

    - According to common sense, ChangeScene affects the whole game, not the button. So it should belong to a game manager.

    - According to a component-based architecture (which Unity promotes) the behaviour should belong to the button, and to every button that changes the screen.

    - According to coding realism, we want this game-specific logic to be kept in a singleton manager, but the Scene3 singleton instance is going to be destroyed (since Scene1 singleton already exists) so we can't drag the Scene3 manager to the unity event of the button. A button bridge function must also be written to programmatically call the running singleton instance method. Double work (imagine doing this for every game-specific behaviour). Maybe there is not a single behaviour here but 2 of them: RequestSceneChange and ImplementSceneChange. The first belongs to the button and the second to game manager.

    Generalize the same problem with behaviours such as PlaySound, SaveProgress, FadeScreen, SyncToCloud etc.

    What is the proper way of writing behaviours in Unity?
     
    Last edited: Mar 17, 2018
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I have exactly just such a script among my EventComponents. I generally just attach it to the button. But the beauty of the component model (and Unity events) is that it doesn't matter — if you need to attach it to the game manager, or some sort of level object, or whatever, then do that.

    In general, I think it's to embrace components and events. So I divide all my encapsulated, reusable stuff into little scripts that are generally one of three types:
    1. Triggers: they detect when something has happened (a key is pressed, or something has collided with something else, or whatever), and invoke an appropriate event, sometimes with some data.
    2. Actions: they have a public method (or perhaps several related public methods) with a signature that can be invoked by an event (sometimes with a parameter).
    3. Relays: specifically intended to respond to an event by invoking some other event.
    You can see my initial set of these at the link above, but in various projects I've occasionally needed more, and so I add those following the same pattern. And of course more complex game-specific objects (like a player controller) often serve several of those roles at once. But that's the basic idea.

    And then you just snap these things together like LEGO. It really doesn't matter where you attach them; usually there is an obvious place to do so, or so it seems to me anyway. For example, in the case above, if it's a scene-change button, then I want the button to change the scene. So I put the LoadScene action on that button, and now that's what it does. If I copy/paste this button to another scene, it'll still be a scene-loading button, and I can simply change the parameter on the event to make it load a different scene.
     
    Ryiah likes this.
  3. Bill_Martini

    Bill_Martini

    Joined:
    Apr 19, 2016
    Posts:
    445
    Either way will work. I'm thinking you're getting bogged down on optimizing your code before you've written it. On a case by case basis there may be advantages to do one way or the other but at the end of the day they both just work. Try to anticipate which way to use by addressing your current game design, usually one way will require less work or duplicate code. Altering or debugging a single code block is much easier than bits scattered over many objects and components.
     
    JoeStrout likes this.
  4. smallstep

    smallstep

    Joined:
    Jan 25, 2016
    Posts:
    34
    @JoeStrout
    I guess you are talking about unity events (and not c# events). I have read all things you have written about unity events and watched your videos "true power of unity events" etc, but I still don't get something crucial:

    Say you have a player object that chacks if it is inside screen bounds and when touching the screen bounds game must be finished invoking a unity event "GameOver". Of course you can handle the event within inspector for the player object binding it with e.g. a "FreezePlayer" method. But how can you send this event to all of the other screen objects that want to subscribe to this message, for the "gameover pop menu" to be displayed and the "hearts label" to be decreased by one etc? I see how you can do this with delegates (c# events) but I don't get it with unity events.

    In your post above, are you talking about just same-object events handling? If yes, you mean that when detecting a trigger has been fired you just handle it with calling other objects' methods? If this is the case, how would you handle a trigger that needs to call methods from 10 different objects, or of objects that are unknown to the trigger hosting object? The ideal functionality for me would be something like broadcasting a message to all subscribers and hearing it globally from other objects without any prior binding. Then you would indeed place objects like LEGO with a zen feeling that everything is going to work. Are you achieving this somehow with unity events? Thanks!
     
  5. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    The UI elements you give as examples here would be easily hooked up as just additional handlers for the player's onGameOver event (or whatever you choose to call it). So when inspecting the player, under the GameOver event, you'd see (1) a call to FreezePlayer on itself, (2) a setting of the GameOverPopMenu to active; and (3) a call to HeartsMgr.Decrement (where HeartsMgr is a script on the hearts label).

    Although if FreezePlayer is in the same script (class) as the bounds detection, I wouldn't use an event for that. The point of events is to decouple different classes; if it's another method in the same class, just call it directly.

    Also, note that UnityEvents aren't the right tool for everything, though (as you clearly recognize) I do think they're often overlooked in situations where they really are the best tool. But if you really need to (say) notify all game objects in the scene that something has happened, then you might want to use something else.

    Not necessarily. But with these event components, it does often happen that one script invokes (via a UnityEvent) another script on the same object.

    You handle it exactly the same way. That's one of the great things about them — it doesn't matter whether the receiver of the event is on the same object, or a different one (or 10 different ones). And the method invoked doesn't care whether it was invoked from the same object, or a different one, or by 10 different ones.

    Without any binding?! I'm struggling to see how that is ideal.

    Well, I see how it could work with events that are very specific. Like in this particular game, this particular event means only this very specific thing — and anybody might need to know about it. So yeah, in that case a UnityEvent isn't the ideal solution; you might want to use something else, such as the event messaging system, or your own system based on C# delegates.

    The real value of UnityEvents is that you can hook them up in the Inspector. The need to hook them up is inherent in what they represent, which is generally something very generic — "this thing was clicked," "some amount of time has passed," etc. Obviously there is no point in having all objects care about "some unspecified thing was clicked;" if you're thinking about it that way, you're thinking about some specific thing being clicked. But when an event is something generic, then to make use of it, it matters both where that event was generated, and who specifically should do something about it — and UnityEvents let you define all that right in the Inspector, without writing any code.
     
    Last edited: Mar 19, 2018
    smallstep likes this.