Unity编辑器界面深度解析与商业级工作流实践

第3章 Unity编辑器架构解析与生产环境配置

3.1 商业级项目管理方法论

3.1.1 项目创建策略与模板架构设计

商业游戏项目创建不是简单的点击操作,而是涉及技术栈选择、架构设计和团队协作的复杂决策过程。Unity提供了多种项目模板,但商业项目通常需要定制化的项目结构。

创建项目时需要考虑的关键要素:

  1. 渲染管线选择:URP(通用渲染管线)适合移动端和跨平台项目,HDRP(高清渲染管线)适合PC和主机项目,内置管线适合特定兼容性需求
  2. 输入系统:传统Input Manager或新的Input System Package
  3. 2D/3D混合架构:支持多维度游戏体验
  4. 物理引擎:Unity Physics、Havok Physics或第三方解决方案
using UnityEngine;
using System.IO;
using System.Collections.Generic;

// 商业项目创建向导类
public class CommercialProjectCreator : MonoBehaviour
{
    [System.Serializable]
    public class ProjectTemplate
    {
        public string templateName;
        public string description;
        public RenderPipelineType renderPipeline;
        public InputSystemType inputSystem;
        public bool includeNetworking;
        public bool includeUIElements;
        public string[] requiredPackages;
        public DirectoryStructure directoryStructure;
    }

    [System.Serializable]
    public class DirectoryStructure
    {
        public string rootFolder;
        public string[] subFolders;
        public Dictionary<string, string[]> folderDescriptions;
    }

    public enum RenderPipelineType
    {
        BuiltIn,
        Universal,
        HighDefinition
    }

    public enum InputSystemType
    {
        Legacy,
        NewInputSystem,
        Both
    }

    [SerializeField]
    private List<ProjectTemplate> availableTemplates = new List<ProjectTemplate>();

    [SerializeField]
    private ProjectTemplate selectedTemplate;

    // 初始化默认模板
    private void Reset()
    {
        availableTemplates = new List<ProjectTemplate>
        {
            new ProjectTemplate
            {
                templateName = "移动端RPG模板",
                description = "适用于手机平台的角色扮演游戏,包含URP、寻路、存档系统",
                renderPipeline = RenderPipelineType.Universal,
                inputSystem = InputSystemType.NewInputSystem,
                includeNetworking = false,
                includeUIElements = true,
                requiredPackages = new string[] 
                { 
                    "com.unity.render-pipelines.universal",
                    "com.unity.inputsystem",
                    "com.unity.ai.navigation"
                },
                directoryStructure = new DirectoryStructure
                {
                    rootFolder = "Assets",
                    subFolders = new string[]
                    {
                        "Scripts",
                        "Prefabs",
                        "Scenes",
                        "Art",
                        "Audio",
                        "UI",
                        "Data",
                        "ThirdParty",
                        "Editor"
                    },
                    folderDescriptions = new Dictionary<string, string[]>
                    {
                        { "Scripts", new string[] { "Core", "Gameplay", "UI", "Managers", "Utilities" } },
                        { "Art", new string[] { "Materials", "Textures", "Models", "Animations", "Shaders" } },
                        { "Data", new string[] { "ScriptableObjects", "JSON", "Binary", "Addressables" } }
                    }
                }
            },
            new ProjectTemplate
            {
                templateName = "PC端FPS模板",
                description = "适用于PC平台的第一人称射击游戏,包含HDRP、高级物理、网络同步",
                renderPipeline = RenderPipelineType.HighDefinition,
                inputSystem = InputSystemType.NewInputSystem,
                includeNetworking = true,
                includeUIElements = true,
                requiredPackages = new string[]
                {
                    "com.unity.render-pipelines.high-definition",
                    "com.unity.inputsystem",
                    "com.unity.netcode",
                    "com.unity.physics"
                },
                directoryStructure = new DirectoryStructure
                {
                    rootFolder = "Assets",
                    subFolders = new string[]
                    {
                        "Scripts",
                        "Content",
                        "Levels",
                        "Characters",
                        "Weapons",
                        "UI",
                        "Networking",
                        "Audio",
                        "Config"
                    }
                }
            }
        };
    }

    // 模拟项目创建过程
    public void CreateProjectWithTemplate(ProjectTemplate template, string projectPath)
    {
        if (string.IsNullOrEmpty(projectPath))
        {
            Debug.LogError("项目路径不能为空");
            return;
        }

        if (!Directory.Exists(projectPath))
        {
            Directory.CreateDirectory(projectPath);
        }

        // 创建目录结构
        CreateDirectoryStructure(template.directoryStructure, projectPath);

        // 生成项目配置文件
        GenerateProjectFiles(template, projectPath);

        // 安装必要的包
        InstallRequiredPackages(template.requiredPackages);

        // 设置渲染管线
        ConfigureRenderPipeline(template.renderPipeline);

        // 配置输入系统
        ConfigureInputSystem(template.inputSystem);

        Debug.Log($"项目创建成功: {template.templateName}");
        Debug.Log($"位置: {projectPath}");
    }

    private void CreateDirectoryStructure(DirectoryStructure structure, string basePath)
    {
        string rootPath = Path.Combine(basePath, structure.rootFolder);
        
        if (!Directory.Exists(rootPath))
        {
            Directory.CreateDirectory(rootPath);
        }

        foreach (string folder in structure.subFolders)
        {
            string folderPath = Path.Combine(rootPath, folder);
            Directory.CreateDirectory(folderPath);

            // 创建子目录
            if (structure.folderDescriptions != null && 
                structure.folderDescriptions.ContainsKey(folder))
            {
                foreach (string subFolder in structure.folderDescriptions[folder])
                {
                    string subFolderPath = Path.Combine(folderPath, subFolder);
                    Directory.CreateDirectory(subFolderPath);
                }
            }
        }
    }

    private void GenerateProjectFiles(ProjectTemplate template, string projectPath)
    {
        // 生成README文件
        string readmeContent = GenerateReadmeContent(template);
        string readmePath = Path.Combine(projectPath, "README.md");
        File.WriteAllText(readmePath, readmeContent);

        // 生成项目设置文件
        string settingsContent = GenerateProjectSettingsContent(template);
        string settingsPath = Path.Combine(projectPath, "ProjectSettings", "ProjectTemplate.json");
        File.WriteAllText(settingsPath, settingsContent);

        // 生成默认场景
        CreateDefaultScene(projectPath);
    }

    private string GenerateReadmeContent(ProjectTemplate template)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        sb.AppendLine($"# {template.templateName}");
        sb.AppendLine();
        sb.AppendLine($"## 项目描述");
        sb.AppendLine(template.description);
        sb.AppendLine();
        sb.AppendLine($"## 技术栈");
        sb.AppendLine($"- 渲染管线: {template.renderPipeline}");
        sb.AppendLine($"- 输入系统: {template.inputSystem}");
        sb.AppendLine($"- 网络支持: {(template.includeNetworking ? "是" : "否")}");
        sb.AppendLine();
        sb.AppendLine($"## 目录结构说明");
        
        foreach (string folder in template.directoryStructure.subFolders)
        {
            sb.AppendLine($"- {folder}/");
            
            if (template.directoryStructure.folderDescriptions != null && 
                template.directoryStructure.folderDescriptions.ContainsKey(folder))
            {
                foreach (string subFolder in template.directoryStructure.folderDescriptions[folder])
                {
                    sb.AppendLine($"  - {subFolder}/");
                }
            }
        }
        
        return sb.ToString();
    }

    private string GenerateProjectSettingsContent(ProjectTemplate template)
    {
        return JsonUtility.ToJson(template, true);
    }

    private void CreateDefaultScene(string projectPath)
    {
        // 在实际项目中,这里会创建一个包含基本设置的空场景
        string sceneContent = @"%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
  m_ObjectHideFlags: 0
  serializedVersion: 2
  m_OcclusionBakeSettings:
    smallestOccluder: 5
    smallestHole: 0.25
    backfaceThreshold: 100
  m_SceneGUID: 00000000000000000000000000000000
  m_OcclusionCullingData: {fileID: 0}";
        
        string scenePath = Path.Combine(projectPath, "Assets", "Scenes", "Main.unity");
        File.WriteAllText(scenePath, sceneContent);
    }

    private void InstallRequiredPackages(string[] packages)
    {
        // 在实际项目中,这里会通过Unity Package Manager API安装包
        Debug.Log("安装必要包:");
        foreach (string package in packages)
        {
            Debug.Log($"- {package}");
        }
    }

    private void ConfigureRenderPipeline(RenderPipelineType pipelineType)
    {
        switch (pipelineType)
        {
            case RenderPipelineType.Universal:
                Debug.Log("配置通用渲染管线(URP)");
                break;
            case RenderPipelineType.HighDefinition:
                Debug.Log("配置高清渲染管线(HDRP)");
                break;
            default:
                Debug.Log("使用内置渲染管线");
                break;
        }
    }

    private void ConfigureInputSystem(InputSystemType inputType)
    {
        switch (inputType)
        {
            case InputSystemType.NewInputSystem:
                Debug.Log("配置新输入系统");
                break;
            case InputSystemType.Both:
                Debug.Log("配置双输入系统");
                break;
            default:
                Debug.Log("使用传统输入系统");
                break;
        }
    }

    // 验证项目设置
    public List<string> ValidateProjectSetup(string projectPath)
    {
        List<string> issues = new List<string>();

        if (!Directory.Exists(projectPath))
        {
            issues.Add("项目目录不存在");
            return issues;
        }

        // 检查必要文件夹
        string[] requiredFolders = { "Assets", "ProjectSettings", "Packages" };
        foreach (string folder in requiredFolders)
        {
            string folderPath = Path.Combine(projectPath, folder);
            if (!Directory.Exists(folderPath))
            {
                issues.Add($"缺少必要文件夹: {folder}");
            }
        }

        // 检查Unity版本文件
        string versionFilePath = Path.Combine(projectPath, "ProjectSettings", "ProjectVersion.txt");
        if (!File.Exists(versionFilePath))
        {
            issues.Add("缺少Unity版本文件");
        }

        return issues;
    }
}
3.1.2 多项目管理与协作工作流

商业游戏工作室通常同时管理多个项目,需要高效的项目切换和资源共享机制。Unity Hub作为项目管理中心,提供了项目发现、版本管理和团队协作的基础功能。

using UnityEngine;
using System.Collections.Generic;
using System.IO;

// 商业级项目管理器
public class CommercialProjectManager : MonoBehaviour
{
    [System.Serializable]
    public class ProjectMetadata
    {
        public string projectName;
        public string projectPath;
        public string unityVersion;
        public string lastOpened;
        public string projectType;
        public List<string> teamMembers;
        public Dictionary<string, string> projectTags;
        public long projectSize;
    }

    [System.Serializable]
    public class ProjectCollection
    {
        public List<ProjectMetadata> projects;
        public Dictionary<string, List<string>> categorizedProjects;
    }

    private ProjectCollection projectCollection = new ProjectCollection();
    private string projectsDatabasePath;

    private void Awake()
    {
        projectsDatabasePath = Path.Combine(Application.persistentDataPath, "ProjectDatabase.json");
        LoadProjectDatabase();
        ScanForProjects();
    }

    private void LoadProjectDatabase()
    {
        if (File.Exists(projectsDatabasePath))
        {
            string json = File.ReadAllText(projectsDatabasePath);
            projectCollection = JsonUtility.FromJson<ProjectCollection>(json);
        }
        else
        {
            projectCollection = new ProjectCollection
            {
                projects = new List<ProjectMetadata>(),
                categorizedProjects = new Dictionary<string, List<string>>()
            };
        }
    }

    private void SaveProjectDatabase()
    {
        string json = JsonUtility.ToJson(projectCollection, true);
        File.WriteAllText(projectsDatabasePath, json);
    }

    public void ScanForProjects()
    {
        // 扫描常见Unity项目位置
        List<string> searchPaths = new List<string>
        {
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Unity Projects"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Unity"),
            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Unity Projects")
        };

        foreach (string searchPath in searchPaths)
        {
            if (Directory.Exists(searchPath))
            {
                ScanDirectoryForProjects(searchPath);
            }
        }

        SaveProjectDatabase();
    }

    private void ScanDirectoryForProjects(string directoryPath)
    {
        try
        {
            string[] subDirectories = Directory.GetDirectories(directoryPath);
            
            foreach (string subDir in subDirectories)
            {
                // 检查是否是Unity项目(包含Assets和ProjectSettings文件夹)
                string assetsPath = Path.Combine(subDir, "Assets");
                string projectSettingsPath = Path.Combine(subDir, "ProjectSettings");
                
                if (Directory.Exists(assetsPath) && Directory.Exists(projectSettingsPath))
                {
                    AddOrUpdateProject(subDir);
                }
                else
                {
                    // 递归扫描子目录
                    ScanDirectoryForProjects(subDir);
                }
            }
        }
        catch (System.Exception e)
        {
            Debug.LogWarning($"扫描目录失败 {directoryPath}: {e.Message}");
        }
    }

    private void AddOrUpdateProject(string projectPath)
    {
        string projectName = Path.GetFileName(projectPath);
        
        // 检查是否已存在
        ProjectMetadata existingProject = projectCollection.projects.Find(p => p.projectPath == projectPath);
        
        if (existingProject != null)
        {
            UpdateProjectMetadata(existingProject, projectPath);
        }
        else
        {
            ProjectMetadata newProject = CreateProjectMetadata(projectPath, projectName);
            projectCollection.projects.Add(newProject);
            CategorizeProject(newProject);
            
            Debug.Log($"发现新项目: {projectName}");
        }
    }

    private ProjectMetadata CreateProjectMetadata(string projectPath, string projectName)
    {
        DirectoryInfo dirInfo = new DirectoryInfo(projectPath);
        
        ProjectMetadata metadata = new ProjectMetadata
        {
            projectName = projectName,
            projectPath = projectPath,
            lastOpened = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
            projectSize = CalculateDirectorySize(projectPath),
            teamMembers = new List<string>(),
            projectTags = new Dictionary<string, string>()
        };

        // 尝试读取Unity版本
        string versionFilePath = Path.Combine(projectPath, "ProjectSettings", "ProjectVersion.txt");
        if (File.Exists(versionFilePath))
        {
            string[] lines = File.ReadAllLines(versionFilePath);
            if (lines.Length > 0)
            {
                metadata.unityVersion = lines[0].Replace("m_EditorVersion: ", "");
            }
        }

        // 尝试检测项目类型
        metadata.projectType = DetectProjectType(projectPath);

        return metadata;
    }

    private long CalculateDirectorySize(string directoryPath)
    {
        long size = 0;
        
        try
        {
            // 计算文件大小
            foreach (string file in Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories))
            {
                FileInfo fileInfo = new FileInfo(file);
                size += fileInfo.Length;
            }
        }
        catch
        {
            // 忽略无法访问的文件
        }
        
        return size;
    }

    private string DetectProjectType(string projectPath)
    {
        // 通过分析项目内容检测项目类型
        string assetsPath = Path.Combine(projectPath, "Assets");
        
        if (Directory.Exists(Path.Combine(assetsPath, "Scenes")))
        {
            return "3D项目";
        }
        else if (Directory.Exists(Path.Combine(assetsPath, "Sprites")))
        {
            return "2D项目";
        }
        else if (Directory.Exists(Path.Combine(assetsPath, "VR")))
        {
            return "VR项目";
        }
        
        return "未知类型";
    }

    private void UpdateProjectMetadata(ProjectMetadata project, string projectPath)
    {
        project.lastOpened = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        project.projectSize = CalculateDirectorySize(projectPath);
    }

    private void CategorizeProject(ProjectMetadata project)
    {
        // 按项目类型分类
        if (!projectCollection.categorizedProjects.ContainsKey(project.projectType))
        {
            projectCollection.categorizedProjects[project.projectType] = new List<string>();
        }
        
        if (!projectCollection.categorizedProjects[project.projectType].Contains(project.projectName))
        {
            projectCollection.categorizedProjects[project.projectType].Add(project.projectName);
        }
    }

    public void OpenProject(string projectPath)
    {
        if (!Directory.Exists(projectPath))
        {
            Debug.LogError($"项目路径不存在: {projectPath}");
            return;
        }

        // 在实际应用中,这里会调用Unity Hub API打开项目
        Debug.Log($"打开项目: {projectPath}");
        
        // 更新最后打开时间
        ProjectMetadata project = projectCollection.projects.Find(p => p.projectPath == projectPath);
        if (project != null)
        {
            project.lastOpened = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            SaveProjectDatabase();
        }
    }

    public List<ProjectMetadata> GetProjectsByCategory(string category)
    {
        List<ProjectMetadata> result = new List<ProjectMetadata>();
        
        if (projectCollection.categorizedProjects.ContainsKey(category))
        {
            foreach (string projectName in projectCollection.categorizedProjects[category])
            {
                ProjectMetadata project = projectCollection.projects.Find(p => p.projectName == projectName);
                if (project != null)
                {
                    result.Add(project);
                }
            }
        }
        
        return result;
    }

    public void AddProjectTag(string projectPath, string tagKey, string tagValue)
    {
        ProjectMetadata project = projectCollection.projects.Find(p => p.projectPath == projectPath);
        if (project != null)
        {
            if (project.projectTags == null)
            {
                project.projectTags = new Dictionary<string, string>();
            }
            
            project.projectTags[tagKey] = tagValue;
            SaveProjectDatabase();
        }
    }

    public void ExportProjectList(string exportPath)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        sb.AppendLine("Unity项目清单");
        sb.AppendLine($"生成时间: {System.DateTime.Now}");
        sb.AppendLine("=".PadRight(50, '='));
        sb.AppendLine();
        
        foreach (ProjectMetadata project in projectCollection.projects)
        {
            sb.AppendLine($"项目名称: {project.projectName}");
            sb.AppendLine($"路径: {project.projectPath}");
            sb.AppendLine($"Unity版本: {project.unityVersion}");
            sb.AppendLine($"类型: {project.projectType}");
            sb.AppendLine($"大小: {FormatFileSize(project.projectSize)}");
            sb.AppendLine($"最后打开: {project.lastOpened}");
            sb.AppendLine();
        }
        
        File.WriteAllText(exportPath, sb.ToString());
        Debug.Log($"项目清单已导出至: {exportPath}");
    }

    private string FormatFileSize(long bytes)
    {
        string[] sizes = { "B", "KB", "MB", "GB", "TB" };
        int order = 0;
        double size = bytes;
        
        while (size >= 1024 && order < sizes.Length - 1)
        {
            order++;
            size = size / 1024;
        }
        
        return $"{size:0.##} {sizes[order]}";
    }
}

3.2 项目资源管理高级技术

3.2.1 资产创建与智能组织系统

在商业项目中,资产数量可能达到数万甚至数十万,手动管理几乎不可能。需要建立智能的资源组织系统,包括自动分类、版本控制和依赖关系管理。

using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Linq;

// 智能资产管理系统
public class AdvancedAssetManager : MonoBehaviour
{
    [System.Serializable]
    public class AssetMetadata
    {
        public string guid;
        public string assetPath;
        public string assetType;
        public long fileSize;
        public System.DateTime lastModified;
        public List<string> dependencies;
        public Dictionary<string, string> customProperties;
        public AssetImportSettings importSettings;
    }

    [System.Serializable]
    public class AssetImportSettings
    {
        public bool generateMipMaps = true;
        public FilterMode filterMode = FilterMode.Bilinear;
        public TextureWrapMode wrapMode = TextureWrapMode.Repeat;
        public int maxTextureSize = 2048;
        public ModelImporterMeshCompression meshCompression = ModelImporterMeshCompression.Medium;
    }

    public enum ModelImporterMeshCompression
    {
        Off,
        Low,
        Medium,
        High
    }

    private Dictionary<string, AssetMetadata> assetDatabase = new Dictionary<string, AssetMetadata>();
    private string assetDatabasePath;

    private void Start()
    {
        assetDatabasePath = Path.Combine(Application.dataPath, "../Library/AssetDatabase.json");
        LoadAssetDatabase();
        ScanAssets();
    }

    private void LoadAssetDatabase()
    {
        if (File.Exists(assetDatabasePath))
        {
            string json = File.ReadAllText(assetDatabasePath);
            AssetDatabaseWrapper wrapper = JsonUtility.FromJson<AssetDatabaseWrapper>(json);
            assetDatabase = wrapper.ToDictionary();
        }
        else
        {
            assetDatabase = new Dictionary<string, AssetMetadata>();
        }
    }

    private void SaveAssetDatabase()
    {
        AssetDatabaseWrapper wrapper = new AssetDatabaseWrapper(assetDatabase);
        string json = JsonUtility.ToJson(wrapper, true);
        File.WriteAllText(assetDatabasePath, json);
    }

    [System.Serializable]
    private class AssetDatabaseWrapper
    {
        public List<AssetMetadata> assets = new List<AssetMetadata>();

        public AssetDatabaseWrapper(Dictionary<string, AssetMetadata> dict)
        {
            assets = dict.Values.ToList();
        }

        public Dictionary<string, AssetMetadata> ToDictionary()
        {
            Dictionary<string, AssetMetadata> dict = new Dictionary<string, AssetMetadata>();
            foreach (AssetMetadata asset in assets)
            {
                dict[asset.guid] = asset;
            }
            return dict;
        }
    }

    public void ScanAssets()
    {
        string assetsPath = Application.dataPath;
        ScanDirectory(assetsPath);
        SaveAssetDatabase();
        
        Debug.Log($"资产扫描完成,共发现 {assetDatabase.Count} 个资产");
    }

    private void ScanDirectory(string directoryPath)
    {
        try
        {
            // 扫描文件
            string[] files = Directory.GetFiles(directoryPath, "*.*", SearchOption.TopDirectoryOnly);
            foreach (string file in files)
            {
                if (IsSupportedAssetFile(file))
                {
                    ProcessAssetFile(file);
                }
            }

            // 递归扫描子目录
            string[] subDirectories = Directory.GetDirectories(directoryPath);
            foreach (string subDir in subDirectories)
            {
                ScanDirectory(subDir);
            }
        }
        catch (System.Exception e)
        {
            Debug.LogWarning($"扫描目录失败 {directoryPath}: {e.Message}");
        }
    }

    private bool IsSupportedAssetFile(string filePath)
    {
        string extension = Path.GetExtension(filePath).ToLower();
        string[] supportedExtensions = 
        { 
            ".prefab", ".mat", ".fbx", ".obj", ".png", ".jpg", 
            ".tga", ".psd", ".wav", ".mp3", ".ogg", ".anim", 
            ".controller", ".asset", ".unity", ".cs", ".shader"
        };
        
        return supportedExtensions.Contains(extension);
    }

    private void ProcessAssetFile(string filePath)
    {
        string relativePath = GetRelativeAssetPath(filePath);
        string guid = GenerateAssetGuid(relativePath);
        
        FileInfo fileInfo = new FileInfo(filePath);
        
        AssetMetadata metadata = new AssetMetadata
        {
            guid = guid,
            assetPath = relativePath,
            assetType = DetectAssetType(filePath),
            fileSize = fileInfo.Length,
            lastModified = fileInfo.LastWriteTime,
            dependencies = new List<string>(),
            customProperties = new Dictionary<string, string>(),
            importSettings = GetDefaultImportSettings(Path.GetExtension(filePath))
        };

        // 分析资产依赖关系
        AnalyzeDependencies(metadata, filePath);

        assetDatabase[guid] = metadata;
    }

    private string GetRelativeAssetPath(string absolutePath)
    {
        string dataPath = Application.dataPath;
        if (absolutePath.StartsWith(dataPath))
        {
            return "Assets" + absolutePath.Substring(dataPath.Length);
        }
        return absolutePath;
    }

    private string GenerateAssetGuid(string assetPath)
    {
        // 使用MD5生成稳定的GUID
        using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
        {
            byte[] hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(assetPath));
            return System.BitConverter.ToString(hash).Replace("-", "").ToLower();
        }
    }

    private string DetectAssetType(string filePath)
    {
        string extension = Path.GetExtension(filePath).ToLower();
        
        switch (extension)
        {
            case ".prefab": return "Prefab";
            case ".mat": return "Material";
            case ".fbx":
            case ".obj": return "3D Model";
            case ".png":
            case ".jpg":
            case ".tga":
            case ".psd": return "Texture";
            case ".wav":
            case ".mp3":
            case ".ogg": return "Audio";
            case ".anim": return "Animation";
            case ".controller": return "Animator Controller";
            case ".asset": return "Scriptable Object";
            case ".unity": return "Scene";
            case ".cs": return "Script";
            case ".shader": return "Shader";
            default: return "Unknown";
        }
    }

    private void AnalyzeDependencies(AssetMetadata metadata, string filePath)
    {
        // 根据文件类型分析依赖关系
        string extension = Path.GetExtension(filePath).ToLower();
        
        if (extension == ".prefab" || extension == ".unity")
        {
            // 对于Prefab和场景文件,可以解析YAML内容查找依赖
            FindDependenciesInYaml(metadata, filePath);
        }
        else if (extension == ".mat")
        {
            // 材质依赖纹理和Shader
            FindMaterialDependencies(metadata, filePath);
        }
    }

    private void FindDependenciesInYaml(AssetMetadata metadata, string filePath)
    {
        try
        {
            string[] lines = File.ReadAllLines(filePath);
            foreach (string line in lines)
            {
                if (line.Contains("guid:"))
                {
                    // 提取GUID
                    string guid = ExtractGuidFromYamlLine(line);
                    if (!string.IsNullOrEmpty(guid) && guid != "0000000000000000e000000000000000")
                    {
                        metadata.dependencies.Add(guid);
                    }
                }
            }
        }
        catch
        {
            // 忽略解析错误
        }
    }

    private string ExtractGuidFromYamlLine(string line)
    {
        int guidIndex = line.IndexOf("guid:") + 5;
        if (guidIndex >= 5 && guidIndex < line.Length)
        {
            string guidPart = line.Substring(guidIndex).Trim();
            if (guidPart.Length >= 32)
            {
                return guidPart.Substring(0, 32);
            }
        }
        return null;
    }

    private void FindMaterialDependencies(AssetMetadata metadata, string filePath)
    {
        // 简化实现,实际项目中会解析材质的Shader和纹理引用
        metadata.dependencies.Add("shader_guid_example"); // 示例
    }

    private AssetImportSettings GetDefaultImportSettings(string extension)
    {
        AssetImportSettings settings = new AssetImportSettings();
        
        switch (extension.ToLower())
        {
            case ".png":
            case ".jpg":
            case ".tga":
                settings.generateMipMaps = true;
                settings.filterMode = FilterMode.Trilinear;
                settings.maxTextureSize = 2048;
                break;
            case ".fbx":
            case ".obj":
                settings.meshCompression = ModelImporterMeshCompression.Medium;
                break;
        }
        
        return settings;
    }

    public List<AssetMetadata> FindUnusedAssets()
    {
        List<AssetMetadata> unusedAssets = new List<AssetMetadata>();
        HashSet<string> usedGuids = new HashSet<string>();

        // 收集所有被引用的GUID
        foreach (AssetMetadata asset in assetDatabase.Values)
        {
            foreach (string dependency in asset.dependencies)
            {
                usedGuids.Add(dependency);
            }
        }

        // 查找未被引用的资产
        foreach (AssetMetadata asset in assetDatabase.Values)
        {
            if (!usedGuids.Contains(asset.guid))
            {
                // 排除特定类型的资产(如场景、脚本等)
                if (!IsEssentialAssetType(asset.assetType))
                {
                    unusedAssets.Add(asset);
                }
            }
        }

        return unusedAssets;
    }

    private bool IsEssentialAssetType(string assetType)
    {
        string[] essentialTypes = { "Scene", "Script", "Shader", "Animator Controller" };
        return essentialTypes.Contains(assetType);
    }

    public void GenerateAssetReport(string outputPath)
    {
        System.Text.StringBuilder report = new System.Text.StringBuilder();
        report.AppendLine("Unity资产分析报告");
        report.AppendLine($"生成时间: {System.DateTime.Now}");
        report.AppendLine($"资产总数: {assetDatabase.Count}");
        report.AppendLine("=".PadRight(60, '='));
        report.AppendLine();

        // 按类型统计
        Dictionary<string, int> typeCount = new Dictionary<string, int>();
        Dictionary<string, long> typeSize = new Dictionary<string, long>();

        foreach (AssetMetadata asset in assetDatabase.Values)
        {
            if (!typeCount.ContainsKey(asset.assetType))
            {
                typeCount[asset.assetType] = 0;
                typeSize[asset.assetType] = 0;
            }

            typeCount[asset.assetType]++;
            typeSize[asset.assetType] += asset.fileSize;
        }

        report.AppendLine("按类型统计:");
        foreach (var kvp in typeCount.OrderByDescending(x => x.Value))
        {
            string type = kvp.Key;
            int count = kvp.Value;
            long size = typeSize[type];
            
            report.AppendLine($"{type.PadRight(20)}: {count.ToString().PadRight(6)} 文件, {FormatFileSize(size).PadRight(12)}");
        }

        report.AppendLine();
        
        // 查找大文件
        var largeAssets = assetDatabase.Values
            .OrderByDescending(a => a.fileSize)
            .Take(20);
        
        report.AppendLine("最大的20个文件:");
        foreach (AssetMetadata asset in largeAssets)
        {
            report.AppendLine($"{Path.GetFileName(asset.assetPath).PadRight(30)}: {FormatFileSize(asset.fileSize).PadRight(12)} ({asset.assetType})");
        }

        File.WriteAllText(outputPath, report.ToString());
        Debug.Log($"资产报告已生成: {outputPath}");
    }

    private string FormatFileSize(long bytes)
    {
        string[] sizes = { "B", "KB", "MB", "GB" };
        int order = 0;
        double size = bytes;
        
        while (size >= 1024 && order < sizes.Length - 1)
        {
            order++;
            size = size / 1024;
        }
        
        return $"{size:0.##} {sizes[order]}";
    }
}
3.2.2 高级资产搜索与过滤系统

商业项目中资产搜索不仅需要支持名称搜索,还需要支持类型过滤、属性搜索、依赖关系查询和自定义标签系统。

using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

// 高级资产搜索系统
public class AdvancedAssetSearch : MonoBehaviour
{
    [System.Serializable]
    public class SearchQuery
    {
        public string keyword;
        public List<string> assetTypes = new List<string>();
        public string pathFilter;
        public long minFileSize;
        public long maxFileSize;
        public System.DateTime modifiedAfter;
        public System.DateTime modifiedBefore;
        public Dictionary<string, string> customProperties;
        public bool includeDependencies;
        public SearchScope searchScope = SearchScope.EntireProject;
    }

    public enum SearchScope
    {
        CurrentFolder,
        SelectedFolders,
        EntireProject
    }

    private AdvancedAssetManager assetManager;
    private List<SearchQuery> searchHistory = new List<SearchQuery>();

    private void Start()
    {
        assetManager = FindObjectOfType<AdvancedAssetManager>();
        if (assetManager == null)
        {
            Debug.LogError("需要AdvancedAssetManager组件");
        }
    }

    public List<AdvancedAssetManager.AssetMetadata> SearchAssets(SearchQuery query)
    {
        if (assetManager == null)
        {
            Debug.LogError("资产管理器未初始化");
            return new List<AdvancedAssetManager.AssetMetadata>();
        }

        // 获取所有资产
        var allAssets = assetManager.GetAllAssets();
        if (allAssets == null) return new List<AdvancedAssetManager.AssetMetadata>();

        // 应用过滤条件
        IEnumerable<AdvancedAssetManager.AssetMetadata> results = allAssets;

        // 关键字搜索
        if (!string.IsNullOrEmpty(query.keyword))
        {
            results = results.Where(asset => 
                MatchKeyword(asset, query.keyword));
        }

        // 类型过滤
        if (query.assetTypes != null && query.assetTypes.Count > 0)
        {
            results = results.Where(asset => 
                query.assetTypes.Contains(asset.assetType));
        }

        // 路径过滤
        if (!string.IsNullOrEmpty(query.pathFilter))
        {
            results = results.Where(asset => 
                asset.assetPath.Contains(query.pathFilter));
        }

        // 文件大小过滤
        if (query.minFileSize > 0)
        {
            results = results.Where(asset => 
                asset.fileSize >= query.minFileSize);
        }

        if (query.maxFileSize > 0 && query.maxFileSize < long.MaxValue)
        {
            results = results.Where(asset => 
                asset.fileSize <= query.maxFileSize);
        }

        // 修改时间过滤
        if (query.modifiedAfter != System.DateTime.MinValue)
        {
            results = results.Where(asset => 
                asset.lastModified >= query.modifiedAfter);
        }

        if (query.modifiedBefore != System.DateTime.MinValue)
        {
            results = results.Where(asset => 
                asset.lastModified <= query.modifiedBefore);
        }

        // 自定义属性过滤
        if (query.customProperties != null && query.customProperties.Count > 0)
        {
            results = results.Where(asset => 
                MatchCustomProperties(asset, query.customProperties));
        }

        List<AdvancedAssetManager.AssetMetadata> resultList = results.ToList();

        // 包含依赖项
        if (query.includeDependencies)
        {
            HashSet<AdvancedAssetManager.AssetMetadata> allResults = new HashSet<AdvancedAssetManager.AssetMetadata>(resultList);
            foreach (var asset in resultList.ToList())
            {
                AddDependencies(asset, allResults, assetManager);
            }
            resultList = allResults.ToList();
        }

        // 保存搜索历史
        SaveSearchHistory(query);

        return resultList;
    }

    private bool MatchKeyword(AdvancedAssetManager.AssetMetadata asset, string keyword)
    {
        // 支持通配符和正则表达式
        if (keyword.Contains("*") || keyword.Contains("?"))
        {
            string pattern = "^" + Regex.Escape(keyword)
                .Replace("\\*", ".*")
                .Replace("\\?", ".") + "$";
            
            return Regex.IsMatch(Path.GetFileNameWithoutExtension(asset.assetPath), pattern, RegexOptions.IgnoreCase);
        }

        // 简单包含匹配
        return Path.GetFileNameWithoutExtension(asset.assetPath)
            .IndexOf(keyword, System.StringComparison.OrdinalIgnoreCase) >= 0;
    }

    private bool MatchCustomProperties(AdvancedAssetManager.AssetMetadata asset, 
        Dictionary<string, string> requiredProperties)
    {
        if (asset.customProperties == null) return false;

        foreach (var kvp in requiredProperties)
        {
            if (!asset.customProperties.ContainsKey(kvp.Key) || 
                asset.customProperties[kvp.Key] != kvp.Value)
            {
                return false;
            }
        }

        return true;
    }

    private void AddDependencies(AdvancedAssetManager.AssetMetadata asset, 
        HashSet<AdvancedAssetManager.AssetMetadata> resultSet, 
        AdvancedAssetManager manager)
    {
        if (asset.dependencies == null) return;

        foreach (string dependencyGuid in asset.dependencies)
        {
            var dependencyAsset = manager.GetAssetByGuid(dependencyGuid);
            if (dependencyAsset != null && !resultSet.Contains(dependencyAsset))
            {
                resultSet.Add(dependencyAsset);
                AddDependencies(dependencyAsset, resultSet, manager); // 递归添加
            }
        }
    }

    private void SaveSearchHistory(SearchQuery query)
    {
        searchHistory.Add(query);
        
        // 限制历史记录数量
        if (searchHistory.Count > 50)
        {
            searchHistory.RemoveAt(0);
        }
    }

    public SearchQuery CreateSmartSearchQuery(string input)
    {
        SearchQuery query = new SearchQuery();
        
        // 解析智能搜索字符串
        // 示例: "texture:icon size>1mb type:png modified>2023-01-01"
        
        string[] tokens = input.Split(' ');
        
        foreach (string token in tokens)
        {
            if (token.Contains(":"))
            {
                // 属性搜索
                string[] parts = token.Split(':');
                if (parts.Length == 2)
                {
                    switch (parts[0].ToLower())
                    {
                        case "type":
                            query.assetTypes.Add(parts[1].ToUpper());
                            break;
                        case "path":
                            query.pathFilter = parts[1];
                            break;
                        default:
                            if (query.customProperties == null)
                                query.customProperties = new Dictionary<string, string>();
                            query.customProperties[parts[0]] = parts[1];
                            break;
                    }
                }
            }
            else if (token.Contains(">") || token.Contains("<"))
            {
                // 数值比较
                ParseComparisonToken(token, query);
            }
            else
            {
                // 关键字
                if (string.IsNullOrEmpty(query.keyword))
                {
                    query.keyword = token;
                }
                else
                {
                    query.keyword += " " + token;
                }
            }
        }

        return query;
    }

    private void ParseComparisonToken(string token, SearchQuery query)
    {
        if (token.Contains("size>"))
        {
            string value = token.Substring(5);
            query.minFileSize = ParseFileSize(value);
        }
        else if (token.Contains("size<"))
        {
            string value = token.Substring(5);
            query.maxFileSize = ParseFileSize(value);
        }
        else if (token.Contains("modified>"))
        {
            string value = token.Substring(9);
            if (System.DateTime.TryParse(value, out System.DateTime date))
            {
                query.modifiedAfter = date;
            }
        }
        else if (token.Contains("modified<"))
        {
            string value = token.Substring(9);
            if (System.DateTime.TryParse(value, out System.DateTime date))
            {
                query.modifiedBefore = date;
            }
        }
    }

    private long ParseFileSize(string sizeString)
    {
        sizeString = sizeString.ToLower();
        long multiplier = 1;

        if (sizeString.EndsWith("kb"))
        {
            multiplier = 1024;
            sizeString = sizeString.Substring(0, sizeString.Length - 2);
        }
        else if (sizeString.EndsWith("mb"))
        {
            multiplier = 1024 * 1024;
            sizeString = sizeString.Substring(0, sizeString.Length - 2);
        }
        else if (sizeString.EndsWith("gb"))
        {
            multiplier = 1024 * 1024 * 1024;
            sizeString = sizeString.Substring(0, sizeString.Length - 2);
        }

        if (long.TryParse(sizeString, out long value))
        {
            return value * multiplier;
        }

        return 0;
    }

    public string GenerateSearchSummary(SearchQuery query, List<AdvancedAssetManager.AssetMetadata> results)
    {
        System.Text.StringBuilder summary = new System.Text.StringBuilder();
        summary.AppendLine("搜索摘要");
        summary.AppendLine($"搜索时间: {System.DateTime.Now}");
        summary.AppendLine($"查询条件: {SummarizeQuery(query)}");
        summary.AppendLine($"结果数量: {results.Count}");
        summary.AppendLine($"总大小: {FormatTotalSize(results)}");
        summary.AppendLine();

        // 按类型分组
        var groupedResults = results.GroupBy(r => r.assetType)
            .OrderByDescending(g => g.Count());

        foreach (var group in groupedResults)
        {
            summary.AppendLine($"{group.Key}: {group.Count()} 个文件");
        }

        return summary.ToString();
    }

    private string SummarizeQuery(SearchQuery query)
    {
        List<string> conditions = new List<string>();

        if (!string.IsNullOrEmpty(query.keyword))
            conditions.Add($"关键字: {query.keyword}");

        if (query.assetTypes != null && query.assetTypes.Count > 0)
            conditions.Add($"类型: {string.Join(", ", query.assetTypes)}");

        if (query.minFileSize > 0 || query.maxFileSize < long.MaxValue)
        {
            string sizeCondition = "大小: ";
            if (query.minFileSize > 0)
                sizeCondition += $">{FormatFileSize(query.minFileSize)} ";
            if (query.maxFileSize < long.MaxValue)
                sizeCondition += $"<{FormatFileSize(query.maxFileSize)}";
            conditions.Add(sizeCondition.Trim());
        }

        return string.Join("; ", conditions);
    }

    private string FormatTotalSize(List<AdvancedAssetManager.AssetMetadata> assets)
    {
        long totalSize = assets.Sum(a => a.fileSize);
        return FormatFileSize(totalSize);
    }

    private string FormatFileSize(long bytes)
    {
        string[] sizes = { "B", "KB", "MB", "GB" };
        int order = 0;
        double size = bytes;
        
        while (size >= 1024 && order < sizes.Length - 1)
        {
            order++;
            size = size / 1024;
        }
        
        return $"{size:0.##} {sizes[order]}";
    }

    // 为AdvancedAssetManager添加扩展方法
    public List<AdvancedAssetManager.AssetMetadata> FindAssetsByReference(string guid)
    {
        var allAssets = assetManager.GetAllAssets();
        return allAssets.Where(asset => 
            asset.dependencies != null && 
            asset.dependencies.Contains(guid))
            .ToList();
    }

    public List<AdvancedAssetManager.AssetMetadata> FindDuplicateAssets()
    {
        var allAssets = assetManager.GetAllAssets();
        var duplicates = allAssets
            .GroupBy(asset => GetAssetSignature(asset))
            .Where(group => group.Count() > 1)
            .SelectMany(group => group)
            .ToList();
        
        return duplicates;
    }

    private string GetAssetSignature(AdvancedAssetManager.AssetMetadata asset)
    {
        // 简化的资产签名,实际项目中会使用文件哈希
        return $"{Path.GetFileName(asset.assetPath)}_{asset.fileSize}";
    }
}
3.2.3 智能标签与分类系统

商业项目中,资产的分类和标签系统对于团队协作和资产管理至关重要。智能标签系统可以自动为资产分类,减少手动标注的工作量。

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

// 智能标签系统
public class SmartTaggingSystem : MonoBehaviour
{
    [System.Serializable]
    public class AssetTag
    {
        public string tagName;
        public string category;
        public Color tagColor;
        public List<string> autoAssignmentRules;
    }

    [System.Serializable]
    public class AutoAssignmentRule
    {
        public string ruleType; // "NameContains", "PathContains", "TypeIs", "SizeRange"
        public string ruleValue;
        public string[] ruleParameters;
    }

    [SerializeField]
    private List<AssetTag> predefinedTags = new List<AssetTag>();

    private Dictionary<string, AssetTag> tagDictionary = new Dictionary<string, AssetTag>();
    private AdvancedAssetManager assetManager;

    private void Awake()
    {
        assetManager = FindObjectOfType<AdvancedAssetManager>();
        InitializeTagSystem();
    }

    private void InitializeTagSystem()
    {
        // 初始化预定义标签
        predefinedTags = new List<AssetTag>
        {
            new AssetTag
            {
                tagName = "UI",
                category = "AssetType",
                tagColor = Color.blue,
                autoAssignmentRules = new List<string>
                {
                    "PathContains:/UI/",
                    "NameContains:Button,Panel,Text,Image"
                }
            },
            new AssetTag
            {
                tagName = "Character",
                category = "AssetType",
                tagColor = Color.green,
                autoAssignmentRules = new List<string>
                {
                    "PathContains:/Characters/",
                    "TypeIs:3D Model"
                }
            },
            new AssetTag
            {
                tagName = "Environment",
                category = "AssetType",
                tagColor = Color.yellow,
                autoAssignmentRules = new List<string>
                {
                    "PathContains:/Environment/",
                    "NameContains:Terrain,Rock,Tree,Building"
                }
            },
            new AssetTag
            {
                tagName = "HighPriority",
                category = "Importance",
                tagColor = Color.red,
                autoAssignmentRules = new List<string>
                {
                    "UsedInScene:Main",
                    "DependencyCount:>5"
                }
            }
        };

        // 构建标签字典
        foreach (AssetTag tag in predefinedTags)
        {
            tagDictionary[tag.tagName] = tag;
        }
    }

    public void AutoTagAssets()
    {
        if (assetManager == null)
        {
            Debug.LogError("需要AdvancedAssetManager组件");
            return;
        }

        var allAssets = assetManager.GetAllAssets();
        int taggedCount = 0;

        foreach (var asset in allAssets)
        {
            List<string> applicableTags = FindApplicableTags(asset);
            
            if (applicableTags.Count > 0)
            {
                ApplyTagsToAsset(asset, applicableTags);
                taggedCount++;
            }
        }

        Debug.Log($"自动标签完成,处理了 {taggedCount} 个资产");
    }

    private List<string> FindApplicableTags(AdvancedAssetManager.AssetMetadata asset)
    {
        List<string> applicableTags = new List<string>();

        foreach (AssetTag tag in predefinedTags)
        {
            if (ShouldApplyTag(asset, tag))
            {
                applicableTags.Add(tag.tagName);
            }
        }

        return applicableTags;
    }

    private bool ShouldApplyTag(AdvancedAssetManager.AssetMetadata asset, AssetTag tag)
    {
        if (tag.autoAssignmentRules == null || tag.autoAssignmentRules.Count == 0)
            return false;

        foreach (string ruleString in tag.autoAssignmentRules)
        {
            AutoAssignmentRule rule = ParseRule(ruleString);
            if (rule != null && EvaluateRule(asset, rule))
            {
                return true;
            }
        }

        return false;
    }

    private AutoAssignmentRule ParseRule(string ruleString)
    {
        string[] parts = ruleString.Split(':');
        if (parts.Length != 2)
            return null;

        AutoAssignmentRule rule = new AutoAssignmentRule
        {
            ruleType = parts[0],
            ruleValue = parts[1]
        };

        // 解析参数
        if (rule.ruleValue.Contains(","))
        {
            rule.ruleParameters = rule.ruleValue.Split(',');
        }

        return rule;
    }

    private bool EvaluateRule(AdvancedAssetManager.AssetMetadata asset, AutoAssignmentRule rule)
    {
        switch (rule.ruleType)
        {
            case "PathContains":
                return asset.assetPath.Contains(rule.ruleValue);
                
            case "NameContains":
                if (rule.ruleParameters != null)
                {
                    string assetName = System.IO.Path.GetFileNameWithoutExtension(asset.assetPath);
                    return rule.ruleParameters.Any(param => 
                        assetName.IndexOf(param, System.StringComparison.OrdinalIgnoreCase) >= 0);
                }
                break;
                
            case "TypeIs":
                return asset.assetType == rule.ruleValue;
                
            case "UsedInScene":
                // 简化实现,实际项目中会检查资产是否在指定场景中被引用
                return true;
                
            case "DependencyCount":
                if (rule.ruleValue.StartsWith(">"))
                {
                    if (int.TryParse(rule.ruleValue.Substring(1), out int minCount))
                    {
                        return asset.dependencies != null && asset.dependencies.Count > minCount;
                    }
                }
                break;
        }

        return false;
    }

    private void ApplyTagsToAsset(AdvancedAssetManager.AssetMetadata asset, List<string> tags)
    {
        if (asset.customProperties == null)
        {
            asset.customProperties = new Dictionary<string, string>();
        }

        if (!asset.customProperties.ContainsKey("Tags"))
        {
            asset.customProperties["Tags"] = string.Join(",", tags);
        }
        else
        {
            // 合并现有标签
            string existingTags = asset.customProperties["Tags"];
            List<string> existingTagList = existingTags.Split(',').ToList();
            
            foreach (string tag in tags)
            {
                if (!existingTagList.Contains(tag))
                {
                    existingTagList.Add(tag);
                }
            }
            
            asset.customProperties["Tags"] = string.Join(",", existingTagList);
        }
    }

    public List<AdvancedAssetManager.AssetMetadata> GetAssetsByTag(string tagName)
    {
        if (assetManager == null)
            return new List<AdvancedAssetManager.AssetMetadata>();

        var allAssets = assetManager.GetAllAssets();
        return allAssets.Where(asset =>
            asset.customProperties != null &&
            asset.customProperties.ContainsKey("Tags") &&
            asset.customProperties["Tags"].Contains(tagName))
            .ToList();
    }

    public Dictionary<string, int> GetTagStatistics()
    {
        Dictionary<string, int> statistics = new Dictionary<string, int>();

        foreach (AssetTag tag in predefinedTags)
        {
            int count = GetAssetsByTag(tag.tagName).Count;
            statistics[tag.tagName] = count;
        }

        return statistics;
    }

    public void GenerateTagReport(string outputPath)
    {
        var tagStats = GetTagStatistics();
        
        System.Text.StringBuilder report = new System.Text.StringBuilder();
        report.AppendLine("资产标签统计报告");
        report.AppendLine($"生成时间: {System.DateTime.Now}");
        report.AppendLine("=".PadRight(50, '='));
        report.AppendLine();

        foreach (var kvp in tagStats.OrderByDescending(x => x.Value))
        {
            report.AppendLine($"{kvp.Key.PadRight(20)}: {kvp.Value} 个资产");
        }

        // 显示未标签的资产
        var allAssets = assetManager.GetAllAssets();
        int untaggedCount = allAssets.Count(asset => 
            asset.customProperties == null || 
            !asset.customProperties.ContainsKey("Tags"));
        
        report.AppendLine();
        report.AppendLine($"未标签的资产: {untaggedCount}");

        System.IO.File.WriteAllText(outputPath, report.ToString());
        Debug.Log($"标签报告已生成: {outputPath}");
    }

    public void CreateCustomTag(string tagName, string category, Color color)
    {
        if (tagDictionary.ContainsKey(tagName))
        {
            Debug.LogWarning($"标签 '{tagName}' 已存在");
            return;
        }

        AssetTag newTag = new AssetTag
        {
            tagName = tagName,
            category = category,
            tagColor = color,
            autoAssignmentRules = new List<string>()
        };

        predefinedTags.Add(newTag);
        tagDictionary[tagName] = newTag;
        
        Debug.Log($"已创建新标签: {tagName}");
    }

    public void AddAutoAssignmentRule(string tagName, string rule)
    {
        if (!tagDictionary.ContainsKey(tagName))
        {
            Debug.LogError($"标签 '{tagName}' 不存在");
            return;
        }

        AssetTag tag = tagDictionary[tagName];
        if (tag.autoAssignmentRules == null)
        {
            tag.autoAssignmentRules = new List<string>();
        }

        tag.autoAssignmentRules.Add(rule);
        Debug.Log($"已为标签 '{tagName}' 添加规则: {rule}");
    }

    public void ExportTagConfiguration(string exportPath)
    {
        TagConfiguration config = new TagConfiguration
        {
            tags = predefinedTags,
            exportTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
        };

        string json = JsonUtility.ToJson(config, true);
        System.IO.File.WriteAllText(exportPath, json);
        Debug.Log($"标签配置已导出: {exportPath}");
    }

    [System.Serializable]
    private class TagConfiguration
    {
        public List<AssetTag> tags;
        public string exportTime;
    }
}

3.3 场景层级管理专业实践

3.3.1 游戏对象的智能创建与组织

商业游戏场景通常包含成千上万个游戏对象,手动管理几乎不可能。需要建立智能的对象创建和组织系统。

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

// 智能场景对象管理系统
public class SmartHierarchyManager : MonoBehaviour
{
    [System.Serializable]
    public class SceneLayer
    {
        public string layerName;
        public Color layerColor = Color.white;
        public bool isVisible = true;
        public bool isLocked = false;
        public Transform layerRoot;
        public List<GameObject> objectsInLayer = new List<GameObject>();
    }

    [SerializeField]
    private List<SceneLayer> sceneLayers = new List<SceneLayer>();

    [SerializeField]
    private GameObject layerPrefab;

    [SerializeField]
    private bool autoOrganize = true;

    private Dictionary<string, SceneLayer> layerDictionary = new Dictionary<string, SceneLayer>();

    private void Start()
    {
        InitializeDefaultLayers();
        if (autoOrganize)
        {
            OrganizeExistingObjects();
        }
    }

    private void InitializeDefaultLayers()
    {
        // 创建默认层级
        SceneLayer[] defaultLayers = 
        {
            new SceneLayer { layerName = "Environment", layerColor = Color.green },
            new SceneLayer { layerName = "Characters", layerColor = Color.blue },
            new SceneLayer { layerName = "UI", layerColor = Color.yellow },
            new SceneLayer { layerName = "Lighting", layerColor = Color.white },
            new SceneLayer { layerName = "Audio", layerColor = Color.magenta },
            new SceneLayer { layerName = "Effects", layerColor = Color.red }
        };

        foreach (SceneLayer layer in defaultLayers)
        {
            AddLayer(layer);
        }
    }

    public void AddLayer(SceneLayer newLayer)
    {
        if (layerDictionary.ContainsKey(newLayer.layerName))
        {
            Debug.LogWarning($"层级 '{newLayer.layerName}' 已存在");
            return;
        }

        // 创建层级根对象
        GameObject layerRoot = new GameObject(newLayer.layerName);
        layerRoot.transform.SetParent(transform);
        
        // 添加标识组件
        LayerRootIdentifier identifier = layerRoot.AddComponent<LayerRootIdentifier>();
        identifier.layerName = newLayer.layerName;
        
        newLayer.layerRoot = layerRoot.transform;
        sceneLayers.Add(newLayer);
        layerDictionary[newLayer.layerName] = newLayer;
        
        Debug.Log($"已创建层级: {newLayer.layerName}");
    }

    public GameObject CreateObjectInLayer(string objectName, string layerName, 
        Vector3 position = default, Quaternion rotation = default)
    {
        if (!layerDictionary.ContainsKey(layerName))
        {
            Debug.LogError($"层级 '{layerName}' 不存在");
            return null;
        }

        SceneLayer layer = layerDictionary[layerName];
        
        GameObject newObject = new GameObject(objectName);
        newObject.transform.position = position;
        newObject.transform.rotation = rotation;
        newObject.transform.SetParent(layer.layerRoot);
        
        // 添加层级标签
        HierarchyTag tag = newObject.AddComponent<HierarchyTag>();
        tag.assignedLayer = layerName;
        
        layer.objectsInLayer.Add(newObject);
        
        return newObject;
    }

    public GameObject CreatePrefabInLayer(GameObject prefab, string layerName, 
        Vector3 position = default, Quaternion rotation = default)
    {
        if (!layerDictionary.ContainsKey(layerName))
        {
            Debug.LogError($"层级 '{layerName}' 不存在");
            return null;
        }

        SceneLayer layer = layerDictionary[layerName];
        
        GameObject instance = Instantiate(prefab, position, rotation);
        instance.transform.SetParent(layer.layerRoot);
        instance.name = prefab.name;
        
        // 添加层级标签
        HierarchyTag tag = instance.GetComponent<HierarchyTag>();
        if (tag == null)
        {
            tag = instance.AddComponent<HierarchyTag>();
        }
        tag.assignedLayer = layerName;
        
        layer.objectsInLayer.Add(instance);
        
        return instance;
    }

    private void OrganizeExistingObjects()
    {
        // 获取场景中的所有对象(排除管理器和UI对象)
        GameObject[] allObjects = FindObjectsOfType<GameObject>()
            .Where(obj => obj.transform.parent == null && 
                   !obj.GetComponent<Camera>() && 
                   !obj.GetComponent<Light>())
            .ToArray();

        foreach (GameObject obj in allObjects)
        {
            string layerName = DetermineObjectLayer(obj);
            
            if (!string.IsNullOrEmpty(layerName) && layerDictionary.ContainsKey(layerName))
            {
                MoveObjectToLayer(obj, layerName);
            }
        }

        Debug.Log($"场景组织完成,处理了 {allObjects.Length} 个对象");
    }

    private string DetermineObjectLayer(GameObject obj)
    {
        // 根据对象名称、组件等判断所属层级
        string objectName = obj.name.ToLower();
        
        if (objectName.Contains("terrain") || objectName.Contains("tree") || 
            objectName.Contains("rock") || objectName.Contains("building"))
            return "Environment";
        
        if (objectName.Contains("player") || objectName.Contains("enemy") || 
            objectName.Contains("npc") || objectName.Contains("character"))
            return "Characters";
        
        if (obj.GetComponent<Canvas>() || objectName.Contains("ui") || 
            objectName.Contains("button") || objectName.Contains("panel"))
            return "UI";
        
        if (obj.GetComponent<Light>() || obj.GetComponent<ReflectionProbe>())
            return "Lighting";
        
        if (obj.GetComponent<AudioSource>())
            return "Audio";
        
        if (obj.GetComponent<ParticleSystem>() || objectName.Contains("effect"))
            return "Effects";
        
        return null;
    }

    private void MoveObjectToLayer(GameObject obj, string layerName)
    {
        SceneLayer layer = layerDictionary[layerName];
        
        // 检查是否已有层级标签
        HierarchyTag existingTag = obj.GetComponent<HierarchyTag>();
        if (existingTag != null)
        {
            // 从原层级移除
            if (layerDictionary.ContainsKey(existingTag.assignedLayer))
            {
                SceneLayer oldLayer = layerDictionary[existingTag.assignedLayer];
                oldLayer.objectsInLayer.Remove(obj);
            }
            
            existingTag.assignedLayer = layerName;
        }
        else
        {
            HierarchyTag newTag = obj.AddComponent<HierarchyTag>();
            newTag.assignedLayer = layerName;
        }
        
        obj.transform.SetParent(layer.layerRoot);
        layer.objectsInLayer.Add(obj);
    }

    public void ToggleLayerVisibility(string layerName, bool isVisible)
    {
        if (!layerDictionary.ContainsKey(layerName))
            return;

        SceneLayer layer = layerDictionary[layerName];
        layer.isVisible = isVisible;
        
        foreach (GameObject obj in layer.objectsInLayer)
        {
            if (obj != null)
            {
                obj.SetActive(isVisible);
            }
        }
    }

    public void LockLayer(string layerName, bool isLocked)
    {
        if (!layerDictionary.ContainsKey(layerName))
            return;

        SceneLayer layer = layerDictionary[layerName];
        layer.isLocked = isLocked;
        
        // 在实际编辑器中,这里会设置对象的锁定状态
        Debug.Log($"层级 '{layerName}' {(isLocked ? "已锁定" : "已解锁")}");
    }

    public List<GameObject> FindObjectsByTypeInLayer<T>(string layerName) where T : Component
    {
        if (!layerDictionary.ContainsKey(layerName))
            return new List<GameObject>();

        SceneLayer layer = layerDictionary[layerName];
        return layer.objectsInLayer
            .Where(obj => obj != null && obj.GetComponent<T>() != null)
            .ToList();
    }

    public void CleanEmptyLayers()
    {
        List<SceneLayer> layersToRemove = new List<SceneLayer>();
        
        foreach (SceneLayer layer in sceneLayers)
        {
            // 移除空对象引用
            layer.objectsInLayer.RemoveAll(obj => obj == null);
            
            // 标记待删除的空层级
            if (layer.objectsInLayer.Count == 0 && layer.layerRoot.childCount == 0)
            {
                layersToRemove.Add(layer);
            }
        }
        
        // 删除空层级
        foreach (SceneLayer layer in layersToRemove)
        {
            sceneLayers.Remove(layer);
            layerDictionary.Remove(layer.layerName);
            
            if (layer.layerRoot != null)
            {
                Destroy(layer.layerRoot.gameObject);
            }
            
            Debug.Log($"已删除空层级: {layer.layerName}");
        }
    }

    public void GenerateHierarchyReport(string outputPath)
    {
        System.Text.StringBuilder report = new System.Text.StringBuilder();
        report.AppendLine("场景层级分析报告");
        report.AppendLine($"生成时间: {System.DateTime.Now}");
        report.AppendLine($"场景: {UnityEngine.SceneManagement.SceneManager.GetActiveScene().name}");
        report.AppendLine("=".PadRight(50, '='));
        report.AppendLine();

        int totalObjects = 0;
        
        foreach (SceneLayer layer in sceneLayers)
        {
            int objectCount = layer.objectsInLayer.Count(obj => obj != null);
            totalObjects += objectCount;
            
            report.AppendLine($"{layer.layerName.PadRight(15)}: {objectCount.ToString().PadRight(5)} 个对象");
            
            // 统计对象类型
            var typeGroups = layer.objectsInLayer
                .Where(obj => obj != null)
                .GroupBy(obj => GetObjectType(obj))
                .OrderByDescending(group => group.Count());
            
            foreach (var group in typeGroups.Take(5)) // 显示前5种类型
            {
                report.AppendLine($"  {group.Key.PadRight(20)}: {group.Count()}");
            }
            
            report.AppendLine();
        }
        
        report.AppendLine($"总对象数: {totalObjects}");
        report.AppendLine($"层级数量: {sceneLayers.Count}");

        System.IO.File.WriteAllText(outputPath, report.ToString());
        Debug.Log($"层级报告已生成: {outputPath}");
    }

    private string GetObjectType(GameObject obj)
    {
        if (obj.GetComponent<MeshRenderer>()) return "Mesh";
        if (obj.GetComponent<SpriteRenderer>()) return "Sprite";
        if (obj.GetComponent<ParticleSystem>()) return "Particle";
        if (obj.GetComponent<Camera>()) return "Camera";
        if (obj.GetComponent<Light>()) return "Light";
        if (obj.GetComponent<Canvas>()) return "UI Canvas";
        return "Empty";
    }
}

// 层级标签组件
public class HierarchyTag : MonoBehaviour
{
    public string assignedLayer = "Default";
    public string objectCategory = "";
    public List<string> customTags = new List<string>();
    
    [TextArea]
    public string notes = "";
    
    private void OnValidate()
    {
        // 在编辑器中验证层级名称
        if (string.IsNullOrEmpty(assignedLayer))
        {
            assignedLayer = "Default";
        }
    }
}

// 层级根标识组件
public class LayerRootIdentifier : MonoBehaviour
{
    public string layerName = "";
    public bool isLocked = false;
    public bool isVisible = true;
    
    private void OnEnable()
    {
        gameObject.hideFlags = HideFlags.NotEditable;
    }
}
3.3.2 场景对象的智能搜索与过滤

在包含数千个对象的商业场景中,快速定位特定对象至关重要。Unity的Hierarchy搜索功能基础,但商业项目需要更强大的搜索能力。

using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

// 高级场景对象搜索系统
public class AdvancedSceneSearch : MonoBehaviour
{
    [System.Serializable]
    public class SceneSearchQuery
    {
        public string searchTerm;
        public SearchMode searchMode = SearchMode.NameContains;
        public ComponentType componentFilter = ComponentType.Any;
        public string tagFilter;
        public string layerFilter;
        public bool includeInactive = false;
        public bool searchInChildren = true;
        public SortOrder sortOrder = SortOrder.NameAscending;
    }

    public enum SearchMode
    {
        NameContains,
        NameExact,
        NameRegex,
        ComponentExists,
        PropertyValue
    }

    public enum ComponentType
    {
        Any,
        Renderer,
        Collider,
        Light,
        Camera,
        Rigidbody,
        Animator,
        Custom
    }

    public enum SortOrder
    {
        NameAscending,
        NameDescending,
        HierarchyOrder,
        DepthFirst
    }

    private SmartHierarchyManager hierarchyManager;

    private void Start()
    {
        hierarchyManager = FindObjectOfType<SmartHierarchyManager>();
    }

    public List<GameObject> SearchScene(SceneSearchQuery query)
    {
        // 获取所有游戏对象
        GameObject[] allObjects;
        
        if (query.includeInactive)
        {
            allObjects = Resources.FindObjectsOfTypeAll<GameObject>()
                .Where(obj => obj.hideFlags == HideFlags.None)
                .ToArray();
        }
        else
        {
            allObjects = FindObjectsOfType<GameObject>(true);
        }

        // 应用过滤
        IEnumerable<GameObject> results = allObjects;

        // 层级过滤
        if (!string.IsNullOrEmpty(query.layerFilter) && hierarchyManager != null)
        {
            results = results.Where(obj =>
            {
                HierarchyTag tag = obj.GetComponent<HierarchyTag>();
                return tag != null && tag.assignedLayer == query.layerFilter;
            });
        }

        // 标签过滤
        if (!string.IsNullOrEmpty(query.tagFilter))
        {
            results = results.Where(obj => obj.CompareTag(query.tagFilter));
        }

        // 组件过滤
        if (query.componentFilter != ComponentType.Any)
        {
            results = results.Where(obj => HasRequiredComponent(obj, query.componentFilter));
        }

        // 名称搜索
        if (!string.IsNullOrEmpty(query.searchTerm))
        {
            results = results.Where(obj => MatchSearchTerm(obj, query));
        }

        List<GameObject> resultList = results.ToList();

        // 排序
        resultList = SortResults(resultList, query.sortOrder);

        return resultList;
    }

    private bool HasRequiredComponent(GameObject obj, ComponentType componentType)
    {
        switch (componentType)
        {
            case ComponentType.Renderer:
                return obj.GetComponent<Renderer>() != null;
            case ComponentType.Collider:
                return obj.GetComponent<Collider>() != null;
            case ComponentType.Light:
                return obj.GetComponent<Light>() != null;
            case ComponentType.Camera:
                return obj.GetComponent<Camera>() != null;
            case ComponentType.Rigidbody:
                return obj.GetComponent<Rigidbody>() != null;
            case ComponentType.Animator:
                return obj.GetComponent<Animator>() != null;
            case ComponentType.Custom:
                return obj.GetComponents<Component>().Length > 1; // 至少有一个非Transform组件
            default:
                return true;
        }
    }

    private bool MatchSearchTerm(GameObject obj, SceneSearchQuery query)
    {
        switch (query.searchMode)
        {
            case SearchMode.NameContains:
                return obj.name.IndexOf(query.searchTerm, System.StringComparison.OrdinalIgnoreCase) >= 0;
                
            case SearchMode.NameExact:
                return obj.name.Equals(query.searchTerm, System.StringComparison.OrdinalIgnoreCase);
                
            case SearchMode.NameRegex:
                try
                {
                    return Regex.IsMatch(obj.name, query.searchTerm, RegexOptions.IgnoreCase);
                }
                catch
                {
                    return false;
                }
                
            case SearchMode.ComponentExists:
                return obj.GetComponent(query.searchTerm) != null;
                
            case SearchMode.PropertyValue:
                // 简化实现,实际项目中会检查组件属性
                return SearchInComponents(obj, query.searchTerm);
                
            default:
                return true;
        }
    }

    private bool SearchInComponents(GameObject obj, string searchTerm)
    {
        Component[] allComponents = obj.GetComponents<Component>();
        
        foreach (Component component in allComponents)
        {
            // 使用反射检查属性值(简化实现)
            var properties = component.GetType().GetProperties();
            foreach (var property in properties)
            {
                try
                {
                    object value = property.GetValue(component);
                    if (value != null && value.ToString().IndexOf(searchTerm, System.StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        return true;
                    }
                }
                catch
                {
                    // 忽略无法访问的属性
                }
            }
        }
        
        return false;
    }

    private List<GameObject> SortResults(List<GameObject> results, SortOrder sortOrder)
    {
        switch (sortOrder)
        {
            case SortOrder.NameAscending:
                return results.OrderBy(obj => obj.name).ToList();
                
            case SortOrder.NameDescending:
                return results.OrderByDescending(obj => obj.name).ToList();
                
            case SortOrder.HierarchyOrder:
                return results.OrderBy(obj => GetHierarchyDepth(obj)).ToList();
                
            case SortOrder.DepthFirst:
                return SortDepthFirst(results);
                
            default:
                return results;
        }
    }

    private int GetHierarchyDepth(GameObject obj)
    {
        int depth = 0;
        Transform current = obj.transform;
        
        while (current.parent != null)
        {
            depth++;
            current = current.parent;
        }
        
        return depth;
    }

    private List<GameObject> SortDepthFirst(List<GameObject> objects)
    {
        List<GameObject> sorted = new List<GameObject>();
        HashSet<GameObject> visited = new HashSet<GameObject>();
        
        foreach (GameObject obj in objects)
        {
            if (!visited.Contains(obj))
            {
                AddDepthFirst(obj, sorted, visited);
            }
        }
        
        return sorted;
    }

    private void AddDepthFirst(GameObject obj, List<GameObject> sorted, HashSet<GameObject> visited)
    {
        visited.Add(obj);
        sorted.Add(obj);
        
        // 添加子对象
        for (int i = 0; i < obj.transform.childCount; i++)
        {
            GameObject child = obj.transform.GetChild(i).gameObject;
            if (!visited.Contains(child))
            {
                AddDepthFirst(child, sorted, visited);
            }
        }
    }

    public SceneSearchQuery ParseSearchString(string searchString)
    {
        SceneSearchQuery query = new SceneSearchQuery();
        
        if (string.IsNullOrEmpty(searchString))
            return query;

        // 解析搜索语法
        // 示例: "name:player component:renderer layer:characters"
        
        Dictionary<string, string> parameters = ParseParameters(searchString);
        
        if (parameters.ContainsKey("name"))
        {
            query.searchTerm = parameters["name"];
            query.searchMode = SearchMode.NameContains;
        }
        
        if (parameters.ContainsKey("component"))
        {
            string componentName = parameters["component"];
            if (System.Enum.TryParse(componentName, true, out ComponentType componentType))
            {
                query.componentFilter = componentType;
            }
            else if (componentName == "any")
            {
                query.componentFilter = ComponentType.Any;
            }
            else
            {
                query.componentFilter = ComponentType.Custom;
                query.searchMode = SearchMode.ComponentExists;
                query.searchTerm = componentName;
            }
        }
        
        if (parameters.ContainsKey("layer"))
        {
            query.layerFilter = parameters["layer"];
        }
        
        if (parameters.ContainsKey("tag"))
        {
            query.tagFilter = parameters["tag"];
        }
        
        if (parameters.ContainsKey("inactive"))
        {
            query.includeInactive = bool.Parse(parameters["inactive"]);
        }

        return query;
    }

    private Dictionary<string, string> ParseParameters(string searchString)
    {
        Dictionary<string, string> parameters = new Dictionary<string, string>();
        
        string[] parts = searchString.Split(' ');
        foreach (string part in parts)
        {
            if (part.Contains(":"))
            {
                string[] keyValue = part.Split(':');
                if (keyValue.Length == 2)
                {
                    parameters[keyValue[0]] = keyValue[1];
                }
            }
            else if (!parameters.ContainsKey("name"))
            {
                parameters["name"] = part;
            }
        }
        
        return parameters;
    }

    public void SelectObjectsInEditor(List<GameObject> objects)
    {
        if (objects == null || objects.Count == 0)
            return;

        #if UNITY_EDITOR
        UnityEditor.Selection.objects = objects.ToArray();
        Debug.Log($"已选择 {objects.Count} 个对象");
        #endif
    }

    public void FocusOnObject(GameObject obj, bool frameSelection = true)
    {
        if (obj == null)
            return;

        #if UNITY_EDITOR
        UnityEditor.Selection.activeObject = obj;
        
        if (frameSelection)
        {
            UnityEditor.SceneView.lastActiveSceneView.FrameSelected();
        }
        #endif
    }

    public Dictionary<string, List<GameObject>> GroupResultsByType(List<GameObject> objects)
    {
        Dictionary<string, List<GameObject>> grouped = new Dictionary<string, List<GameObject>>();
        
        foreach (GameObject obj in objects)
        {
            string type = GetObjectTypeString(obj);
            
            if (!grouped.ContainsKey(type))
            {
                grouped[type] = new List<GameObject>();
            }
            
            grouped[type].Add(obj);
        }
        
        return grouped;
    }

    private string GetObjectTypeString(GameObject obj)
    {
        Component[] components = obj.GetComponents<Component>();
        
        if (components.Length <= 1) // 只有Transform
            return "Empty";
        
        // 查找主要组件类型
        foreach (Component component in components)
        {
            if (component is Renderer) return "Renderer";
            if (component is Collider) return "Collider";
            if (component is Light) return "Light";
            if (component is Camera) return "Camera";
            if (component is Rigidbody) return "Rigidbody";
            if (component is Animator) return "Animator";
        }
        
        return "Custom";
    }

    public void GenerateSearchReport(List<GameObject> results, string outputPath)
    {
        System.Text.StringBuilder report = new System.Text.StringBuilder();
        report.AppendLine("场景搜索报告");
        report.AppendLine($"生成时间: {System.DateTime.Now}");
        report.AppendLine($"结果数量: {results.Count}");
        report.AppendLine("=".PadRight(50, '='));
        report.AppendLine();

        // 按类型分组
        var groupedResults = GroupResultsByType(results)
            .OrderByDescending(g => g.Value.Count);
        
        foreach (var group in groupedResults)
        {
            report.AppendLine($"{group.Key}: {group.Value.Count} 个对象");
            
            // 显示前10个对象
            int count = 0;
            foreach (GameObject obj in group.Value)
            {
                if (count++ >= 10) break;
                report.AppendLine($"  - {obj.name} ({GetFullPath(obj)})");
            }
            
            if (group.Value.Count > 10)
            {
                report.AppendLine($"  ... 还有 {group.Value.Count - 10} 个");
            }
            
            report.AppendLine();
        }

        System.IO.File.WriteAllText(outputPath, report.ToString());
        Debug.Log($"搜索报告已生成: {outputPath}");
    }

    private string GetFullPath(GameObject obj)
    {
        string path = "/" + obj.name;
        Transform parent = obj.transform.parent;
        
        while (parent != null)
        {
            path = "/" + parent.name + path;
            parent = parent.parent;
        }
        
        return path;
    }
}

3.4 Inspector视图的扩展与自定义

3.4.1 标题栏的自定义与批量操作

商业项目中,经常需要对多个对象执行相同的操作。通过扩展Inspector标题栏,可以添加批量操作按钮和快速访问功能。

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

// Inspector扩展管理器
public class InspectorExtensionManager : MonoBehaviour
{
    [System.Serializable]
    public class CustomInspectorAction
    {
        public string actionName;
        public string buttonText;
        public System.Action<GameObject[]> action;
        public bool requiresConfirmation = false;
        public string confirmationMessage = "确认执行此操作?";
    }

    [SerializeField]
    private List<CustomInspectorAction> globalActions = new List<CustomInspectorAction>();

    private static InspectorExtensionManager instance;

    public static InspectorExtensionManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<InspectorExtensionManager>();
                if (instance == null)
                {
                    GameObject obj = new GameObject("InspectorExtensionManager");
                    instance = obj.AddComponent<InspectorExtensionManager>();
                }
            }
            return instance;
        }
    }

    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        instance = this;
        DontDestroyOnLoad(gameObject);
        InitializeDefaultActions();
    }

    private void InitializeDefaultActions()
    {
        globalActions = new List<CustomInspectorAction>
        {
            new CustomInspectorAction
            {
                actionName = "ResetTransform",
                buttonText = "重置变换",
                action = (objects) =>
                {
                    foreach (GameObject obj in objects)
                    {
                        obj.transform.localPosition = Vector3.zero;
                        obj.transform.localRotation = Quaternion.identity;
                        obj.transform.localScale = Vector3.one;
                    }
                    Debug.Log($"已重置 {objects.Length} 个对象的变换");
                }
            },
            new CustomInspectorAction
            {
                actionName = "EnableRenderers",
                buttonText = "启用渲染器",
                action = (objects) =>
                {
                    int count = 0;
                    foreach (GameObject obj in objects)
                    {
                        Renderer renderer = obj.GetComponent<Renderer>();
                        if (renderer != null && !renderer.enabled)
                        {
                            renderer.enabled = true;
                            count++;
                        }
                    }
                    Debug.Log($"已启用 {count} 个渲染器");
                }
            },
            new CustomInspectorAction
            {
                actionName = "DisableRenderers",
                buttonText = "禁用渲染器",
                action = (objects) =>
                {
                    int count = 0;
                    foreach (GameObject obj in objects)
                    {
                        Renderer renderer = obj.GetComponent<Renderer>();
                        if (renderer != null && renderer.enabled)
                        {
                            renderer.enabled = false;
                            count++;
                        }
                    }
                    Debug.Log($"已禁用 {count} 个渲染器");
                }
            },
            new CustomInspectorAction
            {
                actionName = "AddRigidbody",
                buttonText = "添加刚体",
                requiresConfirmation = true,
                confirmationMessage = "确认要为选中的所有对象添加刚体?",
                action = (objects) =>
                {
                    int count = 0;
                    foreach (GameObject obj in objects)
                    {
                        if (obj.GetComponent<Rigidbody>() == null)
                        {
                            obj.AddComponent<Rigidbody>();
                            count++;
                        }
                    }
                    Debug.Log($"已添加 {count} 个刚体");
                }
            }
        };
    }

    public void RegisterCustomAction(CustomInspectorAction action)
    {
        if (globalActions.Any(a => a.actionName == action.actionName))
        {
            Debug.LogWarning($"操作 '{action.actionName}' 已存在,将被替换");
            globalActions.RemoveAll(a => a.actionName == action.actionName);
        }
        
        globalActions.Add(action);
    }

    public void ExecuteAction(string actionName, GameObject[] targets)
    {
        CustomInspectorAction action = globalActions.Find(a => a.actionName == actionName);
        if (action == null)
        {
            Debug.LogError($"未找到操作: {actionName}");
            return;
        }

        if (targets == null || targets.Length == 0)
        {
            Debug.LogWarning("没有选中的对象");
            return;
        }

        if (action.requiresConfirmation)
        {
            ShowConfirmationDialog(action, targets);
        }
        else
        {
            ExecuteActionInternal(action, targets);
        }
    }

    private void ShowConfirmationDialog(CustomInspectorAction action, GameObject[] targets)
    {
        // 在实际编辑器中,这里会显示确认对话框
        #if UNITY_EDITOR
        bool confirmed = UnityEditor.EditorUtility.DisplayDialog(
            "确认操作",
            action.confirmationMessage,
            "确认",
            "取消");
        
        if (confirmed)
        {
            ExecuteActionInternal(action, targets);
        }
        #else
        ExecuteActionInternal(action, targets);
        #endif
    }

    private void ExecuteActionInternal(CustomInspectorAction action, GameObject[] targets)
    {
        try
        {
            action.action?.Invoke(targets);
            Debug.Log($"操作 '{action.actionName}' 执行成功");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"执行操作 '{action.actionName}' 时出错: {e.Message}");
        }
    }

    public List<CustomInspectorAction> GetApplicableActions(GameObject[] targets)
    {
        List<CustomInspectorAction> applicableActions = new List<CustomInspectorAction>();

        foreach (CustomInspectorAction action in globalActions)
        {
            // 这里可以添加条件检查逻辑
            applicableActions.Add(action);
        }

        return applicableActions;
    }

    public void BatchModifyProperty<T>(GameObject[] targets, string propertyName, T value) where T : struct
    {
        if (targets == null || targets.Length == 0)
            return;

        int modifiedCount = 0;
        
        foreach (GameObject obj in targets)
        {
            Component[] components = obj.GetComponents<Component>();
            foreach (Component component in components)
            {
                var property = component.GetType().GetProperty(propertyName);
                if (property != null && property.CanWrite)
                {
                    try
                    {
                        property.SetValue(component, value);
                        modifiedCount++;
                    }
                    catch
                    {
                        // 忽略设置失败的情况
                    }
                }
            }
        }
        
        Debug.Log($"已修改 {modifiedCount} 个属性");
    }

    public void CopyComponentValues(Component source, GameObject[] targets)
    {
        if (source == null || targets == null)
            return;

        System.Type componentType = source.GetType();
        int copiedCount = 0;
        
        foreach (GameObject target in targets)
        {
            Component targetComponent = target.GetComponent(componentType);
            if (targetComponent != null)
            {
                UnityEditor.EditorUtility.CopySerializedIfDifferent(source, targetComponent);
                copiedCount++;
            }
        }
        
        Debug.Log($"已复制组件值到 {copiedCount} 个对象");
    }
}

// 为组件添加自定义Inspector扩展
#if UNITY_EDITOR
using UnityEditor;

[CustomEditor(typeof(Transform))]
public class TransformInspectorExtension : Editor
{
    private InspectorExtensionManager inspectorManager;

    private void OnEnable()
    {
        inspectorManager = InspectorExtensionManager.Instance;
    }

    public override void OnInspectorGUI()
    {
        // 显示默认Inspector
        base.OnInspectorGUI();

        if (inspectorManager == null)
            return;

        // 添加分隔线
        EditorGUILayout.Space();
        EditorGUILayout.LabelField("批量操作", EditorStyles.boldLabel);
        
        // 获取当前选中的对象
        GameObject[] selectedObjects = Selection.gameObjects;
        
        if (selectedObjects.Length > 1)
        {
            // 显示批量操作按钮
            var applicableActions = inspectorManager.GetApplicableActions(selectedObjects);
            
            foreach (var action in applicableActions)
            {
                if (GUILayout.Button(action.buttonText))
                {
                    inspectorManager.ExecuteAction(action.actionName, selectedObjects);
                }
            }
            
            EditorGUILayout.Space();
            
            // 添加快速属性修改
            EditorGUILayout.LabelField("快速属性修改", EditorStyles.miniBoldLabel);
            
            if (GUILayout.Button("重置所有位置"))
            {
                foreach (GameObject obj in selectedObjects)
                {
                    obj.transform.localPosition = Vector3.zero;
                }
            }
            
            if (GUILayout.Button("统一缩放 (1,1,1)"))
            {
                foreach (GameObject obj in selectedObjects)
                {
                    obj.transform.localScale = Vector3.one;
                }
            }
        }
    }
}

[CustomEditor(typeof(Renderer))]
public class RendererInspectorExtension : Editor
{
    public override void OnInspectorGUI()
    {
        // 显示默认Inspector
        base.OnInspectorGUI();

        EditorGUILayout.Space();
        EditorGUILayout.LabelField("渲染优化", EditorStyles.boldLabel);
        
        Renderer renderer = (Renderer)target;
        
        // 添加静态批处理选项
        bool staticBatching = EditorGUILayout.Toggle("启用静态批处理", 
            GameObjectUtility.GetStaticEditorFlags(renderer.gameObject).HasFlag(StaticEditorFlags.BatchingStatic));
        
        if (staticBatching)
        {
            GameObjectUtility.SetStaticEditorFlags(renderer.gameObject, 
                GameObjectUtility.GetStaticEditorFlags(renderer.gameObject) | StaticEditorFlags.BatchingStatic);
        }
        else
        {
            GameObjectUtility.SetStaticEditorFlags(renderer.gameObject, 
                GameObjectUtility.GetStaticEditorFlags(renderer.gameObject) & ~StaticEditorFlags.BatchingStatic);
        }
        
        // 添加LOD设置快速访问
        if (GUILayout.Button("配置LOD组"))
        {
            Selection.activeObject = renderer.gameObject;
            EditorApplication.ExecuteMenuItem("GameObject/3D Object/LOD Group");
        }
    }
}
#endif

3.5 Scene视图的专业导航技巧

3.5.1 高级场景导航与视图控制

在大型商业场景中,有效的导航技巧可以显著提高工作效率。本节介绍专业级的场景导航技术和自定义视图控制。

using UnityEngine;
using System.Collections.Generic;

// 高级场景导航控制器
public class AdvancedSceneNavigation : MonoBehaviour
{
    [System.Serializable]
    public class SavedViewpoint
    {
        public string viewpointName;
        public Vector3 cameraPosition;
        public Quaternion cameraRotation;
        public float fieldOfView;
        public bool isOrthographic;
        public float orthographicSize;
        public GameObject focusObject;
    }

    [SerializeField]
    private List<SavedViewpoint> savedViewpoints = new List<SavedViewpoint>();

    [SerializeField]
    private float navigationSpeed = 5f;
    
    [SerializeField]
    private float rotationSpeed = 100f;
    
    [SerializeField]
    private float zoomSpeed = 10f;

    private Camera sceneCamera;
    private Vector3 lastMousePosition;
    private bool isNavigating = false;

    private void Start()
    {
        // 尝试获取场景相机
        FindSceneCamera();
        
        if (sceneCamera == null)
        {
            Debug.LogWarning("未找到场景相机,将创建临时相机");
            CreateSceneCamera();
        }
    }

    private void FindSceneCamera()
    {
        // 在编辑器中查找场景视图相机
        #if UNITY_EDITOR
        var sceneView = UnityEditor.SceneView.lastActiveSceneView;
        if (sceneView != null)
        {
            sceneCamera = sceneView.camera;
        }
        #endif
    }

    private void CreateSceneCamera()
    {
        GameObject cameraObj = new GameObject("SceneNavigationCamera");
        sceneCamera = cameraObj.AddComponent<Camera>();
        sceneCamera.transform.position = new Vector3(0, 10, -10);
        sceneCamera.transform.LookAt(Vector3.zero);
        sceneCamera.orthographic = true;
        sceneCamera.orthographicSize = 10;
    }

    private void Update()
    {
        if (sceneCamera == null)
            return;

        HandleNavigationInput();
    }

    private void HandleNavigationInput()
    {
        // 鼠标右键 - 旋转视图
        if (Input.GetMouseButtonDown(1))
        {
            isNavigating = true;
            lastMousePosition = Input.mousePosition;
        }
        
        if (Input.GetMouseButtonUp(1))
        {
            isNavigating = false;
        }
        
        if (isNavigating && Input.GetMouseButton(1))
        {
            Vector3 delta = Input.mousePosition - lastMousePosition;
            
            // 旋转相机
            sceneCamera.transform.RotateAround(
                sceneCamera.transform.position, 
                Vector3.up, 
                delta.x * rotationSpeed * Time.deltaTime);
            
            sceneCamera.transform.RotateAround(
                sceneCamera.transform.position, 
                sceneCamera.transform.right, 
                -delta.y * rotationSpeed * Time.deltaTime);
            
            lastMousePosition = Input.mousePosition;
        }
        
        // WASD移动
        Vector3 moveInput = new Vector3(
            (Input.GetKey(KeyCode.D) ? 1 : 0) - (Input.GetKey(KeyCode.A) ? 1 : 0),
            (Input.GetKey(KeyCode.E) ? 1 : 0) - (Input.GetKey(KeyCode.Q) ? 1 : 0),
            (Input.GetKey(KeyCode.W) ? 1 : 0) - (Input.GetKey(KeyCode.S) ? 1 : 0)
        );
        
        if (moveInput.magnitude > 0)
        {
            Vector3 moveDirection = sceneCamera.transform.TransformDirection(moveInput);
            sceneCamera.transform.position += moveDirection * navigationSpeed * Time.deltaTime;
        }
        
        // 鼠标滚轮 - 缩放
        float scroll = Input.GetAxis("Mouse ScrollWheel");
        if (scroll != 0)
        {
            if (sceneCamera.orthographic)
            {
                sceneCamera.orthographicSize = Mathf.Max(0.1f, 
                    sceneCamera.orthographicSize - scroll * zoomSpeed);
            }
            else
            {
                sceneCamera.transform.position += sceneCamera.transform.forward * scroll * zoomSpeed;
            }
        }
    }

    public void SaveCurrentViewpoint(string viewpointName, GameObject focusObject = null)
    {
        if (sceneCamera == null)
            return;

        SavedViewpoint viewpoint = new SavedViewpoint
        {
            viewpointName = viewpointName,
            cameraPosition = sceneCamera.transform.position,
            cameraRotation = sceneCamera.transform.rotation,
            fieldOfView = sceneCamera.fieldOfView,
            isOrthographic = sceneCamera.orthographic,
            orthographicSize = sceneCamera.orthographicSize,
            focusObject = focusObject
        };

        // 检查是否已存在同名视点
        int existingIndex = savedViewpoints.FindIndex(v => v.viewpointName == viewpointName);
        if (existingIndex >= 0)
        {
            savedViewpoints[existingIndex] = viewpoint;
            Debug.Log($"更新视点: {viewpointName}");
        }
        else
        {
            savedViewpoints.Add(viewpoint);
            Debug.Log($"保存新视点: {viewpointName}");
        }
    }

    public void RestoreViewpoint(string viewpointName)
    {
        SavedViewpoint viewpoint = savedViewpoints.Find(v => v.viewpointName == viewpointName);
        if (viewpoint == null)
        {
            Debug.LogError($"未找到视点: {viewpointName}");
            return;
        }

        if (sceneCamera != null)
        {
            sceneCamera.transform.position = viewpoint.cameraPosition;
            sceneCamera.transform.rotation = viewpoint.cameraRotation;
            sceneCamera.fieldOfView = viewpoint.fieldOfView;
            sceneCamera.orthographic = viewpoint.isOrthographic;
            sceneCamera.orthographicSize = viewpoint.orthographicSize;
            
            Debug.Log($"恢复视点: {viewpointName}");
            
            // 如果有焦点对象,尝试聚焦
            if (viewpoint.focusObject != null)
            {
                FocusOnObject(viewpoint.focusObject);
            }
        }
    }

    public void FocusOnObject(GameObject target, float distanceMultiplier = 2f)
    {
        if (target == null || sceneCamera == null)
            return;

        // 计算包围盒
        Renderer renderer = target.GetComponent<Renderer>();
        Bounds bounds;
        
        if (renderer != null)
        {
            bounds = renderer.bounds;
        }
        else
        {
            // 使用碰撞器或估计大小
            Collider collider = target.GetComponent<Collider>();
            if (collider != null)
            {
                bounds = collider.bounds;
            }
            else
            {
                bounds = new Bounds(target.transform.position, Vector3.one * 2f);
            }
        }
        
        // 计算相机位置
        float objectSize = Mathf.Max(bounds.size.x, bounds.size.y, bounds.size.z);
        float requiredDistance = objectSize * distanceMultiplier;
        
        Vector3 direction = (sceneCamera.transform.position - bounds.center).normalized;
        if (direction == Vector3.zero) direction = Vector3.back;
        
        Vector3 newPosition = bounds.center + direction * requiredDistance;
        
        sceneCamera.transform.position = newPosition;
        sceneCamera.transform.LookAt(bounds.center);
        
        // 调整正交大小
        if (sceneCamera.orthographic)
        {
            sceneCamera.orthographicSize = objectSize * 0.5f * distanceMultiplier;
        }
        
        Debug.Log($"已聚焦到: {target.name}");
    }

    public void ToggleCameraMode()
    {
        if (sceneCamera != null)
        {
            sceneCamera.orthographic = !sceneCamera.orthographic;
            Debug.Log($"相机模式切换为: {(sceneCamera.orthographic ? "正交" : "透视")}");
        }
    }

    public void AlignViewToAxis(Axis axis)
    {
        if (sceneCamera == null)
            return;

        switch (axis)
        {
            case Axis.X:
                sceneCamera.transform.position = new Vector3(10, 0, 0);
                sceneCamera.transform.LookAt(Vector3.zero);
                break;
            case Axis.Y:
                sceneCamera.transform.position = new Vector3(0, 10, 0);
                sceneCamera.transform.LookAt(Vector3.zero);
                break;
            case Axis.Z:
                sceneCamera.transform.position = new Vector3(0, 0, 10);
                sceneCamera.transform.LookAt(Vector3.zero);
                break;
        }
        
        sceneCamera.orthographic = true;
        sceneCamera.orthographicSize = 10;
    }

    public enum Axis
    {
        X,
        Y,
        Z
    }

    public void ExportViewpoints(string exportPath)
    {
        System.Text.StringBuilder export = new System.Text.StringBuilder();
        export.AppendLine("场景视点配置");
        export.AppendLine($"导出时间: {System.DateTime.Now}");
        export.AppendLine($"场景: {UnityEngine.SceneManagement.SceneManager.GetActiveScene().name}");
        export.AppendLine("=".PadRight(50, '='));
        export.AppendLine();
        
        foreach (SavedViewpoint viewpoint in savedViewpoints)
        {
            export.AppendLine($"视点: {viewpoint.viewpointName}");
            export.AppendLine($"位置: {viewpoint.cameraPosition}");
            export.AppendLine($"旋转: {viewpoint.cameraRotation.eulerAngles}");
            export.AppendLine($"投影: {(viewpoint.isOrthographic ? "正交" : "透视")}");
            if (viewpoint.isOrthographic)
            {
                export.AppendLine($"正交大小: {viewpoint.orthographicSize}");
            }
            else
            {
                export.AppendLine($"视野: {viewpoint.fieldOfView}");
            }
            if (viewpoint.focusObject != null)
            {
                export.AppendLine($"焦点对象: {viewpoint.focusObject.name}");
            }
            export.AppendLine();
        }
        
        System.IO.File.WriteAllText(exportPath, export.ToString());
        Debug.Log($"视点配置已导出: {exportPath}");
    }
}

// 场景导航快捷键管理器
public class SceneNavigationHotkeys : MonoBehaviour
{
    [System.Serializable]
    public class HotkeyConfig
    {
        public KeyCode key;
        public string description;
        public System.Action action;
    }

    [SerializeField]
    private List<HotkeyConfig> hotkeyConfigs = new List<HotkeyConfig>();

    private AdvancedSceneNavigation navigation;

    private void Start()
    {
        navigation = FindObjectOfType<AdvancedSceneNavigation>();
        InitializeHotkeys();
    }

    private void InitializeHotkeys()
    {
        hotkeyConfigs = new List<HotkeyConfig>
        {
            new HotkeyConfig
            {
                key = KeyCode.F1,
                description = "保存当前视点",
                action = () => 
                {
                    if (navigation != null)
                    {
                        navigation.SaveCurrentViewpoint($"视点_{System.DateTime.Now:HHmmss}");
                    }
                }
            },
            new HotkeyConfig
            {
                key = KeyCode.F2,
                description = "切换相机模式",
                action = () => 
                {
                    if (navigation != null)
                    {
                        navigation.ToggleCameraMode();
                    }
                }
            },
            new HotkeyConfig
            {
                key = KeyCode.F3,
                description = "顶视图",
                action = () => 
                {
                    if (navigation != null)
                    {
                        navigation.AlignViewToAxis(AdvancedSceneNavigation.Axis.Y);
                    }
                }
            },
            new HotkeyConfig
            {
                key = KeyCode.F4,
                description = "前视图",
                action = () => 
                {
                    if (navigation != null)
                    {
                        navigation.AlignViewToAxis(AdvancedSceneNavigation.Axis.Z);
                    }
                }
            },
            new HotkeyConfig
            {
                key = KeyCode.F5,
                description = "右视图",
                action = () => 
                {
                    if (navigation != null)
                    {
                        navigation.AlignViewToAxis(AdvancedSceneNavigation.Axis.X);
                    }
                }
            }
        };
    }

    private void Update()
    {
        foreach (HotkeyConfig hotkey in hotkeyConfigs)
        {
            if (Input.GetKeyDown(hotkey.key))
            {
                hotkey.action?.Invoke();
            }
        }
        
        // 快速聚焦选中的对象
        if (Input.GetKeyDown(KeyCode.F))
        {
            FocusOnSelected();
        }
    }

    private void FocusOnSelected()
    {
        #if UNITY_EDITOR
        GameObject[] selected = UnityEditor.Selection.gameObjects;
        if (selected.Length > 0 && navigation != null)
        {
            navigation.FocusOnObject(selected[0]);
        }
        #endif
    }

    public void AddHotkey(HotkeyConfig hotkey)
    {
        hotkeyConfigs.Add(hotkey);
    }

    public void PrintHotkeyHelp()
    {
        Debug.Log("场景导航快捷键:");
        foreach (HotkeyConfig hotkey in hotkeyConfigs)
        {
            Debug.Log($"{hotkey.key}: {hotkey.description}");
        }
        Debug.Log("F: 聚焦选中对象");
        Debug.Log("右键拖拽: 旋转视图");
        Debug.Log("WASD: 移动");
        Debug.Log("QE: 升降");
        Debug.Log("滚轮: 缩放");
    }
}

本章详细介绍了Unity编辑器的核心架构和商业级工作流实践,涵盖了项目管理、资源管理、场景组织、Inspector扩展和场景导航等关键领域。通过实现这些高级功能,可以显著提高大型商业项目的开发效率和质量。每个系统都设计为可扩展和可定制的,以适应不同项目的特定需求。在实际商业项目中,这些系统通常需要进一步优化和集成,以满足团队协作和项目规模的要求。

Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐