You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity3D Android构建后如何从文件夹动态加载图片与音频

Android端Unity动态加载资源的解决方案(适配你的场景切换需求)

兄弟,我之前做类似的严肃游戏项目时也踩过这个坑!编辑器里能直接读项目文件夹的资源,但打包成Android APK后,资源的存储路径和访问逻辑完全变了——你说的WWW失效大概率是路径没写对,或者没处理Android的异步加载要求。下面给你几个针对性的方案,按你的需求选:

1. 直接用StreamingAssets打包资源(适合不需要用户修改资源的场景)

StreamingAssets里的内容会原封不动打包进APK,但Android上不能用File类直接读取,必须用UnityWebRequest(WWW已经被Unity弃用了,别用了)异步加载。

第一步:整理你的资源结构

把所有需要动态加载的文件都放到Assets/StreamingAssets目录下,比如:

StreamingAssets/
  ├─ 你的场景文件夹/
  │  ├─ letters.txt
  │  ├─ 配置.csv
  │  ├─ Sprites/
  │  │  ├─ Tile_A.png
  │  │  └─ Tile_B.png
  │  └─ Audios/
  │     ├─ Sound_A.mp3
  │     └─ Sound_B.mp3

注意:Sprite图片要设置成Texture Type为Sprite (2D and UI),不然加载后转不成Sprite。

第二步:读取txt/CSV文本文件

用UnityWebRequest异步读取,兼容编辑器和Android:

IEnumerator LoadTextFile(string relativePath, Action<string> onLoaded)
{
    string fullPath;
    // 区分Android和编辑器的路径
    #if UNITY_ANDROID && !UNITY_EDITOR
        fullPath = $"jar:file://{Application.dataPath}!/assets/{relativePath}";
    #else
        fullPath = Path.Combine(Application.streamingAssetsPath, relativePath);
    #endif

    using (UnityWebRequest www = UnityWebRequest.Get(fullPath))
    {
        yield return www.SendWebRequest();

        if (www.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"加载文件失败:{www.error}");
            onLoaded?.Invoke(null);
        }
        else
        {
            // 返回读取到的文本内容
            onLoaded?.Invoke(www.downloadHandler.text);
        }
    }
}

调用示例:StartCoroutine(LoadTextFile("你的场景文件夹/letters.txt", (content) => { /* 这里写解析逻辑 */ }));

第三步:加载Sprite资源

Android上不能直接读StreamingAssets里的Sprite,得先加载Texture2D再转成Sprite:

IEnumerator LoadSprite(string spriteRelativePath, Action<Sprite> onLoaded)
{
    string fullPath;
    #if UNITY_ANDROID && !UNITY_EDITOR
        fullPath = $"jar:file://{Application.dataPath}!/assets/{spriteRelativePath}";
    #else
        fullPath = Path.Combine(Application.streamingAssetsPath, spriteRelativePath);
    #endif

    using (UnityWebRequest www = UnityWebRequestTexture.GetTexture(fullPath))
    {
        yield return www.SendWebRequest();

        if (www.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"加载Sprite失败:{www.error}");
            onLoaded?.Invoke(null);
        }
        else
        {
            Texture2D texture = DownloadHandlerTexture.GetContent(www);
            Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
            onLoaded?.Invoke(sprite);
        }
    }
}

调用后把Sprite赋值给预制体的SpriteRenderer就行。

第四步:加载音频资源

用UnityWebRequestMultimedia加载AudioClip:

IEnumerator LoadAudio(string audioRelativePath, Action<AudioClip> onLoaded)
{
    string fullPath;
    #if UNITY_ANDROID && !UNITY_EDITOR
        fullPath = $"jar:file://{Application.dataPath}!/assets/{audioRelativePath}";
    #else
        fullPath = Path.Combine(Application.streamingAssetsPath, audioRelativePath);
    #endif

    using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(fullPath, AudioType.MPEG))
    {
        yield return www.SendWebRequest();

        if (www.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"加载音频失败:{www.error}");
            onLoaded?.Invoke(null);
        }
        else
        {
            AudioClip clip = DownloadHandlerAudioClip.GetContent(www);
            onLoaded?.Invoke(clip);
        }
    }
}

2. 支持用户修改资源:用PersistentDataPath

如果需要让不懂Unity的人直接复制文件夹修改内容,StreamingAssets就不行了(因为它打包在APK里,用户没法改)。这时候可以把初始资源放到StreamingAssets,第一次启动游戏时复制到Application.persistentDataPath——这个目录是Android的外部存储,用户可以访问和修改。

核心思路:

  1. 第一次启动时,把StreamingAssets里的场景资源全部复制到PersistentDataPath
  2. 之后所有资源读取都从PersistentDataPath走,用File类直接读写(因为这个目录是可读写的)

复制资源的示例代码

IEnumerator CopyInitialResources()
{
    string sourceRoot = Path.Combine(Application.streamingAssetsPath, "你的场景文件夹");
    string targetRoot = Path.Combine(Application.persistentDataPath, "你的场景文件夹");

    // 先检查目标目录是否存在,不存在就创建
    if (!Directory.Exists(targetRoot))
    {
        Directory.CreateDirectory(targetRoot);
    }

    // 注意:Android上没法直接获取StreamingAssets的文件夹列表,所以你需要提前在StreamingAssets里放一个file_list.txt,记录所有需要复制的文件相对路径
    yield return LoadTextFile("file_list.txt", (content) =>
    {
        string[] allFiles = content.Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries);
        foreach (string filePath in allFiles)
        {
            StartCoroutine(CopySingleFile(filePath));
        }
    });
}

IEnumerator CopySingleFile(string relativePath)
{
    string sourcePath;
    #if UNITY_ANDROID && !UNITY_EDITOR
        sourcePath = $"jar:file://{Application.dataPath}!/assets/{relativePath}";
    #else
        sourcePath = Path.Combine(Application.streamingAssetsPath, relativePath);
    #endif

    string targetPath = Path.Combine(Application.persistentDataPath, relativePath);

    using (UnityWebRequest www = UnityWebRequest.Get(sourcePath))
    {
        yield return www.SendWebRequest();

        if (www.result == UnityWebRequest.Result.Success)
        {
            // 写入到PersistentDataPath
            File.WriteAllBytes(targetPath, www.downloadHandler.data);
        }
    }
}

之后读取资源时,直接用Path.Combine(Application.persistentDataPath, "你的资源路径")就行,和编辑器里的逻辑一致。

3. 进阶方案:Addressables(推荐复杂场景)

如果你的游戏资源量大,或者需要热更新,Unity的Addressables系统是最优解。它会自动处理平台差异,编辑器和移动端的加载逻辑完全一致,还支持远程资源更新。

快速上手:

  1. 在Package Manager里安装Addressables包
  2. 右键你要动态加载的资源(Sprite、音频、文本),选择Addressable -> Add to Addressables
  3. 用代码加载资源(支持异步/await):
async void LoadTileSprite(string spriteAddress)
{
    var loadHandle = Addressables.LoadAssetAsync<Sprite>(spriteAddress);
    await loadHandle.Task;

    if (loadHandle.Status == AsyncOperationStatus.Succeeded)
    {
        Sprite targetSprite = loadHandle.Result;
        // 赋值给预制体的SpriteRenderer
    }
    // 记得释放资源,避免内存泄漏
    Addressables.Release(loadHandle);
}

Addressables的好处是你不用关心平台路径,直接用资源的Addressable名称加载就行,后续要更新资源也不用重新打包APK。

避坑提醒

  • Android必须异步加载:同步读取StreamingAssets会直接失败,一定要用Coroutine或者async/await
  • 路径区分大小写:Android是Linux内核,路径大小写不匹配会加载失败,比如Tile_a.png和Tile_A.png是两个不同的文件
  • 权限问题:如果用PersistentDataPath,Android 10及以上需要申请读写权限,Unity在打包时会自动处理大部分,但最好在代码里加权限申请逻辑
  • 音频格式:尽量用MP3或WAV,避免用OGG(部分低端设备可能不支持)

内容的提问来源于stack exchange,提问作者Nin4ikP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:38:06