1. 项目概述为什么Unity需要一个跨平台文件对话框在Unity项目开发中尤其是涉及到需要用户选择本地文件或目录的功能时一个稳定、易用且外观统一的文件对话框是刚需。无论是让玩家上传自定义头像、加载本地存档还是在编辑器工具中让开发者选择资源路径这个功能都绕不开。然而Unity引擎本身并没有提供一个开箱即用的、跨平台的本地文件对话框组件。如果你直接去Unity的API文档里翻可能会找到UnityEditor.EditorUtility.OpenFilePanel但请注意这个类名里的Editor已经暴露了它的本质——它只能在Unity编辑器环境下运行一旦打包成Windows、Mac或Android等平台的应用这个API就完全失效了。这就是问题的核心平台差异性。Windows有它的Win32 API或.NET Framework的OpenFileDialogmacOS有NSOpenPanelLinux桌面环境各异而Android和iOS则完全运行在移动端的沙盒环境里文件系统的访问权限和交互方式与桌面端天差地别。自己从头去封装每一套系统的原生API不仅工作量巨大涉及到大量的平台条件编译#if UNITY_EDITOR || UNITY_STANDALONE_WIN等还要处理不同平台下路径格式、异步回调、UI线程阻塞等一堆令人头疼的细节。更麻烦的是如何保证在不同操作系统上对话框的UI和行为能保持一致的用户体验这几乎是一个独立的客户端开发项目。因此社区中出现了许多优秀的第三方解决方案它们封装了底层的复杂性为Unity开发者提供了一个统一的C#接口。CompactStandaloneFileBrowser就是其中备受推崇的一个。它轻量、开源、维护活跃并且真正做到了“开箱即用”支持从编辑器到几乎所有主流桌面和移动平台的发布版本。这个项目实战就是要带你深入理解从直接调用系统API的原始方案到采用成熟、高效的CompactStandaloneFileBrowser的完整演进路径让你彻底掌握在Unity中处理文件对话框的正确姿势。2. 核心需求解析与方案选型在决定采用何种方案之前我们必须先明确自己的核心需求。文件对话框看似简单但细究起来需求点可能很复杂。2.1 功能需求清单一个完整的文件对话框需求通常包括基本功能打开单个文件、打开多个文件、选择文件夹、保存文件。过滤与扩展名能够根据文件类型进行过滤例如“仅显示.png和.jpg文件”。路径设置可以设置对话框的初始打开路径。标题与提示文本自定义对话框的窗口标题和按钮文字。异步操作文件选择是一个需要用户交互的阻塞式操作在Unity的主线程中直接调用会导致游戏卡死因此必须支持异步回调。路径返回值返回用户选择的文件或目录的完整路径字符串或字符串数组。2.2 平台兼容性需求这是跨平台开发的核心挑战。你的方案必须至少覆盖编辑器Editor用于开发阶段测试。独立平台Standalone包括Windows、macOS、Linux。移动平台MobileAndroid和iOS。这里要特别注意iOS由于沙盒机制应用通常只能访问自己沙盒内的文件或者通过特定的UIDocumentPickerViewController来访问用户相册、iCloud等共享区域与桌面端的“浏览整个磁盘”概念完全不同。其他平台如WebGL。在浏览器环境中文件选择通常通过HTML的标签实现完全依赖于浏览器自身的对话框可控性极低。2.3 方案对比原生API vs. 第三方库面对这些需求我们主要有两种实现路径。路径一手动封装各平台原生API这是最直接、理论上最“原生”的方法。你需要为每个目标平台编写特定的代码。Windows可以使用System.Windows.Forms.OpenFileDialog需要为项目添加对System.Windows.Forms的引用且仅适用于Windows平台。macOS通过[DllImport(AppKit)]调用Objective-C的NSOpenPanel。Android通过Unity的AndroidJavaClass和AndroidJavaObject调用Android的Intent.ACTION_GET_CONTENT或Intent.ACTION_OPEN_DOCUMENT。iOS通过[DllImport(__Internal)]调用Objective-C的UIDocumentPickerViewController。优点理论上可以获得该平台最原生、性能最佳的表现。缺点开发成本极高你需要熟悉每个平台的GUI API和调用规范。维护噩梦代码中充斥着大量的#if预处理指令可读性差。任何一个平台的API发生变化都需要你单独适配。UI不统一每个平台的对话框外观、交互流程都不一致用户体验割裂。功能受限在移动端特别是iOS你能做的事情被沙盒严格限制与桌面端功能无法对齐。路径二使用成熟的第三方库以CompactStandaloneFileBrowser为代表的库已经帮我们完成了上述所有脏活累活。优点统一接口一套简单的C# API如FileBrowser.OpenFilePanel通吃所有平台。开箱即用导入插件调用API无需关心底层实现。持续维护开源社区维护会跟随Unity版本和平台API的变化进行更新。体验一致库内部会尽量模拟一个统一的UI在移动端可能调用原生控件但接口一致。功能丰富通常除了基础功能还提供扩展名过滤、多选、初始路径等高级功能。缺点轻微开销引入了一个外部依赖会增加一点包体大小。风格固定对话框的UI风格由库决定自定义程度可能不如完全自己控制。实操心得对于99%的Unity项目尤其是游戏和需要快速迭代的工具强烈推荐直接使用CompactStandaloneFileBrowser这类第三方库。把时间花在游戏逻辑和业务创新上而不是重复造轮子去解决一个已被完美解决的底层交互问题。手动封装原生API只适用于你有极其特殊的、现有库无法满足的定制需求比如需要深度定制Windows对话框的每一个按钮文字但这种情况少之又少。3. 从零封装系统API的艰难之路原理剖析尽管不推荐在生产中使用但了解手动封装的原理对于深入理解跨平台问题的本质非常有帮助。这里我们以Windows平台为例窥探一下背后的复杂性。3.1 Windows平台.NET Framework的OpenFileDialog在Windows独立平台下我们可以利用完整的.NET Framework或.NET Core/.NET 5的Windows兼容包。#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN using System.Windows.Forms; // 需要额外引用 public class WindowsFileDialog { public static string OpenFile(string title, string directory, string filter) { // 注意OpenFileDialog会阻塞当前线程 OpenFileDialog ofd new OpenFileDialog(); ofd.Title title; ofd.InitialDirectory directory; ofd.Filter filter; // 例如 Image files (*.jpg, *.png)|*.jpg;*.png|All files (*.*)|*.* ofd.Multiselect false; if (ofd.ShowDialog() DialogResult.OK) // 这里会弹出系统对话框并阻塞 { return ofd.FileName; } return null; } } #endif关键问题线程阻塞ShowDialog()是同步调用在Unity游戏运行时弹出会卡住整个主线程游戏画面冻结体验极差。平台限定System.Windows.Forms仅在Windows平台有效。在Unity编辑器的非Windows系统如macOS上编译会报错所以必须用#if严格包裹。依赖管理你需要确保项目引用了正确的.NET库。在Unity中可能需要手动编辑.csproj文件或通过特殊方式引入。3.2 Android平台通过Android Intent调用Android没有传统的“文件对话框”而是通过Intent机制启动系统的文件选择器或其他应用如文档管理器、相册来完成选择。#if UNITY_ANDROID !UNITY_EDITOR using UnityEngine; public class AndroidFilePicker { public static void PickImage() { AndroidJavaClass intentClass new AndroidJavaClass(android.content.Intent); AndroidJavaObject intentObject new AndroidJavaObject(android.content.Intent); // 设置动作为获取内容 intentObject.Call(setAction, intentClass.GetStatic(ACTION_GET_CONTENT)); // 设置类型为所有图片类型 intentObject.Call(setType, image/*); // 添加类别使其可从中选择 intentObject.Call(addCategory, intentClass.GetStatic(CATEGORY_OPENABLE)); // 获取当前Activity并启动 AndroidJavaClass unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer); AndroidJavaObject currentActivity unityPlayer.GetStatic(android.app.Activity); currentActivity.Call(startActivityForResult, intentObject, 0); } // 注意还需要在Unity的AndroidManifest.xml中配置并处理Activity的回调结果 // 这需要编写Java插件或使用Unity的AndroidJavaProxy复杂度陡增。 } #endif关键问题异步回调复杂选择结果是通过Android Activity的onActivityResult回调返回的。在Unity中接收这个回调需要编写额外的Java插件或使用复杂的AndroidJavaProxy桥接代码非常晦涩。权限与路径从Intent返回的URI如content://...可能是一个“内容提供器”URI而不是直接的文件路径。你需要使用ContentResolver来打开输入流将其复制到应用可访问的临时目录这个过程很容易出错。功能局限通过ACTION_GET_CONTENT你只能选择文件而无法选择文件夹或指定保存位置。3.3 小结手动封装的痛点通过以上两个例子你可以感受到手动封装的痛苦代码碎片化每个平台一套代码通过#if分割难以阅读和维护。异步处理地狱每个平台的异步机制都不同Windows的线程、Android的Intent回调、iOS的Delegate统一成C#的async/await或回调模式需要大量胶水代码。路径处理坑多不同平台路径格式正反斜杠、URI、权限、沙盒规则完全不同需要大量转换和错误处理。测试困难你必须在每个目标平台上真机测试编辑器环境下很难模拟所有情况。4. CompactStandaloneFileBrowser实战入门现在让我们告别“刀耕火种”拥抱现代工具。CompactStandaloneFileBrowser下文简称CSFB是一个专注于文件对话框的轻量级Unity插件。它的设计哲学是简单、高效、跨平台。4.1 插件导入与基本设置首先你需要获取这个插件。最推荐的方式是通过Unity的Package Manager从Git URL添加打开Unity进入Window - Package Manager。点击左上角的号选择Add package from git URL...。输入插件的Git仓库地址例如https://github.com/yasirkula/UnitySimpleFileBrowser.git请注意CompactStandaloneFileBrowser可能是一个分支或特定版本请以官方仓库为准。等待Unity下载并导入。导入后你通常会在项目的Plugins或Assets目录下看到相关的脚本和预制体。CSFB的核心是一个名为FileBrowser的静态类。4.2 核心API与快速上手CSFB提供了几个非常直观的静态方法FileBrowser.ShowLoadDialog(): 显示加载文件对话框。FileBrowser.ShowSaveDialog(): 显示保存文件对话框。FileBrowser.ShowSelectFolderDialog(): 显示选择文件夹对话框。所有这些方法都接受相似的核心参数并采用异步回调的方式工作完美避免了界面卡死。让我们写一个最简单的示例点击一个UI按钮让用户选择一张图片并在UI的Image组件上显示。using UnityEngine; using UnityEngine.UI; using SimpleFileBrowser; // 这是CSFB常用的命名空间请以实际导入为准 public class FilePickerDemo : MonoBehaviour { public RawImage previewImage; // 用于预览的UI RawImage public Button selectButton; void Start() { selectButton.onClick.AddListener(OnSelectButtonClicked); } void OnSelectButtonClicked() { // 1. 设置过滤器只显示png和jpg文件 FileBrowser.SetFilters(true, new FileBrowser.Filter(Images, .jpg, .png)); // 2. 设置默认过滤器索引从0开始 FileBrowser.SetDefaultFilter(.jpg); // 3. 设置初始路径例如用户的图片文件夹 // FileBrowser.SetInitialPath(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures)); // 4. 弹出文件选择对话框 FileBrowser.ShowLoadDialog( onSuccess: (paths) { // 选择成功后的回调paths是一个字符串数组 if (paths.Length 0 !string.IsNullOrEmpty(paths[0])) { string selectedFilePath paths[0]; Debug.Log(选中的文件: selectedFilePath); LoadAndDisplayImage(selectedFilePath); } }, onCancel: () { // 用户取消的回调 Debug.Log(文件选择被取消); }, pickMode: FileBrowser.PickMode.Files, // 选择文件模式 allowMultiSelection: false, // 是否允许多选 title: 请选择一张图片, // 对话框标题 loadButtonText: 选择 // 确认按钮文字 ); } void LoadAndDisplayImage(string filePath) { // 注意在移动平台或某些情况下filePath可能是一个URI不能直接用File.ReadAllBytes // CSFB通常会返回一个可访问的路径。这里我们使用UnityWebRequest来加载它支持file://协议 StartCoroutine(LoadImageCoroutine(filePath)); } System.Collections.IEnumerator LoadImageCoroutine(string path) { // 为本地文件路径添加 file:// 前缀 string url file:// path; using (UnityEngine.Networking.UnityWebRequest request UnityEngine.Networking.UnityWebRequestTexture.GetTexture(url)) { yield return request.SendWebRequest(); if (request.result UnityEngine.Networking.UnityWebRequest.Result.Success) { Texture2D texture ((UnityEngine.Networking.DownloadHandlerTexture)request.downloadHandler).texture; previewImage.texture texture; } else { Debug.LogError(加载图片失败: request.error); } } } }这段代码已经是一个可工作的原型。点击按钮会弹出一个系统风格的文件选择对话框。选择图片后图片会被加载并显示在UI上。注意事项UnityWebRequest用于加载本地文件时需要在路径前加上file://协议头。这是Unity跨平台文件访问的推荐方式之一比旧的WWW类更好也比直接使用System.IO.File更安全因为后者在WebGL或移动端沙盒内可能无法直接访问某些路径。5. 高级功能与深度配置掌握了基础用法后CSFB还提供了许多高级功能来满足复杂场景。5.1 文件过滤器的灵活运用过滤器是提升用户体验的关键。CSFB的过滤器功能强大。// 示例1多种类型过滤器 FileBrowser.SetFilters(true, new FileBrowser.Filter(图片, .jpg, .jpeg, .png, .gif), new FileBrowser.Filter(文本文件, .txt, .csv, .json), new FileBrowser.Filter(所有文件, .*) ); // 用户将在对话框的下拉框中看到“图片”、“文本文件”、“所有文件”三个选项。 // 示例2自定义过滤器显示名称和扩展名 // Filter构造函数的第一个参数是显示名后面是可变参数的扩展名列表。5.2 多文件选择与文件夹选择只需调整PickMode和allowMultiSelection参数。// 选择多个文件 FileBrowser.ShowLoadDialog( onSuccess: (paths) { foreach(string path in paths) { Debug.Log(path); } }, onCancel: () {}, pickMode: FileBrowser.PickMode.Files, allowMultiSelection: true // 关键参数设为true ); // 选择单个文件夹 FileBrowser.ShowLoadDialog( onSuccess: (paths) { // 选择文件夹时paths数组只有一个元素即文件夹路径 string folderPath paths[0]; Debug.Log(选中文件夹: folderPath); // 可以遍历该文件夹下的文件 string[] files System.IO.Directory.GetFiles(folderPath, *.txt); }, onCancel: () {}, pickMode: FileBrowser.PickMode.Folders, // 模式改为Folders allowMultiSelection: false );5.3 保存文件对话框保存对话框需要用户输入文件名逻辑略有不同。FileBrowser.ShowSaveDialog( onSuccess: (paths) { string savePath paths[0]; // 注意这个路径是用户输入或选择的路径但文件可能不存在。 // 你需要自己实现文件写入逻辑并处理文件已存在等情况CSFB可能会询问是否覆盖但行为取决于平台。 System.IO.File.WriteAllText(savePath, 这里是文件内容); Debug.Log(文件已保存至: savePath); }, onCancel: () {}, pickMode: FileBrowser.PickMode.Files, initialFilename: 默认文件名.txt, // 可以提供一个默认文件名 title: 保存文件 );5.4 自定义UI与外观CSFB通常自带一个UI预制体。你可以找到它例如Assets/Plugins/SimpleFileBrowser/Prefabs/FileBrowserPrefab.prefab直接拖到场景中或通过代码实例化并对其进行修改比如替换按钮图片、调整字体、更改颜色主题等使其更符合你的游戏或应用风格。重要提示自定义UI时请务必保留预制体上必要的组件和脚本引用不要破坏其功能结构。最好先复制一份再进行修改。6. 跨平台实战处理移动端与WebGL的特殊性CSFB虽然统一了接口但不同平台的后端实现天差地别。理解这些差异有助于你写出更健壮的代码。6.1 Android与iOS的权限处理在移动端权限是第一道坎。即使CSFB帮你调起了文件选择器如果用户没有授予应用存储权限选择器可能无法正常工作或只能访问有限区域。Android需要READ_EXTERNAL_STORAGE读取和/或WRITE_EXTERNAL_STORAGE写入权限。在Unity中你需要在Player Settings - Android - Publishing Settings中勾选对应的权限并在Android 6.0 (API 23) 以上版本在运行时动态请求权限。CSFB本身不处理权限请求你需要集成Unity的AndroidPermissionsAPI或第三方权限插件。// 简化版的Android权限检查示例需使用UnityEngine.Android #if UNITY_ANDROID if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead)) { Permission.RequestUserPermission(Permission.ExternalStorageRead); // 权限请求是异步的你需要等待回调或下次操作时再检查 } #endifiOS文件访问主要受沙盒限制。访问相册需要NSPhotoLibraryUsageDescription权限并在Info.plist中添加对应的描述字符串。CSFB在iOS上可能会使用UIDocumentPickerViewController它本身会处理权限提示。你只需要确保在Player Settings - iOS - Camera Usage Description等字段中填写了合理的描述信息。6.2 路径差异与安全访问这是跨平台文件操作最大的坑没有之一。桌面端Win/Mac/LinuxCSFB返回的通常是标准的绝对路径如C:\Users\Name\Pictures\img.jpg或/Users/Name/Pictures/img.jpg。你可以相对安全地使用System.IO下的类进行操作但也要注意路径中的空格和特殊字符。Android返回的路径可能五花八门。可能是content://开头的URI来自系统文档UI或相册也可能是/storage/emulated/0/...这样的真实路径。强烈建议不要直接使用System.IO.File去操作content://URI。CSFB内部通常会尝试处理但最稳妥的方式是使用Unity提供的UnityWebRequest或UnityEngine.Networking.DownloadHandler来加载文件数据就像我们之前示例中做的那样。对于写入优先考虑写入到Application.persistentDataPath应用私有目录这个路径你始终有完全控制权。iOS情况与Android类似返回的可能是file://路径或assets-library://等特殊URL。同样使用UnityWebRequest是最具可移植性的方法。WebGL在浏览器环境中文件选择完全由HTML5的File API驱动。用户选择的文件不会给你一个本地文件路径而是直接给你一个文件对象的数据。CSFB在WebGL平台下其onSuccess回调中得到的paths数组可能是空的或者包含一个临时的Blob URL。你需要通过插件提供的其他方式如FileBrowser.Result对象来获取文件的二进制数据。WebGL平台下你几乎无法直接“保存”文件到用户磁盘的任意位置只能通过触发浏览器下载的方式将数据保存为一个文件。实操心得为了写出真正跨平台的稳健代码在处理CSFB返回的路径时遵循以下原则加载文件优先使用UnityWebRequest对于图片、音频、文本或WWW旧版不推荐新项目使用。它们内部处理了不同平台的路径协议。读取文本/字节如果非要用System.IO先对路径做一个简单的判断如果路径包含://如file://content://则不要直接使用转而用UnityWebRequest下载到内存或临时文件再处理。保存文件始终将文件保存到Application.persistentDataPath可写或Application.temporaryCachePath临时缓存。这是唯一在所有平台尤其是移动端和WebGL都保证可写且安全的位置。不要试图保存到用户选择的“下载”或“文档”目录这在移动端通常是不允许的。6.3 异步回调与Unity协程的集成CSFB的所有对话框都是非阻塞的通过回调函数返回结果。这意味着你的代码逻辑需要适应异步模式。在上面的例子中我们直接在回调中处理了成功逻辑。但在更复杂的业务流中你可能需要将异步操作“串联”起来。一种常见的模式是使用协程Coroutine配合回调来将异步代码写得像同步一样清晰即所谓的“协程化”。public IEnumerator OpenFileCoroutine() { bool isDone false; string selectedPath null; FileBrowser.ShowLoadDialog( onSuccess: (paths) { selectedPath paths[0]; isDone true; }, onCancel: () { isDone true; }, pickMode: FileBrowser.PickMode.Files ); // 等待对话框关闭即回调被触发 while (!isDone) { yield return null; // 每帧检查一次 } if (!string.IsNullOrEmpty(selectedPath)) { // 在这里继续处理选中的文件 selectedPath Debug.Log(协程中获取到路径: selectedPath); // 可以yield return另一个加载文件的协程 yield return LoadFileContentCoroutine(selectedPath); } } // 在某个MonoBehaviour中启动这个协程 // StartCoroutine(OpenFileCoroutine());这种方法将文件选择的异步过程封装进一个协程使得外部调用逻辑看起来是顺序执行的更易于管理。7. 性能优化、调试与常见问题排查即使使用了成熟的库在实际项目中依然会遇到各种问题。这里记录一些实战中积累的经验和常见坑点。7.1 性能注意事项避免频繁调用文件对话框的创建和销毁尤其是移动端调用原生控件有一定开销。不要每帧都尝试打开对话框。确保由明确的用户操作如点击按钮触发并可以添加一个简单的冷却时间或标志位防止重复点击。大文件处理如果用户选择了非常大的文件如数百MB的视频直接使用UnityWebRequest或File.ReadAllBytes一次性加载到内存可能导致卡顿甚至崩溃。对于大文件应该使用流式读取FileStream或分块处理。在移动端更要小心内存压力。路径缓存如果应用需要频繁访问同一个目录可以考虑缓存用户上次选择的路径并通过FileBrowser.SetInitialPath在下一次打开时直接定位到该路径提升用户体验。7.2 调试技巧编辑器与真机差异永远记住在Unity编辑器中测试的行为可能与真机特别是Android/iOS完全不同。编辑器下可能使用操作系统原生对话框路径也是桌面路径。务必在目标真机上进行关键流程测试。日志输出在onSuccess和onCancel回调中详细打印返回的路径信息。对于移动端记录下完整的路径字符串分析其前缀file://content://等这是判断问题来源的第一步。权限检查在移动端调用文件对话框前先输出当前应用的权限状态。可以使用Debug.Log(Permission.HasUserAuthorizedPermission(...))来辅助调试。7.3 常见问题速查表问题现象可能原因解决方案桌面端正常Android/iOS上回调不触发或路径为空1. 权限未授予。2. 移动端使用了特殊的URI后续处理代码不支持。3. 用户取消了选择例如按了返回键但未正确处理onCancel。1. 检查并动态请求存储权限。2. 在回调中打印完整路径确认是content://或file://开头。使用UnityWebRequest加载数据而非System.IO。3. 确保onCancel回调被正确设置和处理。WebGL平台无法打开对话框或报错1. WebGL构建可能禁用了某些不安全操作。2. 浏览器安全策略限制如非用户交互触发的弹出窗口。1. 确保文件对话框是由用户的直接点击事件触发的如onClick。2. 检查浏览器控制台是否有CORS或安全策略错误。WebGL下文件操作限制极多需仔细阅读CSFB的WebGL专项文档。选择的图片/文件加载失败1. 路径错误或文件不存在。2. 使用了错误的API加载如用File.ReadAllText读content://URI。3. 文件格式Unity不支持。1. 打印路径并确认其有效性。2.统一使用UnityWebRequest加载网络和本地文件这是兼容性最好的方法。3. 确认文件扩展名和实际格式匹配Unity支持常见图片和音频格式。保存文件失败特别是在移动端尝试写入到了应用没有权限的目录如SD卡根目录。始终将文件保存到Application.persistentDataPath子目录中。这是应用的私有目录拥有完全读写权限。如果需要让用户在其他应用中也看到该文件可以使用系统的“分享”功能而不是直接写公共目录。对话框UI显示错乱或位置不对1. Unity Canvas的渲染模式或缩放设置冲突。2. CSFB的UI预制体适配问题。1. 确保CSFB的UI预制体被放置在正确的Canvas下并且Canvas的Render Mode和Scale Factor设置合理。2. 检查预制体本身的Rect Transform锚点设置确保其能适应不同屏幕分辨率。在编辑器里运行正常打包后失效平台依赖代码没有正确被包含在构建中。如果你自己写了任何平台#if代码确保条件编译指令正确。使用CSFB则一般无此问题但需确保插件所有必要的源代码和资源都包含在构建中。7.4 一个健壮的文件加载封装示例结合以上所有经验这里提供一个相对健壮的、用于加载任意平台图片文件的工具方法using UnityEngine; using UnityEngine.Networking; using System.Collections; public static class FileUtility { public static IEnumerator LoadTextureFromPath(string filePath, System.ActionTexture2D onLoaded, System.Actionstring onError null) { if (string.IsNullOrEmpty(filePath)) { onError?.Invoke(文件路径为空); yield break; } string loadPath filePath; // 处理可能的平台路径前缀 if (!loadPath.StartsWith(file://) !loadPath.StartsWith(http://) !loadPath.StartsWith(https://) !loadPath.StartsWith(content://)) { // 假设是本地绝对路径添加file://前缀 loadPath file:// loadPath; } // 注意对于Android的content:// URIUnityWebRequest可能也能处理但并非所有情况都行。 // 更稳妥的做法是如果检测到content://使用Android特定的API来获取输入流。 // 这里为简化假设CSFB返回的是可被UnityWebRequest处理的路径。 using (UnityWebRequest request UnityWebRequestTexture.GetTexture(loadPath)) { yield return request.SendWebRequest(); if (request.result UnityWebRequest.Result.Success) { Texture2D texture DownloadHandlerTexture.GetContent(request); onLoaded?.Invoke(texture); } else { Debug.LogError($加载纹理失败: {request.error}, 路径: {loadPath}); onError?.Invoke(request.error); } } } }使用方式// 在CSFB的成功回调中 FileBrowser.ShowLoadDialog(onSuccess: (paths) { StartCoroutine(FileUtility.LoadTextureFromPath(paths[0], (texture) { /* 加载成功使用texture */ }, (error) { /* 处理错误 */ } )); }, ... );这个封装方法尝试处理了不同路径格式并使用UnityWebRequest进行加载在大多数跨平台场景下都能可靠工作。当然对于Android上极其特殊的content://URI可能需要更底层的Android插件来处理但CompactStandaloneFileBrowser通常已经为我们做好了这层转换返回一个更友好的路径。在实际项目中你应该根据CSFB在目标平台上的实际返回路径进行测试和微调。