Audio Source using Actions

Hello, there, I reached the audio source introduction portion of 3d unity beginner course, I had taken the 2d course already. I was also learning about the actions available in unity providing an observer pattern to deal with all different objects.

As I am trying to use the observer pattern, how can I create one audio manager with all the audio defined in it and then place it to different actions? The confusion I have is on audio manager, is it something that is already present in unity system or we have to make one?

If there is any tutorial on actions usage by gamedev, it will be more than helpful. Thanks

Hi,

What exactly is your idea? What problem would you like to solve and why do you think that the observer pattern is suitable? If you are able to answer these questions, you have a part of the solution.

1 Like

Reason for Observer: I don’t want my code to be clustered, or in a way get unmanageable, I am trying to build my own game on sound principle so that if there need be some kind of extension, code is clean and there is no coupling around.

My idea:
I would like to have a seperate manager for each and every feature, like in audio manager I put my all audios regarding each object, and with each and every object each script should have their own methods inside that function so that no other class have any cache for other objects or components.

In Tutorial:
All the audio sources are connected to their own respective object and called upon by caching them.

My solution:
Would be to create a seperate audio manager and probably add it to camera and all the sound will be attached in it, and then whenever some action occurs from any object, it activates the sound from audio manager in camera.

Conclusion:
Okie, I think I got my solution but I have not implemented it yet as an experienced community here, my question is, is it feasible or is there any other way to do it?

I am trying to build my own game on sound principle

A design pattern does not have any self purpose. Never use any patter just for the sake of a pattern. They are not inherently good and do not improve your project automatically.

Maybe I misunderstood your idea but if the AudioManager is the publisher and the buttons are the subscribers, I don’t see why the publisher should notify all buttons just because you clicked a button.


The observer pattern could be used for the following: When the player clicks a button, you want to kill all enemies in the scene.

In the case of the event “button click”, you call the Destroy() method on all enemies in the scene. The idea behind the observer pattern is that you don’t start looking for candidates when the event rises but that you already have a list with objects on which you want to call a specific method. And all you do is to iterate over that list to call that method on all objects in the list.

I don’t see how one would want to implement this with AudioSources unless you have, for example, multiple characters that are supposed to start singing “on button click”. In that case, each singer would subscribe to your publisher. When you press the button, all singers in the list start to sing.


See also:

1 Like

I understand what you are trying to say but I think I failed at describing what I want to achieve

what I am saying is completely opposite of what you referred, instead of making audio manager the publisher, I am making it a subscriber to the player or any event script in the level.

Lets say player jumps, it is registered in its own script, when he jumps, the audio manager trigger is fired through observer pattern.

So you are saying that for audio sources, using observer pattern is a bad idea !!!

@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).

2 Likes

another pattern to learn, amazing stuff man, thanks for answering :slight_smile:

With the newer versions of C# (which newer versions of Unity uses) you can have the empty defaults in the interface, and then the interested parties only need to implement the actions they care about

public interface IActions
{
    void Jump(Vector3 position) { }
    void Shoot(Vector3 position) { }
    void Yell(Vector3 position) { }
}

// This component is only interested in the 'Shoot' action
public class AmmoUI : MonoBehaviour, IActions
{
    [SerializeField] int _maxBullets = 6;

    private int _bulletCount;

    private void Start()
    {
        _bulletCount = _maxBullets;

        var mediator = FindObjectOfType<ActionMediator>();
        mediator.Subscribe(this);
    }

    private void OnDestroy()
    {
        var mediator = FindObjectOfType<ActionMediator>();
        mediator.Unsubscribe(this);
    }

    void IActions.Shoot(Vector3 position)
    {
        _bulletCount--;
        UpdateUI();
    }

    private void UpdateUI()
    {
        // TODO: Update the ammo UI
    }
}

It’s a bad example, but it shows that you don’t need to implement all the actions if you use the new C# stuff

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms