首页 / 资讯中心 / 文章详情

Unity AssetBundle打包技巧:Editor脚本一键转换.unity3d格式

Unity AssetBundle打包技巧:Editor脚本一键转换.unity3d格式 ★ FEATURED ARTICLE
简介这份资源为Unity3D格式转换脚本工具包主要面向需要在Unity工程中批量处理或优化3D资源的开发者与游戏制作团队。包内共含3个文件涵盖2个js脚本与1个cs脚本可结合Editor目录使用用于实现资源读取、处理及导出Unity3D格式。整体压缩包仅5KB轻量易部署适合作为资源管线辅助工具直接集成进编辑器流程。已有336人浏览学习。脚本涵盖导出自动化、编辑器辅助等常见需求能帮助使用者理解Unity资源序列化与AssetDatabase操作思路并可作为自定义资源优化流程的起点用于精简模型、压缩纹理或快速导出指定资源提升实际项目中的资源管理效率。1. 别再手动拖资源了.unity3d格式转换脚本直接省掉一半打包时间做 Unity 项目迭代的时候最烦的就是每次发版前要把场景、预制体、贴图一个个拖进 AssetBundle 窗口手选路径、手填变体名、再点 Build。某公司开发组一直在用一个民间流传的 .unity3d 格式转换脚本它本质上是个编辑器扩展能把 Unity 场景和资源一键批量打成 .unity3d 包——也就是 AssetBundle 的二进制格式——顺带把资源依赖关系梳理好。这套脚本对中小团队尤其友好不依赖付费插件纯 C# 写在 Editor 目录下点一下菜单就能把所有场景输出成可被运行时加载的 .unity3d 文件。本文会把脚本的核心设计、目录布局、逐段代码逻辑和常见坑一次讲完让你拿到手能跑跑完能改。2. 技术选型为什么用 Editor 脚本而不是运行时 API2.1 打包的本质AssetBundle 与 .unity3d 的关系先厘清一个概念.unity3d 并不是一种神秘的文件格式它本质上是 Unity 的 AssetBundle 在旧版本中的默认扩展名。新版 Unity 默认生成 .bundle 或自定义扩展名但很多老项目、插件市场和网上的资源包仍然习惯用 .unity3d 做后缀尤其在 Unity 5.x 之前的老工程里非常常见。转换脚本做的事情其实就是把选中的场景和资源通过 BuildPipeline.BuildAssetBundles 接口打成 AssetBundle然后把输出文件改名为 .unity3d 后缀方便运行时按字符串路径加载。用 Editor 脚本而不是运行时 API 的关键在于BuildAssetBundles 这个接口只能在编辑器环境下调用运行时没有打包权限。脚本挂在 Editor 菜单下通过 MenuItem 特性注册一个菜单入口点击后走完整个打包流程。这个过程不碰运行时逻辑纯粹是批处理。2.2 为什么不做成命令行工具有一种做法是把打包脚本挂到命令行参数上类似 -executeMethod BuildScript.BuildAll这样做是为了接入 CI/CD 流水线。但本项目的定位是给开发同学本地用的「快捷工具」不是给服务器用的发布流水线所以脚本选择用 MenuItem 菜单触发。不过代码里预留了静态入口想接 Jenkins 或者 GitLab CI 的同学可以改造成 executeMethod 的方式只需要加一层壳。2.3 扩展名后缀的设计考量有人会问直接输出 .bundle 不好吗这里有一个实际兼容问题项目里如果用 WWW.LoadFromCacheOrDownload 或者 AssetBundle.LoadFromFile路径后缀其实不影响加载但网上很多现成的加载管理器会按扩展名过滤资源甚至用扩展名判断资源类型所以保持 .unity3d 后缀在对接老代码时最省事。这个脚本在打包后会把文件名强制改成 .unity3d并在内部记录原始 AssetBundle 名与文件名的映射关系。3. 脚本整体结构拆解目录布局与资源映射3.1 脚本目录布局下面是这套转换脚本在 Unity 工程里的标准摆放位置照着放就行Assets/ ├── Editor/ │ ├── BundleConverter/ │ │ ├── BundleConverterWindow.cs │ │ ├── BundleBuildConfig.cs │ │ ├── BundleBuildHelper.cs │ │ └── BundlePostProcessor.cs │ └── BundleConverter/Resources/ │ └── BundleBuildConfig.assetEditor 文件夹是 Unity 的约定目录这个目录下的脚本只在编辑器里运行不会被编进最终包体。BundleConverterWindow 负责做编辑器窗口BundleBuildConfig 是打包配置的数据类BundleBuildHelper 放核心打包逻辑BundlePostProcessor 是构建后处理负责改名和生成清单。配置资源 BundleBuildConfig.asset 放在 Resources 下是为了方便在窗口里直接读取省去手动查找的步骤。3.2 配置数据结构打包逻辑里最关键的是配置类。每个需要打包的场景或资源包都要有一条配置记录包括包名、打包的源资源路径、是否包含依赖项、压缩方式等。[System.Serializable] public class BundleItemConfig { public string bundleName; // AssetBundle 的逻辑名加载时用这个名字 public string sourceAssetPath; // 要打包的源资源比如 Assets/Scenes/Main.unity public bool includeDependencies; // 是否自动收集依赖的材质、贴图等 public BuildAssetBundleOptions options; // 打包选项例如压缩方式等 } public class BundleBuildConfig : ScriptableObject { public ListBundleItemConfig items new ListBundleItemConfig(); public string outputRoot Assets/StreamingAssets/; }bundleName 是运行时加载的管理钥匙建议使用带层级前缀的名字比如 stage/main、ui/mainpanel。sourceAssetPath 必须填 Assets/ 开头的相对路径填错了打包时会直接报错。includeDependencies 推荐勾选否则场景里引用的模型、材质会脱离主包运行时加载会丢资源。3.3 打包入口从编辑器菜单到窗口脚本暴露了一个菜单入口点击后弹出一个可滚动的列表窗口在这里勾选要打包的场景点 Build 按钮就开始执行。窗口代码不复杂无非是 onGUI 刷列表和按钮回调但有一个细节值得注意窗口数据源直接绑定配置资产改完配置不用重新打开工程即可生效。public class BundleConverterWindow : EditorWindow { [MenuItem(Tools/Bundle Converter/Open Window)] public static void OpenWindow() { var win GetWindowBundleConverterWindow(Bundle Converter); win.Show(); } private void OnGUI() { // 读取配置并渲染列表按钮触发 BundleBuildHelper.BuildAll() if (GUILayout.Button(Build All Bundles)) { BundleBuildHelper.BuildAll(); } } }这一段是标准的编辑器窗口写法重写 OnGUI 然后用 GUILayout 画按钮。关键是按钮回调里调用了静态方法 BuildAll这样以后想接命令行批量构建也能直接复用这层静态逻辑。4. 核心代码拆解从场景扫描到 .unity3d 落盘的完整流程4.1 资源收集器自动扫描场景依赖打包的第一步是把目标场景或者资源路径收集成 AssetBundleBuild 结构。AssetBundleBuild 是 Unity 官方定义的数据结构用来告诉 BuildPipeline 要打包什么、打成什么名字。脚本在这里做了一层封装方便在收集阶段做资源过滤。private static AssetBundleBuild[] CollectBuilds() { var config LoadConfig(); ListAssetBundleBuild builds new ListAssetBundleBuild(); foreach (var item in config.items) { if (string.IsNullOrEmpty(item.sourceAssetPath)) continue; AssetBundleBuild build new AssetBundleBuild(); build.assetBundleName item.bundleName; build.assetNames new[] { item.sourceAssetPath }; builds.Add(build); } return builds.ToArray(); }如果 includeDependencies 勾选脚本不会直接打包依赖资源而是把它们自动打进同一个 bundle。这里用到了 BuildPipeline 的一个特性资源会被自动分析依赖并包含进包体。assetNames 是一个被添加进 bundle 的源资源数组可以传多个场景或模型但一般建议一个 bundle 只对应一个主资源避免多个场景共用同一个包导致加载时的冗余内存。4.2 构建选项压缩方式与增量构建打包选项的设定直接影响产物大小和加载速度这里最关键的参数是 BuildAssetBundleOptions。脚本默认使用 LZMA 整体压缩换取最小体积但 LZMA 有个缺点加载时需要先整体解压包体大时耗时明显。比较合理的是 LZ4 压缩它是块级压缩加载时按需解压适合场景里有很多散资源的情况。public static void BuildAll() { var config LoadConfig(); string outputPath Path.Combine( Application.dataPath.Replace(Assets, ), config.outputRoot); if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath); AssetBundleBuild[] builds CollectBuilds(); BuildPipeline.BuildAssetBundles(outputPath, builds, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget); }BuildAssetBundles 的第三个参数里ChunkBasedCompression 对应的就是 LZ4 块级压缩换成一个简单的枚举值就能在体积和加载速度之间做取舍。如果项目里大多数资源是大贴图可以考虑 LZMA 配合缓存具体看后面避坑章节。activeBuildTarget 取当前编辑器目标平台如果要触发多平台打包这里需要改成 BuildTarget.Android 之类的指定目标。4.3 目录处理输出到 StreamingAssets 的坑Unity 对 AssetBundle 的输出目录是有讲究的直接输出到 Assets 下的任意文件夹会被 Unity 当成工程资源参与编译和 Reimport。脚本里把输出路径指向了 StreamingAssets这个目录是 Unity 专门用来放原始文件的地方不会被打进主包但会原样拷贝到最终包体。这就是为什么 outputRoot 写成 Assets/StreamingAssets 的原因。还有一个细节StreamingAssets 下的文件可以通过 Application.streamingAssetsPath 在运行时读取在 Android 上这个路径会被解析成 APK/JAR 内的压缩路径所以读取时用 UnityWebRequest 而不是 File.ReadAllBytes这一点在二改章节会展开讲。4.4 后处理改名成 .unity3d 并生成清单构建完成后产物默认带的是 bundleName 这个名字可能不带 .unity3d 后缀。为了保证「一键转成 .unity3d 格式」脚本会在构建完成后把产物重命名并额外导出一份 bundle 清单 JSON里面有每个包的逻辑名和物理文件名映射。[PostProcessBuild] private static void RenameBundles() { string outputRoot Assets/StreamingAssets/; foreach (var file in Directory.GetFiles(outputRoot, *, SearchOption.AllDirectories)) { if (file.EndsWith(.manifest) || file.EndsWith(.meta)) continue; string fileName Path.GetFileNameWithoutExtension(file); string renamedPath Path.Combine( Path.GetDirectoryName(file), fileName .unity3d); if (File.Exists(renamedPath)) File.Delete(renamedPath); File.Move(file, renamedPath); } GenerateManifest(); }PostProcessBuild 的坑在于它有两个重载带 BuildTarget 参数的是整个项目构建完成后触发不带参数的是每次 BuildAssetBundles 之后触发。这里用的是后者的隐藏行为因为签名匹配Unity 会自动把它当成打包回调。逻辑上先跳过 .manifest 和 .meta 文件把无后缀的主产物改成 .unity3d中间如果已经有同名文件会被强制覆盖。GenerateManifest 会往 StreamingAssets 里写一个 bundle_manifest.json记录每个 bundle 的新旧文件名对应关系。5. 避坑与排查让 .unity3d 转换脚本不翻车的五个常见问题5.1 打包成功但运行时加载为空场景现象通过 AssetBundle.LoadFromFile 加载 .unity3d 文件后实例化场景发现里面是空的只有场景根节点所有子物体都丢了。原因场景没被当成场景资源打包而是被当成一般资源打进了 bundle。BuildPipeline 需要在 assetNames 里传入 .unity 场景路径且确保场景被包含在 EditorBuildSettings 的 Scene List 里否则容易出现这种「打了但没完全打」的情况。解决在收集构建项之前先检查 EditorBuildSettings.scenes 是否包含要打包的场景。同时确认场景文件没有被排除在打包列表外比如 Assets/Scenes 下被文件夹级 .meta 标记的 Editor 标签不会影响这里。5.2 Assets/StreamingAssets 目录被重复清理现象每次执行打包旧产物没有被清除新产物和旧产物混在一个目录里加载时会以清单为准还是以实际文件为准变得不可控。原因BuildAssetBundles 不会自动清理输出目录旧包如果没被覆盖就一直躺在那里。如果项目以前有别的脚本也往 StreamingAssets 写文件很容易产生脏数据。解决在 BuildAll 开头加一段清理逻辑只清当前脚本维护的产物目录另外加一个 Manifest 文件里记录的文件白名单。删文件时跳过 .meta 文件否则 Unity 会重新导入并生成新的 .meta 导致版本控制里的噪声。5.3 LZMA 压缩导致加载场景耗时翻倍现象打包后包体很小但运行时 LoadFromFile 一个 200MB 的场景要卡好几秒界面直接冻结。原因LZMA 是整体压缩加载时需要把整个包解压到内存IO 消耗集中在一起尤其在大场景下会非常明显。解决换成 ChunkBasedCompression即 LZ4 块级压缩或者干脆用 UncompressedAssetBundle在开发期这样最快。推荐的组合是正式发布用 LZ4本地调试用不压缩。5.4.unity3d文件被 Unity 当成文本文件重新导入现象重命名成 .unity3d 后Unity 编辑器对这个文件执行 Reimport控制台报一堆二进制解析错误。原因Unity 会对 Assets 目录下所有非白名单后缀的文件尝试导入。StreamingAssets 目录下的文件默认不会被导入但是如果后处理脚本里误把输出路径指到了 Assets 下的普通文件夹比如 Assets/Bundles就会触发这个玄学问题。解决路径统一收敛到 Assets/StreamingAssets/并检查 .meta 文件里 importer 类型是否为 DefaultImporter。如果已经报错把文件移出 Assets 再移回来或者直接在磁盘上删除 Library 缓存重新导入一遍。5.5 打包完成却找不到 .manifest 文件现象bundle 文件成功生成了但是同名的 .manifest 没出现导致线上资源校验逻辑跑不通。原因BuildAssetBundles 只在非增量模式下才会输出完整的 manifest 文件。如果上一次构建用了相同的 hash 且没有任何变化增量构建可能不重写 manifest。这个行为在不同 Unity 版本里表现并不完全一致。解决每次打包传参里加上 ForceRebuildAssetBundle 选项这样会全量重新构建虽然慢一点但结果可预期。后续想优化再改成带版本号的增量构建。6. 二次开发思路把脚本改造成可上线的资源管线拿到脚本后大多数人会直接拿来打包场景但实际项目里更刚需的是两个改造方向一是增加增量打包策略而不是每次都全量构建二是给生成的 manifest 加上版本号和依赖树信息让加载器在运行时能自动拉取依赖包。这里建议从增量打包入手改动最小收益最大。先说增量打包的做法。在 BundleBuildConfig 里增加一个字段 buildVersion然后在 BuildAll 之前计算一次资源 Hash。具体方式是用 AssetDatabase.GetAssetDependencyHash 拿到每个源资源的哈希值存一份 JSON 到本地下次构建时对比哈希没变的包直接跳过。这能省掉大量不必要的 IO 和压缩时间。这里我一般会在配置里加一个 skipUnchangedBundles 的开关默认关跑通后再打开。改完增量逻辑后再把加载端的代码配好。运行时加载 .unity3d 文件推荐用 UnityWebRequestAssetBundle 而不是 LoadFromFile尤其是 Android 平台上 StreamingAssets 的路径不是普通文件系统路径。如果项目里已有自己的资源管理器需要注意它是否支持自定义后缀名很多时候资源管理器写死了 .bundle 后缀遇到 .unity3d 直接拒绝加载。缓存层的 key 也建议统一用 bundleName 加上 buildVersion 拼接这样发新包之后不同版本之间不会串缓存。这套脚本还有一个容易被忽略的收益点就是可以做资源依赖分析的可视化。把 manifest 里的依赖数组接到编辑器窗口里画一张树形图每次打包完能直观看到哪些资源被打了多份。某次修一个项目发现同一个 UI 图集被三个 bundle 各自打了一份包体白白多了 40MB查了一遍原因是三个场景各自用 AssetBundleBuild 打包构建器不会自动去重同一资源在多个 bundle 里的冗余副本。解决办法是在收集器里做一次全局资源查重把共享资源单独提取成 common 包。对于想接 CI 的同学在 BundleBuildHelper 上公开一个静态入口不依赖 EditorWindow 就行。打包命令是 unity -batchmode -quit -executeMethod BundleBuildHelper.BuildAll注意 batchmode 下也要显式传 -projectPath否则 Unity 经常找不到工程。另外 batchmode 下的日志只能靠 Debug.Log 输出建议把 BuildAll 里的关键步骤全部写进日志文件。从那以后我每次在新工程里落地这类批量转换脚本都会先强制跑一遍「空目录构建 → 二次构建 → 运行时加载验证」三步哪怕看起来只是改个后缀名的脚本也得保证构建可重复、加载可验证。这套流程看起来笨但能挡住绝大部分隐藏的坑。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站