Unity构建项目中如何加载Resources文件夹内子目录的资源?
我的Unity项目中,Resources文件夹下有多层子文件夹,每个子文件夹包含一张或多张精灵表(目录结构示意:Resources/[武器名]/[配件类型]/精灵表.png)。
为了加载这些精灵,我写了代码读取目录结构,把内容存在Dictionary<string, List<Sprite[]>>里——key是配件名称,value是该配件下的精灵数组列表。代码里用Resources.LoadAll<Sprite>(spriteToLoad)加载精灵,路径是相对Resources的路径(比如M4A1/Barrel/Normal_barrels),同时用DirectoryInfo遍历本地文件夹。
代码如下:
// Dictionary to store for each accessory its sprite arrays weaponAccessories = new Dictionary<string, List<Sprite[]>>(); var directoryInfo = new DirectoryInfo(@"Assets\\Resources\\" + weaponName); DirectoryInfo[] dirs = directoryInfo.GetDirectories(); foreach (DirectoryInfo dir in dirs) { // Store every list of sprites present in this dir List<Sprite[]> sprites = new List<Sprite[]>(); // Searching for sprite sheets inside this dir FileInfo[] files = dir.GetFiles(); foreach (FileInfo file in files) // 原代码此处笔误,已修正为files { // Formatting path var spritesToLoad = weaponName + "/" + dir.Name + "/" + file.Name.Replace(".png", ""); // Adding these sprites into sprite arrays for this dir (accessory) sprites.Add(Resources.LoadAll<Sprite>(spritesToLoad)); } // Add <dir, list of sprite arrays> key-value couple into dictionary weaponAccessories.Add(dir.Name, sprites); }
这段代码在编辑器里能正常运行,但打包后完全加载不到精灵——因为构建后Unity会把Resources目录的内容打包整合,本地不存在原来的文件夹结构,DirectoryInfo根本找不到路径。
想问:怎么在运行时正确访问这些资源?有没有办法在构建后遍历Resources的内部子目录,或者换其他实现方式?
方法1:用Resources.LoadAll遍历父路径,替代DirectoryInfo
Unity的Resources.LoadAll可以直接加载某个路径下的所有资源(包括子文件夹内容),且构建后依然有效。你可以先加载武器目录下的所有Sprite,再按路径/命名规则分组:
weaponAccessories = new Dictionary<string, List<Sprite[]>>(); // 加载武器目录下所有Sprite(包含子文件夹内的) Sprite[] allSprites = Resources.LoadAll<Sprite>(weaponName); // 按配件名称(子目录名)分组 var groupedByAccessory = allSprites.GroupBy(sprite => Path.GetDirectoryName(sprite.name).Split('/')[1] // 假设Sprite名称路径为 [武器名]/[配件名]/[精灵表名]_帧序号 ); foreach (var accessoryGroup in groupedByAccessory) { string accessoryName = accessoryGroup.Key; // 再按精灵表分组,将同一张精灵表的Sprite整理为数组 var spriteSheetGroups = accessoryGroup.GroupBy(sprite => Path.GetFileNameWithoutExtension(sprite.name).Split('_')[0] ); List<Sprite[]> spriteSheets = new List<Sprite[]>(); foreach (var sheetGroup in spriteSheetGroups) { spriteSheets.Add(sheetGroup.ToArray()); } weaponAccessories.Add(accessoryName, spriteSheets); }
注意:此方法依赖精灵的命名规则(SpriteSheet切分后的精灵会保留原文件名前缀),如果你的命名逻辑不同,需要调整分组代码。
方法2:使用Addressable Assets系统(推荐)
这是Unity官方推荐的现代资源管理方案,比Resources灵活得多,支持构建后动态加载、分组管理,还能避免Resources打包体积过大的问题。
步骤大致:
- 在Package Manager中安装
Addressables包 - 把Resources里的精灵表文件夹标记为Addressable,设置分组/标签(比如按
[武器名]_[配件名]设置标签) - 运行时通过标签加载资源:
// 异步加载指定配件下的所有精灵 AsyncOperationHandle<IList<Sprite>> handle = Addressables.LoadAssetsAsync<Sprite>( label: $"{weaponName}_{accessoryName}", // 匹配资源标签 sprite => { /* 可选:单个资源加载完成的回调 */ } ); handle.Completed += op => { if (op.Status == AsyncOperationStatus.Succeeded) { // 按精灵表分组并存入字典 var groupedSheets = op.Result.GroupBy(sprite => Path.GetFileNameWithoutExtension(sprite.name).Split('_')[0] ); List<Sprite[]> sheets = groupedSheets.Select(g => g.ToArray()).ToList(); weaponAccessories.Add(accessoryName, sheets); } };
Addressables还支持异步加载、缓存、热更新,适合中大型项目。
方法3:用ScriptableObject预存资源引用
如果你的资源数量不多、结构固定,可以提前在编辑器里把精灵表的引用存入ScriptableObject,运行时直接读取:
- 创建ScriptableObject类:
[CreateAssetMenu(fileName = "WeaponAccessoryData", menuName = "Weapon/Accessory Data")] public class WeaponAccessoryData : ScriptableObject { public List<AccessorySpriteGroup> accessoryGroups; } [System.Serializable] public class AccessorySpriteGroup { public string accessoryName; public List<Sprite[]> spriteSheets; }
- 在编辑器里创建该ScriptableObject的实例,手动将每个配件的精灵表拖入对应字段
- 运行时加载并读取:
WeaponAccessoryData data = Resources.Load<WeaponAccessoryData>($"{weaponName}/AccessoryData"); if (data != null) { foreach (var group in data.accessoryGroups) { weaponAccessories.Add(group.accessoryName, group.spriteSheets); } }
此方法逻辑最简单,适合小型固定资源项目。
方法4:使用AssetBundle
把每个武器的配件资源打包成AssetBundle,运行时加载AB包后取出资源:
- 在编辑器里给资源设置AssetBundle标签(比如
m4a1_accessories) - 用
BuildPipeline.BuildAssetBundles打包资源(可写编辑器扩展一键打包) - 运行时加载AB包:
// 假设AB包放在StreamingAssets目录下 string abPath = Path.Combine(Application.streamingAssetsPath, "m4a1_accessories"); AssetBundle ab = AssetBundle.LoadFromFile(abPath); if (ab != null) { // 加载包内所有Sprite Sprite[] allSprites = ab.LoadAllAssets<Sprite>(); // 后续分组逻辑同方法1 ab.Unload(false); // 用完卸载AB包,false表示保留已加载的资源 }
AssetBundle适合需要分模块加载、热更新的场景,但管理成本比Addressables高。
内容的提问来源于stack exchange,提问作者Wuagliono

