Reputation: 25
What I'm trying to do is contain an audio file in a folder (under Resources) where I can drop any qualifying audio file in the specified folder and have the numerous triggers in my program read from that single point (which is why my AudioClip below is public static so I can reference it). Currently, the same audio file works throughout the program, but to change the file requires manual redefining in the Inspector which my eventual client won't have access to, and besides is tedious due to the numerous reference points that exist.
Here's what I have so far:
public static AudioClip BGM;
public AudioSource BGMSource;
private string formatted1;
void Start()
{
foreach(string file in System.IO.Directory.GetFiles(Application.dataPath+"/Resources/Audio/BGM"))
{
if(file.EndsWith(System.IO.Patch.GetExtension(".mp3")))
{
formatted1 = file.Replace(".mp3",string.Empty);
BGM = Resources.Load<AudioClip>(formatted1);
//BGM = (AudioClip)Resources.Load(formatted1,typeof(AudioClip)); <--same result with this
Debug.Log("found: "+formatted1);
}
}
if(BGM == null)
{
Debug.Log("Yeah, its null");
}
BGMSource.PlayOneShot(BGM, .9f);
if(BGMSource.isPlaying != true)
{
Debug.Log("I'm not playing");
}
}
So as is, this just doesn't play, no error messages. Turns out BGM is null. The Debug says as so, but if I were to add a Debug call for BGMSource.clip.name, it will fully error out with a NullReferenceException on that Debug.
The Debug for the formatted1 string (File path and name), it does present the correct file called Test.mp3 ("C:/...Resources/Audio/BGM\Test") formatted without the ".mp3" as was recommended from another site. I did try with the .mp3 extension on, didn't seem to matter, still didn't play. I also tried with a .wav file and .ogg file, same result (note: all files were fine if I attached as a public AudioClip manually as also the AudioSource as written above would play in that case, but as I lead with, we don't want that for this case). Yes, all test audio files were in the directory /Resources/Audio/BGM.
Another site said something about adding to the top of the file [RequireComponent(typeof(AudioClip))] or [RequireComponent(typeof(AudioSource))]but that did nothing.
Lastly, this program will eventually be given to a group that won't have source access so they MUST be able to swap the audio file by dropping any .mp3 in Resources/Audio/BGM for auto play.
Any help is welcome, thanks!
Upvotes: 1
Views: 2543
Reputation: 90862
First a general note: Never use + "/"
for system file paths! Rather sue Path.Combine
which automatically inserts the correct path separators according to the platform it is running on
string file in System.IO.Directory.GetFiles(Path.Combine(Application.dataPath, "Resources", "Audio", "BGM"))
Then please read the documentation of Resources.Load
!
It requires your file(s) being placed inside a folder called Resources
which is compiled into the application build and therefore can not be changed afterwards. This seems to be the case for you.
It does not take a full system path like you pass in since Directory.GetFiles
returns
An array of the full names (including paths) for the files in the specified directory
but rather expects a path within all Resources
folders - yes you can have multiple ones.
Let's say e.g. you put your files in a structure like
Assets
|--Resources
| |--someFile.mp3
|
|--SomeOtherFolder
| |--Resources
| | |--someOtherFile.mp3
| | |--ASubFolder
| | | |--yetAnotherFile.mp3
Then you would address these by using
Resources.Load<AudioClip>("someFile");
Resources.Load<AudioClip>("someOtherFile");
Resources.Load<AudioClip>("ASubfolder/yetAnotherFile");
since when build all Resources
are packed together.
So in your case it should be
Resources.Load<AudioClip>("Audio/BGM/" + formatted1);
where you have to make sure that formatted1
is not a full path but only the filename! You can simply use Path.GetFileNameWithoutExtension
so you don't even need your replace
var formatted1 = Path.GetFileNameWithoutExtension(file);
It is bad ^^
In their Best Practices for the Resources
folder Unity themselves recommend
Don't use it!
Since it is not recommneded to use the Resources
at all I would rather recommend:
You can't change the Resources
afterwards for e.g. replacing a file. So if you can't change the files later anyway, then why not rather directly reference them in the places where they are needed later?
Simply put your audio files in a folder that is not Resources
and reference them in your scripts directly where you need them:
// Simply drag&drop the clip into this field via the Inspector in Unity
[SerializeField] private AudioClip someClip;
In case you actually would like to be able to replace them later also after a build you could instead use UnityWebRequestMultimedia.GetAudioClip
which can also be used to load files from a system file on runtime. For this you wouldn't put your files into the Resources
or any other folder but rather either the StreamingAssets
or the Application.persistentDataPath
.
I usually go:
StreamingAssets
folder so all stuff lies inside the project and access it via Application.streamingAssetsPath
Application.persistentDataPath
Application.streamingAssetsPath
and store it into Application.persistentDataPath
Application.persistentDataPath
Modified API Example
[RequireComponent(typeof(AudioSource))]
public class AudioExample : MonoBehaviour
{
[SerializeField] private AudioSource _audioSource;
public List<AudioClip> LoadedAudioClips = new List<AudioClip>;
private List<UnityWebRequest> _runningWebRequests = new List<UnityWebRequest>();
private void Awake()
{
if(!_audioSource) _audioSource = GetComponent<AudioSource>();
}
private void Start()
{
StartCoroutine(GetAudioClip());
}
private IEnumerator GetAudioClip()
{
foreach(string file in System.IO.Directory.GetFiles(Path.Combine(Application.persistentDataPath, "Audio", "BGM"))
{
if(!file.EndsWith(System.IO.Patch.GetExtension(".mp3"))) continue;
UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file:///" + file, AudioType.MPEG);
{
_runningWebRequests.Add(www);
www.Send();
}
}
while(_runningWebRequests.Count > 0)
{
foreach(var www in _runningWebRequests.Where(www => www.isDone))
{
_runningWebRequests.Remove(www);
if (www.isError)
{
Debug.LogWarning(www.error);
}
else
{
var clip = DownloadHandlerAudioClip.GetContent(www);
if(clip == null)
{
Debug.LogError("Huh?!");
}
else
{
LoadedAudioClips.Add(clip);
}
}
}
yield return null;
}
}
}
Also saw you comments so:
StreamingAssets
is also a special folder you can store files in you want to read in on runtime. It is local. Also this folder is not "visible" from the outside and can not be altered later.
If you need to be able to alter stuff later (like e.g. also saving files) you will always need to use the Application.persistentDataPath
instead.
Upvotes: 2