Unity3D:Resource.Load 定义的 AudioClip 最终为 Null 进入 AudioSource

Unity3D: Resource.Load defined AudioClip ends up Null going into the AudioSource

我想做的是在一个文件夹(在资源下)中包含一个音频文件,我可以将任何符合条件的音频文件放到指定的文件夹中,并让我的程序中的许多触发器从那个点读取(这就是为什么我下面的 AudioClip 是 public 静态的,所以我可以引用它)。目前,相同的音频文件在整个程序中都有效,但要更改文件需要在检查器中手动重新定义,我的最终客户将无法访问该文件,而且由于存在大量参考点而很乏味。

这是我目前的情况:

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");
   }
}

照原样,这就是无法播放,没有错误消息。原来 BGM 是空的。 Debug 是这样说的,但是如果我要为 BGMSource.clip.name 添加一个 Debug 调用,它将完全出错并在该 Debug.

上出现 NullReferenceException

formatted1 字符串(文件路径和名称)的调试,它确实提供了正确的文件,名为 Test.mp3 ("C:/...Resources/Audio/BGM\Test"),格式不带“.mp3”,这是从另一个站点推荐的。我确实尝试使用 .mp3 扩展名,但似乎没关系,仍然没有播放。我还尝试使用 .wav 文件和 .ogg 文件,结果相同(注意:如果我手动附加为 public AudioClip,则所有文件都很好,因为上面写的 AudioSource 也会在这种情况下播放,但正如我带头,我们不希望这种情况)。是的,所有测试音频文件都在目录 /Resources/Audio/BGM 中。

另一个网站说了一些关于添加到文件顶部的内容 [RequireComponent(typeof(AudioClip))] 或 [RequireComponent(typeof(AudioSource))] 但那没有任何作用。

最后,该程序最终将提供给无法访问源代码的组,因此他们必须能够通过删除 Resources/Audio/BGM 中的任何 .mp3 来交换音频文件以进行自动播放。

欢迎任何帮助,谢谢!

首先要注意:永远不要使用+ "/"作为系统文件路径!而是起诉 Path.Combine,它会根据 运行 在

上的平台自动插入正确的路径分隔符
string file in System.IO.Directory.GetFiles(Path.Combine(Application.dataPath, "Resources", "Audio", "BGM"))

那么请阅读Resources.Load的文档!

  1. 它要求将您的文件放在名为 Resources 的文件夹中,该文件夹已编译到应用程序构建中,因此之后无法更改。看来你是这样的。

  2. 采用完整系统路径,就像你在Directory.GetFiles returns

    An array of the full names (including paths) for the files in the specified directory

    而是希望在所有 Resources 个文件夹中有一个路径 - 是的,您可以有多个。

    比方说你把你的文件放在像

    这样的结构中
    Assets
    |--Resources
    |  |--someFile.mp3
    |
    |--SomeOtherFolder
    |  |--Resources
    |  |  |--someOtherFile.mp3
    |  |  |--ASubFolder
    |  |  |  |--yetAnotherFile.mp3
    

    然后您可以使用

    来解决这些问题
    Resources.Load<AudioClip>("someFile");
    Resources.Load<AudioClip>("someOtherFile");
    Resources.Load<AudioClip>("ASubfolder/yetAnotherFile");
    

    自从构建时所有 Resources 都打包在一起。

    所以你的情况应该是

    Resources.Load<AudioClip>("Audio/BGM/" + formatted1);
    

    您必须确保 formatted1 不是完整路径,而只是文件名!你可以简单地使用 Path.GetFileNameWithoutExtension 所以你甚至不需要你的 replace

    var formatted1 = Path.GetFileNameWithoutExtension(file);
    
  3. 不好^^

    在他们Best Practices for the Resources folderUnity自己推荐

    Don't use it!


然而

因为根本不推荐使用 Resources 我宁愿推荐:

如果您以后不想更改它们

您之后无法更改 Resources,例如替换文件。因此,如果以后无论如何都无法更改文件,那么为什么不直接在以后需要它们的地方引用它们呢?

只需将您的音频文件放在 而非 Resources 的文件夹中,然后在您需要的脚本中直接引用它们:

// Simply drag&drop the clip into this field via the Inspector in Unity
[SerializeField] private AudioClip someClip;

如果您想稍后更改它们

如果您真的希望在构建之后也能够替换它们,您可以改用 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.

我经常去:

  • 在编辑器中使用 StreamingAssets 文件夹,这样所有的东西都在项目中并通过 Application.streamingAssetsPath
  • 访问它
  • 在构建中首先检查文件是否存在于 Application.persistentDataPath
  • 如果不从Application.streamingAssetsPath复制并存入Application.persistentDataPath
  • 否则只需从 Application.persistentDataPath
  • 加载它

已修改 API 示例

[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;
        }
    }
}

也看到你这么评论了:

StreamingAssets也是一个特殊的文件夹,你可以把你想在运行时读入的文件存放在里面。它是本地。此外,此文件夹不是来自外部的 "visible",以后无法更改。

如果您以后需要更改内容(例如还保存文件),您将始终需要使用 Application.persistentDataPath