Unity 跨项目稳定迁移资源

工作中经常需要同时开启两个unity项目,并且在两个项目之前通过拖拽的方式互相传输文件。但我发现这个方案不是很稳定。有时候直接拖拽到另一个项目时候,预制体及材质球跟贴图互相之间挂载和引用是正确的,有时候会丢失引用。这边制作了一个插件,让我们可以稳定的迁移文件,且不丢失索引。

脚本使用完整指南

第一步:创建脚本

  1. 打开你的 目标项目(即接收资源的那个项目)。

  2. Project 窗口的 Assets 文件夹下,检查是否有名为 Editor 的文件夹。如果没有,请右键 Create > Folder 命名为 Editor(名称必须精确)。

  3. Editor 文件夹内,右键 Create > C# Script,命名为 CrossProjectImporter

  4. 双击打开,将上一条回复中的完整代码覆盖粘贴进去并保存。

第二步:打开工具窗口

  1. 回到 Unity 编辑器,等待脚本编译。

  2. 在顶部菜单栏你会看到多出一个选项:Tools

  3. 点击 Tools > 结构化强导工具(完整版)

第三步:设置与导入流程

1. 设置目标存放路径

在工具窗口的第一个输入框内,填写你希望资源存放的相对路径

  • 例子 :根据你的需求,填写 Arts/Avatar/DefaultSkin/001/001

  • 注意 :不需要写开头的 Assets/,脚本会自动补全。如果路径不存在,脚本会自动创建文件夹。

2. 添加源资源(跨项目选择)
  1. 点击 "添加文件或文件夹" 按钮。

  2. 在弹出的系统文件选择框中,横跨硬盘找到你另一个项目(源项目)的路径。

    • 例子 :定位到 D:\Unity_002\...\002 这个文件夹。
  3. 选中文件夹后点击"确定(选择文件夹)"。

3. 执行强力导入
  1. 核对下方列表,确认路径正确。

  2. 点击绿色的 "执行重定向导入" 按钮。

  3. 脚本会开始物理拷贝文件及其对应的 .meta

  4. 完成后会弹出"导入成功"的提示。

    复制代码
    using UnityEngine;
    using UnityEditor;
    using System.IO;
    using System.Collections.Generic;
    
    public class CrossProjectImporter : EditorWindow
    {
        // 默认目标路径(根据你的需求设定)
        private string targetPathInAssets = "请按需求输入被搬运文件的路径";
        private List<string> selectedFolders = new List<string>();
    
        [MenuItem("Tools/结构化强导工具(完整版)")]
        public static void ShowWindow()
        {
            CrossProjectImporter window = GetWindow<CrossProjectImporter>("结构化导入");
            window.minSize = new Vector2(450, 500);
        }
    
        void OnGUI()
        {
            EditorGUILayout.Space(10);
            GUILayout.Label("1. 目标存放位置 (目标项目内)", EditorStyles.boldLabel);
            
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Assets/", GUILayout.Width(50));
            targetPathInAssets = EditorGUILayout.TextField(targetPathInAssets);
            EditorGUILayout.EndHorizontal();
    
            EditorGUILayout.Space(10);
            GUILayout.Label("2. 选择源文件夹 (跨项目选择整个文件夹)", EditorStyles.boldLabel);
            
            if (GUILayout.Button("添加源文件夹 (Add Folder)", GUILayout.Height(30)))
            {
                string path = EditorUtility.OpenFolderPanel("选择要搬运的【S13】特效文件夹", "", "");
                if (!string.IsNullOrEmpty(path) && !selectedFolders.Contains(path))
                {
                    selectedFolders.Add(path);
                }
            }
    
            // 显示已选列表
            EditorGUILayout.BeginVertical("box");
            if (selectedFolders.Count == 0) EditorGUILayout.LabelField("列表为空,请先添加文件夹...");
            for (int i = 0; i < selectedFolders.Count; i++)
            {
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField(selectedFolders[i], EditorStyles.miniLabel);
                if (GUILayout.Button("移除", GUILayout.Width(40))) selectedFolders.RemoveAt(i);
                EditorGUILayout.EndHorizontal();
            }
            EditorGUILayout.EndVertical();
    
            EditorGUILayout.Space(20);
    
            if (selectedFolders.Count > 0)
            {
                GUI.backgroundColor = Color.green;
                if (GUILayout.Button("🚀 执行完整搬运 (保持结构+预制体+GUID)", GUILayout.Height(50)))
                {
                    ExecuteFullImport();
                }
                GUI.backgroundColor = Color.white;
            }
            
            EditorGUILayout.HelpBox("提示:\n1. 脚本会连带 .meta 物理拷贝,保留所有引用。\n2. 若导入后看不见文件,请在 Project 窗口右键 Reimport。", MessageType.Info);
        }
    
        void ExecuteFullImport()
        {
            // 1. 确保目标根目录存在
            string rootDestDir = Path.GetFullPath(Path.Combine(Application.dataPath, targetPathInAssets));
            if (!Directory.Exists(rootDestDir)) Directory.CreateDirectory(rootDestDir);
    
            try
            {
                AssetDatabase.StartAssetEditing(); // 暂停资源数据库自动刷新,提高拷贝效率
    
                foreach (string sourcePath in selectedFolders)
                {
                    if (!Directory.Exists(sourcePath)) continue;
    
                    string folderName = Path.GetFileName(sourcePath);
                    string finalFolderDest = Path.Combine(rootDestDir, folderName);
    
                    // 执行递归拷贝
                    CopyDirectoryWithStructure(sourcePath, finalFolderDest);
                }
            }
            finally
            {
                AssetDatabase.StopAssetEditing(); // 恢复刷新
            }
    
            // 2. 核心:强制同步刷新,解决"看不见预制体"的问题
            EditorUtility.DisplayProgressBar("同步中", "正在强力刷新 AssetDatabase...", 0.5f);
            AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
            EditorUtility.ClearProgressBar();
    
            Debug.Log($"<color=green>搬运成功!</color> 目标路径: Assets/{targetPathInAssets}");
            
            EditorUtility.DisplayDialog("搬运完成", "文件夹结构及预制体已物理搬运成功。\n\n关于FBX报错:由于模型动画曲线异常导致,通常不影响预制体使用。", "确定");
            selectedFolders.Clear();
        }
    
        void CopyDirectoryWithStructure(string sourceDir, string destDir)
        {
            // 创建目录
            if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
            
            // 拷贝当前文件夹自身的 .meta
            CopyMetaFile(sourceDir, destDir);
    
            // 拷贝所有文件
            string[] files = Directory.GetFiles(sourceDir);
            foreach (string file in files)
            {
                if (file.EndsWith(".meta")) continue; 
    
                string fileName = Path.GetFileName(file);
                string destFilePath = Path.Combine(destDir, fileName);
    
                // 物理拷贝(覆盖模式)
                File.Copy(file, destFilePath, true);
                // 拷贝对应的 meta
                CopyMetaFile(file, destFilePath);
            }
    
            // 递归子目录
            string[] subDirs = Directory.GetDirectories(sourceDir);
            foreach (string subDir in subDirs)
            {
                string subDirName = Path.GetFileName(subDir);
                string nextDestDir = Path.Combine(destDir, subDirName);
                CopyDirectoryWithStructure(subDir, nextDestDir);
            }
        }
    
        void CopyMetaFile(string sourcePath, string destPath)
        {
            string sourceMeta = sourcePath + ".meta";
            string destMeta = destPath + ".meta";
            if (File.Exists(sourceMeta))
            {
                File.Copy(sourceMeta, destMeta, true);
            }
        }
    }
相关推荐
sindyra1 天前
Unity资源内存管理与释放
unity·游戏引擎·资源管理·资源释放·内存释放
CreasyChan1 天前
Unity FairyGUI高斯模糊实现方法
unity·游戏引擎·fgui
avi91111 天前
Unity半官方的AssetBundleBrowser插件说明+修复+Reporter插件
unity·游戏引擎·打包·assetbundle·游戏资源
郝学胜-神的一滴1 天前
深入理解Mipmap:原理、实现与应用
c++·程序人生·unity·游戏程序·图形渲染·unreal engine
一个笔记本2 天前
godot log | 修改main scene
游戏引擎·godot
nnsix2 天前
Unity PicoVR开发 实时预览Unity场景 在Pico设备中(串流)
unity·游戏引擎
一只一只2 天前
Unity之UGUI Button按钮组件详细使用教程
unity·游戏引擎·ugui·button·ugui button
神米米2 天前
Maya快速安装UE4 布料权重绘制插件PhysX导出apx
游戏引擎·ue4·maya
WarPigs2 天前
Unity阴影
unity·游戏引擎