I’m working on an RPG where a lot of common utilities, like spawning a character or changing levels, need to be triggered from a variety of narrowly scoped objects. A character whose conversation triggers a cutscene doesn’t need to know anything about the cutscene system, and doesn’t care what happens after it makes the request; it just needs to find some way of requesting that a given scene be loaded and played.
To accomplish this, I ended up with something which is basically a singleton masquerading as a service locator- I made a GameManager class with a static instance that allows any object to access it at runtime, but gave it no internal functionality. All of its public-facing methods are things like SpawnCharacter(characterID, location) or LoadCutscene(cutsceneID). Aside from these methods, the only stuff it contains are private references to the cutscene manager, character database, audio system and so forth. In the case of SpawnCharacter, for instance, all the game manager actually does is take the information passed in, and directly invokes CharacterDatabase.SpawnSomebody(ID, location). It doesn’t know or care what happens, so long as the data is passed on.
This feels like relatively decent design; it’s serving as a messenger, not an actor, and any bugs caused by calls to the game manager are going to be instantly traceable back to the class those calls get routed to.
On the flip side, however, single monolithic classes that everyone talks to are practically the definition of code smell, and I’ve coupled a huge amount of game functionality through the manager just by giving it access to a bunch of unrelated manager classes.
So is this architecture a really bad idea that’s going to explode in my face as the codebase grows, or am I safe to iterate around this concept as long as the manager only serves as a messenger, and never actually grows to contain functionalities beyond passing information and requests where they need to go?