@Nina is right, the observer pattern is not quite what you want here. The audio manager would have to subscribe to everything that may possibly want to make a sound. It will become a maintenance nightmare. The observer pattern is basically âone thing notifying many thingsâ and what you want is âmany things notifying one thingâ - the complete opposite.
What may work is something like the Mediator pattern. You would have a mediator that defines every action that could possibly make a sound, like Jump, Shoot, Yell, etc. If the player is going to make sounds, it will get a reference to this mediator. The mediator could implement the observer pattern, and the audio manager would only subscribe to the mediator. When the player jumps, it will call the corresponding method on the mediator which, in turn, notifies all the observers (eg. audio manager) of this jump - now that I think of it, itâs a bit like the new input system.
Hereâs something I quickly whipped up. Itâs just an example.
// All the relevant actions, and details that may matter
public interface IActions
{
void Jump(Vector3 position);
void Shoot(Vector3 position);
void Yell(Vector3 position);
}
// The mediator doesn't _have_ to implement IActions
// I did it to ensure that it matches the observers
// A scene should have only one of these and if any
// observers are persistent, this may have to be, too
public class ActionMediator : MonoBehaviour, IActions
{
// observer pattern bits
private List<IActions> _observers = new List<IActions>();
public void Subscribe(IActions observer)
{
if (_observers.Contains(observer)) return;
_observers.Add(observer);
}
public void Unsubscribe(IActions observer)
{
_observers.Remove(observer);
}
// mediator pattern bits
public void Jump(Vector3 position)
{
Notify(observer => observer.Jump(position));
}
public void Shoot(Vector3 position)
{
Notify(observer => observer.Shoot(position));
}
public void Yell(Vector3 position)
{
Notify(observer => observer.Yell(position));
}
private void Notify(Action<IActions> action)
{
foreach(var observer in _observers)
{
action?.Invoke(observer);
}
}
}
public class AudioManager : MonoBehaviour, IActions
{
private void Start()
{
var mediator = FindObjectOfType<ActionMediator>();
mediator.Subscribe(this);
}
private void OnDestroy()
{
var mediator = FindObjectOfType<ActionMediator>();
mediator.Unsubscribe(this);
}
void IActions.Jump(Vector3 position)
{
// TODO: Play jump sound
}
void IActions.Shoot(Vector3 position)
{
// TODO: Play shoot sound
}
void IActions.Yell(Vector3 position)
{
// TODO: Play yell sound
}
}
public class Player : MonoBehaviour
{
private ActionMediator _actionMediator;
private void Awake()
{
_actionMediator = FindObjectOfType<ActionMediator>();
}
public void Jump()
{
// TODO: Make the player jump
// send the action
_actionMediator.Jump(transform.position);
}
public void Shoot()
{
// TODO: Make the player shoot
// send the action
_actionMediator.Shoot(transform.position);
}
public void Yell()
{
// TODO: Make the player yell
// send the action
_actionMediator.Yell(transform.position);
}
}
It was written in-post so there may be typos. Curse you intellisense laziness
This decouples the player and audio manager from each other. If the audio manager is removed, the player will still work, there just wonât be any sound.
Anything that may be interested in the actions can subscribe to the mediator and be notified of those actions. It doesnât just have to be the audio manager. But it also means that every possible action would be added to the interface and implemented in any interested party, even if they donât care about some of the actions (in which case theyâd just do nothing).