The approach and some evolutions / pitfalls
Fairly standard. To give your approach a handle of sorts: you want to centralise most or all interactions to go through some high level (if not top level) controller, say Environment
, which reduces coupling. We'll call it something like "top level interactions mediator / intermediary / proxy". See associated design patterns.
The problem comes in when that top level, generalised controller Environment
becomes too cluttered, which will usually happen quickly in such a general class.
This is where you begin factoring Environment
into new subsystems, delegating its responsibilities further down the control tree or hierarchy.
For example, if trading is a possiblity, then Environment
, which may receive a message from a player requesting to trade, may delegate these messages down to a TradingSystem
which acts as intermediary for the exchange of items between the two entities in question, which could be actual trading, looting a corpse, or pickpocketing, depending on the parameters in that message; this would probably be reflected in a similar UI between the three styles of interaction. TradingSystem
may fire a message when that UI closes, indicating that the interaction is complete, which is picked up by Environment
so that it can, for example, ensure that corpse can't be looted again, or even remove the body altogether once looted. TradingSystem
can delegate control down, even further.
What we typically don't want is TradingSystem
having a reference back up to its controller Environment
. This increases coupling, and can make it difficult to trace what is controlling what, since subordinate controllers can (via method call) directly cause a parent controller to react, rather than requesting an action on part of a parent by notifying them via event / message / signal - you suggested choose to raise or player entities can choose to respond to an event
- especially true for higher level controllers which should never have to listen to subordinates.
EDIT re question about messaging coupling The Observer Pattern, AKA pub-sub, loosens coupling in the following way: Publisher
will have a list of Subscriber
s to be notified. These references are "loose" because they refer to type Subscriber
which comes from a generic list of Subscribers
which the Publisher
holds. Publisher
thus has no idea what the actual, concrete types (such as TradingSystem
or Environment
) these objects are, so it cannot call public methods on them, meddle with their public members, etc. And because the Publisher
-Subscriber
is intrinsically limited in functionality, it should remain the same throughout development. So no need to worry about these Subscriber
s changing and breaking Publisher
code.
Will this be overkill?
Not really, but...
I'd always take the simplest approach for now, until that starts to get in the way of efficient development. Then I'd start formulating a plan as to how to solve the problem, systematically. As this plan is crystallising in my mind, and assuming the project is not on a schedule e.g. for a client where other tasks take priority, I'd tie up loose ends on current tasks, merge into the base branch, create a new branch off this base, and implement the planned solution in full.
I would not try to preempt what those problems might be as that is nearly always a waste of time and effort. It may be better to get started on making your game and do the simplest thing that works (for example, direct method calls or a modifying a globally-accessible array of message slots you can read and write as and when), for the time being.
Conclusion
It's good to stay on top of things, and working in "difficult" code can be tedious, but sometimes it is worth the wait to see all the potential ways in which you have problems with a given (sub)system, and the ways in which you could solve them all at once, so that when you finally do fix them, you do so as effectively and universally as possible for the current stage of development.
In the meantime, do the simplest thing that works.