I am working on an audio manager and the idea is this…
I will have a AudioTracks regular system.object class that will have a list (or dictionary) of audio clips as well as information such as volume and audio channel. We use a list because we might have multiple audio clips that share the same settings, and I feel this would be a less annoying way to handle that.
I then have an AudioController MonoBehaviour component that is pretty much just a component that can hold a AudioTracks object as well as setting up an audiosource to be used.
Then there will be a static AudioManager that handles pooling and such. The pool will be pools of gameobjects will AudioControllers, but we have the choice of not using the pool if we just place an AudioController on a gameobject before hand.
The idea is to be able to just pass around the AudioTracks to any AudioController and everything will be set up properly with the new settings.
The issue I am running into is between the AudioController and AudioTracks.
Let me show some code first.
Click for code
//* = Will need to make a custom editor if we want this to work properly at runtime in the editor
public class AudioController : MonoBehaviour
{
[SerializeField] AudioTrack _audioTrack; //*
public AudioTrack audioTrack {get{return _audioTrack;} set{SetAudioTrack(value);}}
AudioSource audioSource;
void Awake()
{
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.hideFlags = HideFlags.NotEditable | HideFlags.HideInInspector;
SetAudioTrack(_audioTrack);
}
void SetAudioTrack(AudioTrack audioTrack)
{
//Here we need to take the parameters from the audiotrack and properly set things up on the audiosource and audiomanager such as volume
}
}
public class AudioTrack
{
//Other fields such as the audio clips dictionary....
[SerializeField] AudioChannel _channel; //*
public AudioChannel channel {get{return _channel;} set{SetChannel(value);}}
[Range(0f, 1f)]
[SerializeField] float _volume; //*
public float volume {get{return _volume;} set{SetVolume(value);}}
void SetChannel(AudioChannel channel)
{
//We need to set our channel, then tell the audiomanager and give them our audiosource to handle volume, but audiocontroller has our audiosource...
}
void SetVolume(float volume)
{
//We need to set our volume and then tell audiocontroller to set the audiosource volume properly, but how can we assign things in a way that we know our audiocontroller?
}
}
So the issue here is, the AudioTrack wants to know about the AudioController or the AudioController wants to know about the AudioTrack, but we cant just pass data through some public method that only the AudioController should use as thats not really good design and can break things.
I wanted to use C# events and let the AudioController assign its private methods to a OnVolumeChange and OnChannelChange event, but assigning to an event causes garbage, and since this will be used in a object pool where things will be constantly assigned and unassigned, I will have to avoid events.
I also dont want to use reflection as I am more curious on how this would be handled normally.
Nesting the AudioTrack class within the AudioController might work, but it would be annoying referencing the AudioTrack as AudioController.AudioTrack (or visa versa) or creating wrappers.
Here is an example of the mess and how I dont like how unsafe it is
Click for code
public class AudioController : MonoBehaviour
{
[SerializeField] AudioTrack _audioTrack;
public AudioTrack audioTrack {get{return _audioTrack;} set{SetAudioTrack(value);}}
AudioSource audioSource;
void Awake()
{
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.hideFlags = HideFlags.NotEditable | HideFlags.HideInInspector;
SetAudioTrack(_audioTrack);
}
void OnDestroy()
{
if(_audioTrack != null) _audioTrack.RemoveFromAudioController();
}
void SetAudioTrack(AudioTrack audioTrack)
{
if(_audioTrack != null) _audioTrack.RemoveFromAudioController();
_audioTrack = audioTrack;
if(_audioTrack != null) _audioTrack.SetAudioController(this, audioSource);
}
}
public class AudioTrack
{
//Other fields such as the audio clips dictionary....
[SerializeField] AudioChannel _channel; //will need to make a custom editor if we want these to work properly at runtime in the editor
public AudioChannel channel {get{return _channel;} set{SetChannel(value);}}
[Range(0f, 1f)]
[SerializeField] float _volume; //will need to make a custom editor if we want these to work properly at runtime in the editor
public float volume {get{return _volume;} set{SetVolume(value);}}
AudioSource audioSource;
AudioController audioController;
//I dont like how unsafe this method is...
//How do we know the audiosource is the audiosource the audiocontroller is using, and how do we make sure this audiotrack is the audiotrack on the audiocontroller (without some strange runtime check)?
public void SetAudioController(AudioController controller, AudioSource source)
{
audioSource = source;
audioController = controller;
SetVolume(_volume);
SetChannel(_channel);
}
public void RemoveFromAudioController()
{
//code for removing from audiomanager goes here
audioSource = null;
if(audioController != null) audioController.audioTrack = null;
audioController = null;
}
void SetChannel(AudioChannel channel)
{
_channel = channel;
//tell audiomanager and give them our audiosource to handle volume
}
void SetVolume(float volume)
{
volume = Mathf.Clamp01(volume);
if(audioSource != null) audioSource.volume *= (volume / _volume);
_volume = volume;
}
}
So I was going for just having the audiotrack handle everything, but the SetAudioController method is a method only the AudioController should be using. If someone calls that method thinking it would properly assign this audiotrack to the audiocontroller, they would be breaking things.
I have not gone to far into designing this, but I think I will have it be so that whenever I want to play an audiotracks clip, I call a play method within the audiotrack, which would check if we are assigned to an audiocontroller and if so then tell the audiocontroller to have the audiosource play the clip, but if we are not assigned to an audiocontroller, then to grab one from the audiomanager pool.
So I am kinda stuck on this and figure its a design flaw and am wondering how to go about handling this.