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 ScriptableObject
s 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:
- Check that the player's ammo is greater than zero (done in
PlayerStats
) - Instantiate a new bullet in the scene (done in
Scene
) - Update the player's ammo (done in
PlayerStats
) - Update the GUI to show the new ammo count (done in
GUI
) - 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.
- The
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
- Same pros as method 2, with the added benefit of the button not having a reference to its parent
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.
- Same cons as above.
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.
- Code is split up between different classes, so
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.
- 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
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)?
GameManager
and 'PlayerStats' are actuallyScriptableObject
s, 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\$