1
\$\begingroup\$

I'm a newbie when it comes to game development and I'm struggling to decide on a good and consistent architecture for having child game objects trigger actions at a higher parent level.

For instance, say that I have game objects like this (in Unity)

This is a conceptual heirarchy, not the actual Unity scene heirarchy. GameManager and PlayerStats are ScriptableObjects in Unity. I have the hierarchy drawn like this because each scene can be thought of as a child of the overall game.

GameManager (ScriptableObject)
|
|--> PlayerStats (ScriptableObject)
|
|--> Scene (the Unity scene)
     |
     |--> GUI (MonoBehaviour)
     |    |
     |    |--> Button (MonoBehaviour)
     |
     |--> Player (MonoBehaviour)
     |
     |--> Enemy1 (MonoBehaviour)
     |
     |--> Enemy2 (MonoBehaviour)

If I click the button in the GUI, I want the player to fire a gun. That requires the following things to happen:

  1. Check that the player's ammo is greater than zero (done in PlayerStats)
  2. Instantiate a new bullet in the scene (done in Scene)
  3. Update the player's ammo (done in PlayerStats)
  4. Update the GUI to show the new ammo count (done in GUI)
  5. Play the "fire" animation (done in Player)

The "fire gun" event is triggered from the Button object, but it needs to perform actions on objects that it is not an ancestor of. There also needs to be some high-level coordination between objects, so that if the player is out of ammo, none of the subsequent actions take place.

Here are the ways I can think of to handle this logic, along with pros and cons:

1. Do all logic within the button's Fire() method

  • Pros

    • Simple (all code is directly in the button's click handler)
  • Cons

    • Violates single responsibility principle. The button is now responsible for game logic.
    • The button has to have references to a bunch of higher level game objects. This smells of poor design.

2. Have the button's click handler call GameManager.FireGun()

  • Pros

    • The entire event sequence is coordinated from a high-level game object that only acts on its children
    • The GameManager.FireGun() method can be called from any other game event that I want to fire the gun from
  • Cons

    • The GameManager class will be huge because it'll have a lot of these types of events.
    • The button has to call a method (FireGun()) from a higher-level object, and so it still has to have a reference to a parent object. This seems like poor design.

3. The button's click handler puts a "fireGun" message on the bus, which is picked up by GameManager.OnGunFired()

  • Pros

    • Same pros as method 2, with the added benefit of the button not having a reference to its parent GameManager
  • Cons

    • Same cons as above. GameManager will have a lot of "OnSomething" methods.
    • GameManager will have to subscribe/unsubscribe to a lot of messages.
    • Unit testing becomes more difficult. Rather than checking to see if a method was called on a mock, each unit test will need to set up a message bus, and verify that the correct messages were sent.

4. Have GameManager, Player Stats, GUI, and Player all subscribe to the fireGun event

  • Pros

    • Code is split up between different classes, so GameManager won't end up with a bunch of big methods that coordinate every action.
    • Better adherence to single responsibility principle. All inter-object communication is done with the message bus, so objects don't have to know about each other.
  • Cons

    • Difficult to do inter-object coordination, such as if the "fire" button is clicked and the player is out of ammo, none of the "fire gun" actions should happen. That could be solved by doing an ammo check in the button's onClick, but again that would require the button to know about higher level player objects.
    • Same unit testing difficulties as method 3.

Among these four architectures, which one(s) tends to be the most common in games (or is there a better way of doing this that I didn't mention)?

\$\endgroup\$
2
  • \$\begingroup\$ I'd recommend against your current structure in the first place. Your scene is not part of your gamemanager, and so shouldn't be a child of it. Same goes for your GUI and scene. --- Your parent-child relationships should be meaningful or actually organizationally sound. Right now, they are neither. You're neither acomplishing any ingame change with them, nor solving any organizational problems. In fact, you're setting up extra organizational problems for yourself. \$\endgroup\$ Commented Feb 16, 2019 at 16:01
  • \$\begingroup\$ My diagram is a conceptual heirarchy, not the actual Unity heirarchy. GameManager and 'PlayerStats' are actually ScriptableObjects, not something in the scene. I'll update the question to make that clear. As far as the GUI, since its an object with each Unity scene, I listed it as a child of the scene. \$\endgroup\$
    – Ben Rubin
    Commented Feb 16, 2019 at 16:11

1 Answer 1

2
\$\begingroup\$
  • Check that the player's ammo is greater than zero (done in PlayerStats)

This check belongs to the firing behavior, which as far as I understand and would recommend, should belong to the player.

PlayerStats should probably be exactly just that. A data class containing the player's stats. No actions should be within it, just data that drives or limits actions elsewhere.


  • Instantiate a new bullet in the scene (done in Scene)

This belongs to the firing behaviour, and should be done in the player.


  • Update the player's ammo (done in PlayerStats)

Same thing. Part of the firing behavior.


  • Update the GUI to show the new ammo count (done in GUI)

This should probably be done through an Observable pattern in an interface that changes the data in PlayerStats. Changes to PlayerStats should only be done through that interface, and changes cause the GUI to update autonomously through the observable pattern.


  • Play the "fire" animation (done in Player)

Already where it should be.


The "fire gun" event is triggered from the Button object, but it needs to perform actions on objects that it is not an ancestor of.

There is no need for events to be children of what they trigger. They will trigger any listener registered to them, and you can register any listener from anywhere.


  1. Do all logic within the button's Fire() method

No. You explained it yourself. Your 'pro' is a con. You're creating a god-class, and that's bad.

  1. Have the button's click handler call GameManager.FireGun()

No. Your GameManager should manage your game-flow in general. Not specific actions of specific objects. Which the firing behavior, GUI update behavior, and all other stuff you listed, are.

  1. The button's click handler puts a "fireGun" message on the bus, which is picked up by GameManager.OnGunFired()

No. Don't use messages. Use UnityEvent. And no. As I explained above, that firing behaviour should not be your gamemanager's responsibility. It belongs to the player.

  1. Have GameManager, Player Stats, GUI, and Player all subscribe to the fireGun event

No. You seem to be fixated on an idea that everything should be set in motion by one trigger. But instead of that, you should let things follow their logical sequence. Including a sequence of one thing triggering the next as needed.


Here's how game behaviour would flow optimally, in my opinion:

  1. Either in the editor, as an Init() method in the GameManager, or in each object. set up dependencies. In this case, that means registering the proper listener to the proper events.
  2. GUI triggers an UnityEvent that calls a method, say, Fire(), in the Player.

Within Fire():

  1. The ammo is checked. If ammo<1, the behavior is interrupted right there with a simple return. Else...
  2. Decrement ammo through an interface that will automatically update the GUI upon changes (with another UnityEvent).
  3. Spawn bullet.
  4. Give bullet it's initial force/speed.
  5. Trigger the start of fire animation.
\$\endgroup\$
2
  • \$\begingroup\$ How should I manage UnityEvents? I have to be able to dynamically subscribe to events in code as objects are created and destroyed, so I think I'd need to use either a ScriptableObject or a static class as an event manager, right? The reason I've been using a message bus so far is that it's easy for objects to subscribe and unsubscribe from messages without knowing anything about the rest of the objects in the game. \$\endgroup\$
    – Ben Rubin
    Commented Feb 16, 2019 at 18:33
  • \$\begingroup\$ @BenRubin That's dependency management. For objects that are present in the scene in editor, u can set UnityEvents from the editor. Otherwise, for dynamic stuff, it has to be done through script. I'd recommend making the script that instantiates the objects also responsible for such UnityEvents' dependency-management. It's part of the objects' setup, after all. --> var instance = Instantiate(yourPrefab); var script = instance.GetComponent<TargetScriptType>(); yourEventSourceObject.yourUnityEvent.AddListener(script.MethodToBeTriggered); - yourEventSourceObject is cached at class-level. \$\endgroup\$ Commented Feb 17, 2019 at 17:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .