Unity3D手游开发日记(10) - 资源打包的一些思考

本文详细介绍了Unity游戏开发中资源管理的最佳实践,包括资源分类设计、文件打包结构、共用资源处理及快速增量打包等方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unity的资源打包,如果要做完美,其实是很复杂的.那么为什么资源要打包呢,因为我们要做资源更新.没办法啊.

在我看来,完美的资源打包至少有以下几个方面要处理好:

1) 资源分类设计合理.控制包粒度,单个包不能太大.

2) 灵活的文件打包结构.支持文件和文件夹打包

3) 共用资源的完美处理.防止重复打包.

4) 快速增量打包.加快打包速度.


具体来聊一下我的一些思考.


1)资源粒度设计合理

其实就是如何划分资源.先说下我的划分.

UI  .prefab        

Model     .prefab        需要动态加载的模型,其实就是角色

Effect  .prefab        需要动态加载的特效,其实就是技能特效

Scene             .unity

Sound             .wav .mp3 .ogg

Animation      .anim           

Table              .txt

Lua                 .lua

以上的划分也比较常规,只是动作文件的划分比较特殊点.因为动作文件共用性很强,比如所有主角的几十套装备模型都是共用的一套动作,所以把动作从模型独立出来,动态加载动作,这样打包模型,就不会打包动作进去.动态的加载,也有一些讲究,原本是每个动作单独打包,用到什么动作才加载,这样粒度最小,最完美.但是由于WWW加载是异步的,不支持同步加载动作,会有很多问题,所以最后的解决方案是,把这个模型的所有动作打成了一个Bundle包,加载一个模型前,先加载这个模型的所有动作.这样来实现同步效果.


2)灵活的文件打包结构

上面提到了动作打包,我们把一个文件夹下面的所有动作打成了一个包,我们也可以单个动作打成一个包,所谓灵活,就是这个包可以支持1个或者多个主资源.我设计了3种分组类型.这样一个包对应哪些资源,就可以划分出来.

// 分组类型  
public enum GroupType  
{  
    File_Recursion,             // 按文件分组(递归遍历)  
    Directory_Recursion,        // 按文件夹分组(递归遍历)  
    File_Directory,             // 按文件和文件夹分组(第一级目录)  
}  

// 获得主资源路径  
public static Dictionary<string, List<string>> GetMainAssetPaths(string path, string[] fileExtArray, GroupType groupType)  
{  
    Dictionary<string, List<string>> paths = new Dictionary<string, List<string>>();  
    path = path.Replace('\\', '/');  
  
    if (groupType == GroupType.File_Directory)  
    {  
        string[] files = Directory.GetFiles(path);  
        string[] dictionaries = Directory.GetDirectories(path);  
  
        // 按文件分组  
        foreach (string file in files)  
        {  
            foreach (string fileExt in fileExtArray)  
            {  
                if (file.EndsWith(fileExt))  
                {  
                    List<string> list = new List<string>(1); // 数量1  
                    string filename = file.Replace(Application.dataPath, "Assets").Replace('\\', '/');  
                    list.Add(filename);  
                    string key = file.Replace('\\', '/');  
                    key = key.Substring(key.LastIndexOf('/') + 1);  
                    key = key.Substring(0, key.Length - fileExt.Length - 1);  
                    paths.Add(key, list);  
                    break;  
                }  
            }  
        }  
  
        // 按文件夹分组  
        foreach (string directory in dictionaries)  
        {  
            files = Directory.GetFiles(directory);  
            string key = directory.Replace('\\', '/');  
            key = key.Substring(key.LastIndexOf('/') + 1);  
            List<string> list = new List<string>();  
            foreach (string file in files)  
            {  
                foreach (string fileExt in fileExtArray)  
                {  
                    if (file.EndsWith(fileExt))  
                    {  
                        string filename = file.Replace(Application.dataPath, "Assets").Replace('\\', '/');  
                        list.Add(filename);  
                    }  
                }  
            }  
            paths.Add(key, list);  
        }  
    }  
    else   
    {  
        GetMainAssetPathsRecursion(paths, path, path, fileExtArray, groupType);  
    }  
  
    return paths;  
}  
  
static void GetMainAssetPathsRecursion(Dictionary<string, List<string>> paths, string startPath, string curPath, string[] fileExtArray, GroupType groupType)  
{  
    string[] files = Directory.GetFiles(curPath);  
    string[] dictionaries = Directory.GetDirectories(curPath);  
  
    foreach (string file in files)  
    {  
        foreach (string fileEnd in fileExtArray)  
        {  
            if (file.EndsWith(fileEnd))  
            {  
                string key = "";  
                string filename = file.Replace(Application.dataPath, "Assets").Replace('\\', '/');  
  
                if (groupType == GroupType.Directory_Recursion)  
                {  
                    key = curPath.Replace('\\', '/');  
                    key = key.Substring(startPath.Length + 1);  
  
                    // 按文件夹分组的话,一个Key对应多个文件.  
                    if (!paths.ContainsKey(key))  
                    {  
                        List<string> list = new List<string>();  
                        list.Add(filename);  
                        paths.Add(key, list);  
                    }  
                    else  
                    {  
                        List<string> list = paths[key];  
                        list.Add(filename);  
                    }                                                    
                }  
                else   
                {  
                    key = file.Replace('\\', '/');  
                    key = key.Substring(startPath.Length + 1);  
                    key = key.Substring(0, key.Length - fileEnd.Length - 1);  
  
                    List<string> list = new List<string>(1); // 数量1  
                    list.Add(filename);  
                    if (!paths.ContainsKey(key))  
                        paths.Add(key, list);  
                    else  
                        LogSystem.WarningLog("GetMainAssetPathsRecursion 已经包含这个key了 key:{0}", key);  
                }  
       
                break;  
            }  
        }  
    }  
  
    // 递归  
    foreach (string directory in dictionaries)  
    {  
        GetMainAssetPathsRecursion(paths, startPath, directory.Replace('\\', '/'), fileExtArray, groupType);  
    }  
}  


3)共用资源的完美处理

游戏发布的时候.整个包小一些,推广费会少很多,要减少包大小,完美处理共用资源,防止资源重复打包就很重要.

怎么完美处理呢,其实找出共用资源,用依赖打包解决就行了.怎么处理依赖,这里就不赘述了.

一些常见的共用资源:

UI:

字体和一些常见的共用图集

Model & Effect:

共用的shader

Scene:

场景共用太多了,得用工具找出来才行,我写了一个工具来查找场景共用资源.一个场景用到了,UsedCounter就加1

1.可以筛选资源的后缀和路径.

2.列表点击标题可以排序,

3.点击资源名字,可以跳转到项目资源并选中,

4.点击使用次数按钮,可以打印哪些场景用到了这个资源.

共用资源找出来了,就可以把共用资源打成一个或者几个包,


处理场景的共用资源,我看有些人从prefab层级去处理,动态加载这些共用的prefab,这种做法其实很麻烦而没必要,你得从场景里面把这些prefab删除了,才能不打包这些共用资源到场景里面.改成从资源层级去处理就简单很多,因为资源才大,而prefab其实只是一些配置文件,不占空间.


4) 快速增量打包

在开发的时候特别重要,有了增量打包,就再也不用选中某个资源来打包了,比如UI改了,重打整个UI,自动找出哪些UI改变或者增减了,一下就打好了.

Unity5有用manifest文件来做增量打包,如果是Unity4,其实自己可以做一套manifest.比较MD5就行了.

分享一下我的代码:

using System;  
using System.IO;  
using System.Collections.Generic;  
using UnityEngine;  
using UnityEditor;  
using System.Security.Cryptography;  
  
namespace Luoyinan  
{  
    // 依赖资源文件信息  
    public class DependencyAssetInfo  
    {  
        public string m_AssetMD5 { get; set; }  
        public string m_AssetMetaMD5 { get; set; }  
        public string m_AssetFileSize { get; set; } // 用来快速检查过大的依赖资源  
    }  
  
    // 包信息  
    public class AssetBundleInfo  
    {  
        public bool m_IsNeedBuild = false;  
        public string m_BundleName;  
  
        public Dictionary<string, DependencyAssetInfo> m_Dependencies = new Dictionary<string, DependencyAssetInfo>();  
  
        public string WriteToString(int id)  
        {  
            string content = "";  
  
            foreach (KeyValuePair<string, DependencyAssetInfo> pair in m_Dependencies)  
            {  
                DependencyAssetInfo info = pair.Value;  
  
                content += string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\n"  
                    , id  
                    , m_BundleName  
                    , pair.Key  
                    , info.m_AssetMD5  
                    , info.m_AssetMetaMD5  
                    , info.m_AssetFileSize  
                    );  
            }  
  
            return content;  
        }  
    }  
  
    public class AssetBundleManifestMgr : Singleton<AssetBundleManifestMgr>  
    {  
        public class MD5Info  
        {  
            public string md5;  
            public string filesize;  
  
            public MD5Info(string md5, string filesize)  
            {  
                this.md5 = md5;  
                this.filesize = filesize;  
            }  
        }  
  
        public MD5CryptoServiceProvider m_Md5Generator = new MD5CryptoServiceProvider();  
        public Dictionary<string, AssetBundleInfo> m_LastABInfos = new Dictionary<string, AssetBundleInfo>();  
        public Dictionary<string, AssetBundleInfo> m_CurABInfos = new Dictionary<string, AssetBundleInfo>();  
        public Dictionary<string, MD5Info> m_MD5s = new Dictionary<string, MD5Info>();  
        public List<string> m_ToDeletedBundles = new List<string>();  
 
        #region public  
  
        // 加载上次清单文件  
        public bool LoadManifestFile(string fileName)  
        {  
            // 清除数据  
            Clear();  
  
            fileName = fileName.Replace('\\', '/');  
            string path = GetManifestFilePath() + fileName;  
  
            if (!File.Exists(path))  
                return true;  
  
            DBC table = new DBC();  
            try  
            {  
                //table.Load(path); 这个函数只支持从表格文件目录加载文本.换成LoadFromStream.  
                StreamReader sr = File.OpenText(path);  
                table.LoadFromStream(sr);  
            }  
            catch (Exception ex)  
            {  
                LogSystem.ErrorLog(ex.ToString());  
                return false;  
            }  
  
            foreach (List<DBC_Row> list in table.HashData.Values)  
            {  
                AssetBundleInfo ab = new AssetBundleInfo();  
                ab.m_BundleName = list[0].SelectFieldByName("AssetBundleName");  
  
                for (int i = 0; i < list.Count; ++i)  
                {  
                    DependencyAssetInfo info = new DependencyAssetInfo();  
                    string name = list[i].SelectFieldByName("DependencyAssetName");  
                    info.m_AssetMD5 = list[i].SelectFieldByName("DependencyAssetMD5");  
                    info.m_AssetMetaMD5 = list[i].SelectFieldByName("DependencyAssetMetaMD5");  
                    //da.m_DependencyAssetFileSize = list[i].SelectFieldByName("DependencyAssetFileSize");  
                    ab.m_Dependencies.Add(name, info);  
                }  
  
                m_LastABInfos.Add(ab.m_BundleName, ab);  
            }  
  
            return true;  
        }  
  
        // 产生这次的打包信息.  
        public void GenerateCurAssetBundleInfo(string bundleName, string[] mainAssetNames)  
        {  
            AssetBundleInfo ab = new AssetBundleInfo();  
            ab.m_BundleName = bundleName;  
  
            // 依赖  
            string[] dependencies = AssetDatabase.GetDependencies(mainAssetNames);  
            if (dependencies != null && dependencies.Length > 0)  
            {  
                foreach (string dFile in dependencies)  
                {  
                    DependencyAssetInfo info = new DependencyAssetInfo();  
                    MD5Info md5_info = GenerateFileMD5(dFile);  
                    info.m_AssetMD5 = md5_info.md5;  
                    info.m_AssetMetaMD5 = GenerateFileMD5(dFile + ".meta").md5;  
                    info.m_AssetFileSize = md5_info.filesize;  
                    ab.m_Dependencies.Add(dFile, info);  
                }  
            }  
  
            m_CurABInfos.Add(ab.m_BundleName, ab);  
        }  
  
        // 分析对比两次打包数据  
        public void AnalyzeAssetBundleInfo()  
        {  
            // 1.删除多余的包  
            foreach (KeyValuePair<string, AssetBundleInfo> pair in m_LastABInfos)  
            {  
                string lastBundleName = pair.Key;  
                if (!m_CurABInfos.ContainsKey(lastBundleName))  
                {  
                    if (!SelectSceneEditor.m_SelectSceneMode) // 如果是选择场景打包,不要删除未打包场景.  
                    {  
                        m_ToDeletedBundles.Add(lastBundleName);  
                        LogSystem.DebugLog("需要删除的包: " + lastBundleName);  
                    }                    
                }  
            }  
  
            // 2.哪些包需要打包  
            int i = 0;  
            foreach (KeyValuePair<string, AssetBundleInfo> pair in m_CurABInfos)  
            {  
                string curBundleName = pair.Key;  
                AssetBundleInfo curAB = pair.Value;  
  
                if (!m_LastABInfos.ContainsKey(curBundleName))  
                {  
                    // 新增加的包,需要打包.  
                    curAB.m_IsNeedBuild = true;  
                    LogSystem.DebugLog("新增加的包: " + curBundleName);  
                    ++i;  
                }  
                else  
                {  
                    curAB = m_CurABInfos[curBundleName];  
                    AssetBundleInfo lastAB = m_LastABInfos[curBundleName];  
  
                    // 原来的包被谁手贱删除了,需要打包.  
                    if (!File.Exists(lastAB.m_BundleName))  
                    {  
                        curAB.m_IsNeedBuild = true;  
                        LogSystem.DebugLog("原来的包被谁手贱删除了,需要重新打包: " + curBundleName);  
                        ++i;  
                        continue;  
                    }  
  
                    // 改变了,需要打包.  
                    if (IsDependenciesChanged(curAB, lastAB))  
                    {  
                        curAB.m_IsNeedBuild = true;  
                        LogSystem.DebugLog("依赖资源改变的包: " + curBundleName);  
                        ++i;  
                    }               
                }  
            }  
  
            LogSystem.DebugLog("需要打包的的数量 : {0}", i);  
        }  
  
        // 是否需要重新打包.  
        public bool IsNeedBuild(string bundleName)  
        {  
            if (!m_CurABInfos.ContainsKey(bundleName))  
            {  
                LogSystem.ErrorLog("请先调用GenerateCurAssetBundleInfo!!! bundleName : {0}", bundleName);  
                return false;  
            }  
  
            return m_CurABInfos[bundleName].m_IsNeedBuild;  
        }  
  
        // 打包结束后的处理。主要是删除多余文件和保存数据。  
        public void PostBundleBuild(string fileName)  
        {  
            DeleteUnusedAssetBundles();  
            SaveManifestFile(fileName);  
        }  
 
        #endregion  
 
        #region private  
  
        // 清空  
        private void Clear()  
        {  
            m_LastABInfos.Clear();  
            m_CurABInfos.Clear();  
            m_MD5s.Clear();  
            m_ToDeletedBundles.Clear();  
        }  
  
        // 保存清单文件  
        private void SaveManifestFile(string fileName)  
        {  
            string header = "ID\tAssetBundleName\tDependencyAssetName\tDependencyAssetMD5\tDependencyAssetMetaMD5\tDependencyAssetFileSize";  
            string type = "INT\tSTRING\tSTRING\tSTRING\tSTRING\tSTRING";  
            string content = header + "\n" + type + "\n";  
  
            // 保存这次的打包信息.  
            int id = 0;  
            foreach (KeyValuePair<string, AssetBundleInfo> pair in m_CurABInfos)  
            {  
                content += pair.Value.WriteToString(id);  
                ++id;  
            }  
  
            // 如果是选择场景打包,不要删除未打包场景的清单文件信息  
            if (SelectSceneEditor.m_SelectSceneMode)   
            {  
                foreach (KeyValuePair<string, AssetBundleInfo> pair in m_LastABInfos)  
                {  
                    if (!m_CurABInfos.ContainsKey(pair.Key))  
                        content += pair.Value.WriteToString(id);  
  
                    ++id;  
                }  
            }  
  
            string path = GetManifestFilePath();  
            if (!Directory.Exists(path))  
                Directory.CreateDirectory(path);  
            File.WriteAllText(path + fileName, content);  
        }  
  
        // 清单文件路径  
        private string GetManifestFilePath()  
        {  
            string assetPath = Application.dataPath;  
            string projPath = assetPath.Remove(assetPath.IndexOf("/Assets")) + "/";  
            string folder = "Build/Luoyinan_Unknown";  
#if UNITY_STANDALONE_WIN  
            folder = "Build/Luoyinan_StandaloneWindows";  
#elif UNITY_ANDROID  
            folder = "Build/Luoyinan_Android";  
#elif UNITY_WP8  
#else  
            folder = "Build/Luoyinan_iPhone";  
#endif  
            if (!Directory.Exists(projPath + folder))  
            {  
                Debug.Log(projPath + folder);  
                Directory.CreateDirectory(projPath + folder);  
            }  
            folder += "/AssetBundleManifest/";  
  
            return projPath + folder;  
        }  
  
        // 产生MD5  
        private MD5Info GenerateFileMD5(string fileName)  
        {  
            if (m_MD5s.ContainsKey(fileName))  
            {  
                return m_MD5s[fileName];  
            }  
            else  
            {  
                string md5 = "";  
                int size = 0;  
                using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))  
                {  
                    byte[] hash = m_Md5Generator.ComputeHash(fs);  
                    md5 = BitConverter.ToString(hash);  
                    size = (int)((float)fs.Length / 1024.0f);   
                }  
  
                MD5Info info = new MD5Info(md5, size.ToString());  
                m_MD5s.Add(fileName, info);  
                return info;  
            }  
        }  
  
        // 删除多余包  
        private void DeleteUnusedAssetBundles()  
        {  
            if (m_ToDeletedBundles.Count > 0)  
            {  
                for (int i = 0; i < m_ToDeletedBundles.Count; ++i)  
                {  
                    FileInfo file = new FileInfo(m_ToDeletedBundles[i]);  
                    if (file.Exists)  
                    {  
                        File.Delete(m_ToDeletedBundles[i]);  
                    }  
                }  
  
                AssetDatabase.SaveAssets();  
                EditorApplication.SaveAssets();  
                AssetDatabase.Refresh();  
            }  
        }  
  
        // 依赖资源是否改变  
        public bool IsDependenciesChanged(AssetBundleInfo curAB, AssetBundleInfo lastAB)  
        {  
            foreach (KeyValuePair<string, DependencyAssetInfo> pair in curAB.m_Dependencies)  
            {  
                if (!lastAB.m_Dependencies.ContainsKey(pair.Key))  
                {  
                    // 新的依赖项  
                    LogSystem.DebugLog("新的依赖项目 : {0}", pair.Key);  
                    return true;  
                }  
                else  
                {  
                    // 依赖项改变  
                    DependencyAssetInfo lastInfo = lastAB.m_Dependencies[pair.Key];  
                    DependencyAssetInfo curInfo = pair.Value;  
                    if (curInfo.m_AssetMD5 != lastInfo.m_AssetMD5  
                        || curInfo.m_AssetMetaMD5 != lastInfo.m_AssetMetaMD5)  
                    {  
                        if (pair.Key.EndsWith(".cs"))  
                        {  
                            LogSystem.DebugLog("依赖项改变.但是是脚本.暂时不重新打包 : {0}", pair.Key);  
                        }                      
                        else  
                        {  
                            LogSystem.DebugLog("依赖项改变 : {0}", pair.Key);  
                            return true;  
                        }  
                    }  
                }  
            }  
  
            return false;  
        }  
 
        #endregion  
  
    }  
} 


依赖打包的一个例子部分代码:



static void BuildBundleDependencies(string selectPath, string outputPath, BuildTarget buildTarget, string[] fileEndArray, GroupType groupType, string manifestFile, string dependBundleName, List<FilterInfo> dependFilters)  
{  
    // 主资源路径  
    Dictionary<string, List<string>> paths = GetMainAssetPaths(Application.dataPath + selectPath, fileEndArray, groupType);  
  
    // 搜集依赖  
    List<string> dependPaths = GetDependAssetPaths(paths, dependFilters, false);  
    Object[] dependObjects = GetMainAssetObjects(dependPaths);  
  
    // 快速增量打包  
    if (m_UseFastBuild)  
    {  
        EditorUtility.DisplayProgressBar("增量打包 " + selectPath, "开始分析两次打包数据...", 0);  
        LogSystem.DebugLog("-----------------开始新的增量打包.路径: {0}", selectPath);  
  
        // 1.加载上次打包数据  
        if (!AssetBundleManifestMgr.Instance.LoadManifestFile(manifestFile))  
            return;  
  
        // 2.产生这次打包数据  
        foreach (KeyValuePair<string, List<string>> pair in paths)  
        {  
            List<string> list = pair.Value;  
            string[] mainAssetNames = new string[list.Count];  
            for (int i = 0; i < list.Count; ++i)  
            {  
                mainAssetNames[i] = list[i];  
            }  
            string bundleName = outputPath + "/" + pair.Key + ".data";  
            AssetBundleManifestMgr.Instance.GenerateCurAssetBundleInfo(bundleName, mainAssetNames);  
        }  
  
        // 3.分析两次打包数据  
        AssetBundleManifestMgr.Instance.AnalyzeAssetBundleInfo();  
    }  
  
    // 打依赖包  
    BuildAssetBundleOptions optionsDepend = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.CompleteAssets;  
    BuildPipeline.PushAssetDependencies();  
    if (dependObjects != null)  
    {  
        string bundleName = outputPath + "/" + dependBundleName;  
        bool needbuild = true; // 依赖包每次重打  
        if (needbuild)  
        {  
            Util.CheckTargetPath(bundleName);  
            EditorUtility.DisplayProgressBar("依赖打包", "开始打包:" + dependBundleName, 0);  
            BuildPipeline.BuildAssetBundle(null, dependObjects, bundleName, optionsDepend, buildTarget);  
        }  
    }  
  
    // 打普通包  
    BuildAssetBundleOptions optionsNormal = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.CollectDependencies;  
    foreach (KeyValuePair<string, List<string>> pair in paths)  
    {  
        bool needbuild = false;  
        string bundleName = outputPath + "/" + pair.Key + ".data";  
        if (!m_UseFastBuild || AssetBundleManifestMgr.Instance.IsNeedBuild(bundleName))  
            needbuild = true;  
  
        if (needbuild)  
        {  
            BuildPipeline.PushAssetDependencies();  
  
            EditorUtility.DisplayProgressBar("依赖打包", "开始打包:" + pair.Key, 0);  
            Util.CheckTargetPath(bundleName);  
            Object[] mainAssetObjects = GetMainAssetObjects(pair.Value);  
            if (mainAssetObjects.Length == 1)  
                BuildPipeline.BuildAssetBundle(mainAssetObjects[0], null, bundleName, optionsNormal, buildTarget);  
            else  
                BuildPipeline.BuildAssetBundle(null, mainAssetObjects, bundleName, optionsNormal, buildTarget);  
  
            BuildPipeline.PopAssetDependencies();  
        }  
    }  
  
    BuildPipeline.PopAssetDependencies();  
  
    // 快速增量打包  
    if (m_UseFastBuild)  
        AssetBundleManifestMgr.Instance.PostBundleBuild(manifestFile);  
} 
原文地址:http://blog.youkuaiyun.com/qq18052887/article/details/52584058
<think>我们正在处理一个关于Unity中使用Directory.GetDirectories方法的问题。根据用户要求,我们需要提供教程或代码示例。 引用[2]提供了关于Directory类的基本信息,包括它用于目录操作(如创建、删除、移动等),并提到它包含静态方法,执行安全检查。 另外,引用[1]和引用[3]虽然提到了Unity中的资源管理,但与本问题关系不大,因此我们主要参考引用[2]。 用户想要的是在Unity中使用Directory.GetDirectories的教程或代码示例。该方法用于获取指定目录下的所有子目录的路径。 注意:在Unity中,我们通常使用Application.dataPath等路径来访问项目内的目录。但是,由于Unity对文件系统的访问有一定的限制(尤其是在WebGL等平台),所以需要注意使用场合。 我们将按照以下步骤进行: 1. 介绍Directory.GetDirectories方法的基本用法。 2. 提供在Unity中使用的示例代码,并解释需要注意的地方(如路径的获取)。 3. 提醒用户关于不同平台下的权限问题。 方法签名: public static string[] GetDirectories(string path); public static string[] GetDirectories(string path, string searchPattern); public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption); 参数说明: path: 要搜索的目录的相对路径或绝对路径。 searchPattern: 要与目录名匹配的搜索字符串。此参数可以包含有效文本路径和通配符(*和?),但不支持正则表达式。 searchOption: 指定搜索操作是应包含所有子目录还是仅包含当前目录(SearchOption.AllDirectories或SearchOption.TopDirectoryOnly)。 在Unity中,我们通常使用Application.dataPath来获取Assets目录的绝对路径。例如,我们想获取Assets目录下所有子目录的路径。 示例代码: string[] directories = Directory.GetDirectories(Application.dataPath); 但是,如果我们想搜索特定模式(比如以"Scene"开头的目录)并且包含子目录,可以这样: string[] directories = Directory.GetDirectories(Application.dataPath, "Scene*", SearchOption.AllDirectories); 注意:在Unity编辑器中进行文件操作时,如果修改了Assets目录下的文件结构(如创建、删除、移动目录或文件),需要调用AssetDatabase.Refresh()来刷新Unity资源数据库。 另外,在运行时(游戏构建后)使用此方法需要谨慎,因为某些平台(如WebGL)可能不支持文件系统的访问。 下面是一个完整的示例,展示如何在Unity编辑器脚本中使用Directory.GetDirectories,并打印出所有子目录的路径。 注意:这个示例脚本可以放在Editor文件夹下,作为编辑器工具使用。</think>在Unity中使用`Directory.GetDirectories`方法可以获取指定目录下的所有子目录路径。以下是详细教程和代码示例: ### 1. **基本用法** ```csharp using UnityEngine; using System.IO; public class DirectoryExample : MonoBehaviour { void Start() { // 获取Assets目录下所有直接子目录 string[] directories = Directory.GetDirectories(Application.dataPath); foreach (string dir in directories) { Debug.Log("子目录: " + dir); } } } ``` - `Application.dataPath` 是Unity项目的`Assets`目录绝对路径(例如:`C:/YourProject/Assets`) - 输出示例: ``` 子目录: C:/YourProject/Assets/Scenes 子目录: C:/YourProject/Assets/Scripts ``` --- ### 2. **进阶用法:搜索过滤** ```csharp // 搜索名称包含"Tex"的子目录(不区分大小写) string[] texDirs = Directory.GetDirectories( Application.dataPath, "*Tex*", SearchOption.AllDirectories // 包含所有嵌套子目录 ); // 搜索名称以"UI"开头的子目录 string[] uiDirs = Directory.GetDirectories( Application.streamingAssetsPath, "UI*" ); ``` - **参数说明**: - `string path`:目标目录路径 - `string searchPattern`:通配符过滤(`*`匹配任意字符,`?`匹配单个字符) - `SearchOption`:`TopDirectoryOnly`(默认)或`AllDirectories`(递归搜索) --- ### 3. **跨平台路径处理** ```csharp // 正确处理不同操作系统的路径分隔符 string targetPath = Path.Combine(Application.streamingAssetsPath, "Resources"); if (Directory.Exists(targetPath)) { string[] subDirs = Directory.GetDirectories(targetPath); // ... } ``` - **关键点**: 1. 使用`Path.Combine()`而非字符串拼接,避免路径分隔符问题(Windows用`\`,Mac/Linux用`/`) 2. 操作前用`Directory.Exists()`检查目录是否存在[^2] --- ### 4. **编辑器扩展示例** 在`Editor`文件夹下创建工具窗口: ```csharp using UnityEditor; using UnityEngine; using System.IO; public class DirectoryTools : EditorWindow { [MenuItem("Tools/目录扫描器")] static void ShowWindow() => GetWindow<DirectoryTools>("目录工具"); void OnGUI() { if (GUILayout.Button("扫描Assets子目录")) { ScanDirectories(Application.dataPath); } } void ScanDirectories(string path) { string[] dirs = Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly); foreach (string dir in dirs) { string relativePath = "Assets" + dir.Replace(Application.dataPath, ""); Debug.Log($"检测到目录: {relativePath}"); // 可选:高亮选中目录 Selection.activeObject = AssetDatabase.LoadAssetAtPath<Object>(relativePath); } } } ``` - 功能:点击按钮扫描并高亮显示`Assets`下所有一级子目录 - 注意:编辑器脚本需放在`Assets/Editor`目录 --- ### **注意事项** 1. **平台限制**: - 在WebGL、iOS等平台**无法直接使用**文件系统操作 - 替代方案:`Application.streamingAssetsPath`目录内容只读(需用`UnityWebRequest`加载) 2. **路径权限**: - 访问`Application.dataPath`父目录可能触发系统安全警告 - 推荐操作范围:`Application.dataPath`、`Application.streamingAssetsPath`、`Application.persistentDataPath` 3. **资源刷新**: - 修改目录结构后需调用`AssetDatabase.Refresh()`更新Unity资源数据库 > ⚠️ **重要安全提示** > 静态`Directory`方法每次调用都会执行安全检查,若需频繁操作目录,改用`DirectoryInfo`实例更高效[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值