I'm currently developing a Unity game and one of the things I'm really trying to achieve in the project is the loosest possible coupling amongst the behavioural components in the codebase.
In order to do this I'm using Manager classes to wire everything together, ensuring that the classes that sit beneath have no knowledge of each other and could easily be brought to another project with ZERO adjustments needed. I'm largely achieving this through dependency injection and events. What I'm striving for is that the only thing another developer would need to do if he or she brought my components to their project would be to wire them up according to the game logic that would be specific to that game.
Below I have included a sample of 3 classes that I think sets the scene for my question.
PlayerInput.cs:
public class PlayerInput : MonoBehaviour
{
public event Action<Vector3> OnLeftMouseButtonDown;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Input.mousePosition;
{
OnLeftMouseButtonDown?.Invoke(mousePos);
}
}
}
}
PlayerMovement.cs:
public class PlayerMovement : MonoBehaviour
{
public void MoveUp()
{
Debug.Log("Moving up");
}
public void MoveDown()
{
Debug.Log("Moving down");
}
}
PlayerManager.cs:
public class PlayerManager : Singleton<PlayerManager>
{
[SerializeField]
PlayerInput playerInput;
[SerializeField]
PlayerMovement playerMovement;
private void OnEnable()
{
playerInput.OnLeftMouseButtonDown += HandlePlayerInputOnLeftMouseButtonDown;
}
private void OnDisable()
{
playerInput.OnLeftMouseButtonDown += HandlePlayerInputOnLeftMouseButtonDown;
}
private void HandlePlayerInputOnLeftMouseButtonDown(Vector3 mousePos)
{
if (mousePos.y > Screen.currentResolution.height / 2)
{
Debug.Log("Clicked top part of screen");
playerMovement.MoveUp();
}
else
{
Debug.Log("Clicked bottom part of screen");
playerMovement.MoveDown();
}
}
}
As you can see, the PlayerManager class encapsulates the logic specific to my game, namely that if the player clicks the top half of the screen the character moves up, and if they click the bottom half the character moves down. I really like that PlayerInput and PlayerMovement know absolutely nothing about each other. Obviously PlayerManager knows about both but it's also the class that is intended to hold the logic specific to this particular game which of course has to go somewhere and keeping it in a higher level (hierarchically speaking) class seems to make sense since it helps to achieve decoupling of the classes under it.
While researching this style of organising classes, I came across a pattern that seemed related to it.
At first I thought "Yes, this is definitely the Facade pattern after reading about it and watching a Youtube video which gave an example that looked very similar to what I'm doing. However the one thing missing from the example in the video was that no logic was being handled by the Facade pattern. As the name implies, the Facade object was only responsible for grouping a bunch of objects together and packing the functionality in an easier to consume API.
As I continued digging I found the Mediator Pattern, and thought "Ok this is what I'm looking for, it's like the Facade pattern but it seems to also contain logic." However after seeing more and more examples, I can see that the Facade Pattern usually involves light coupling between the "child" objects. Rather than completely removing the communication between the low level objects, they instead communicate with each other using one central object, which is the Mediator. I understand that this loosens the coupling, and in some examples the Mediator functions as an EventHandler/EventManager type class which is cool, however now if another developer takes a piece of my code they're forced to use Mediators in their design or to start editing code inside classes.
Hoping someone can clarify exactly what the pattern I'm using is and would also appreciate some general advice when it comes to this kind of class design.