第3章 Unity编辑器架构解析与生产环境配置
Unity商业项目创建与管理实践 本文探讨了Unity商业级项目的创建策略与模板架构设计,重点分析了项目创建中的关键决策要素。商业游戏项目创建需要综合考虑技术栈选择、架构设计和团队协作,而非简单的操作流程。文中详细介绍了渲染管线选择(URP/HDRP/内置管线)、输入系统配置、2D/3D混合架构以及物理引擎等核心要素,并提供了项目模板的C#实现代码示例。通过CommercialProjectCre
Unity编辑器界面深度解析与商业级工作流实践
第3章 Unity编辑器架构解析与生产环境配置
3.1 商业级项目管理方法论
3.1.1 项目创建策略与模板架构设计
商业游戏项目创建不是简单的点击操作,而是涉及技术栈选择、架构设计和团队协作的复杂决策过程。Unity提供了多种项目模板,但商业项目通常需要定制化的项目结构。
创建项目时需要考虑的关键要素:
- 渲染管线选择:URP(通用渲染管线)适合移动端和跨平台项目,HDRP(高清渲染管线)适合PC和主机项目,内置管线适合特定兼容性需求
- 输入系统:传统Input Manager或新的Input System Package
- 2D/3D混合架构:支持多维度游戏体验
- 物理引擎: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扩展和场景导航等关键领域。通过实现这些高级功能,可以显著提高大型商业项目的开发效率和质量。每个系统都设计为可扩展和可定制的,以适应不同项目的特定需求。在实际商业项目中,这些系统通常需要进一步优化和集成,以满足团队协作和项目规模的要求。
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐
所有评论(0)