Unity资源下载进阶:手把手实现带进度条的断点续传下载器(附GitHub源码)

发布时间:2026/6/29 8:04:27

Unity资源下载进阶:手把手实现带进度条的断点续传下载器(附GitHub源码) Unity资源下载进阶构建带进度条的断点续传下载器在游戏开发和多媒体应用中资源下载是不可避免的需求。无论是热更新游戏资源、缓存在线视频还是下载大型DLC内容一个稳定可靠的下载器都能显著提升用户体验。本文将深入探讨如何基于UnityWebRequest构建一个生产级的下载工具实现进度显示、断点续传和后台下载等核心功能。1. 理解UnityWebRequest的基础机制UnityWebRequest是Unity官方推荐的网络请求系统相比传统的WWW类它提供了更精细的控制和更好的性能。要构建一个高效的下载器首先需要深入理解其核心组件DownloadHandlerFile直接将下载数据保存到文件避免内存占用过高UploadHandler虽然下载器不需要上传功能但了解完整请求生命周期很重要CertificateHandler处理HTTPS请求时的证书验证// 基础下载示例 IEnumerator SimpleDownload(string url, string savePath) { using (UnityWebRequest request UnityWebRequest.Get(url)) { request.downloadHandler new DownloadHandlerFile(savePath); yield return request.SendWebRequest(); if (request.result ! UnityWebRequest.Result.Success) { Debug.LogError($下载失败: {request.error}); } else { Debug.Log(下载完成); } } }这个基础版本虽然简单但缺乏关键功能无法显示进度、不能断点续传且大文件下载容易失败。接下来我们将逐步增强这些功能。2. 实现进度显示与UI反馈进度反馈是提升用户体验的关键要素。我们需要考虑两种进度计算方式基于字节数的精确计算适用于已知文件大小的情况基于下载时间的估算适用于未知文件大小的情况进度显示实现要点使用Unity的UI系统创建进度条和文本显示在主线程更新UI避免跨线程问题添加平滑过渡效果避免进度跳动// 带进度回调的下载方法 IEnumerator DownloadWithProgress(string url, string savePath, System.Actionfloat onProgress) { using (UnityWebRequest request UnityWebRequest.Get(url)) { request.downloadHandler new DownloadHandlerFile(savePath); var operation request.SendWebRequest(); while (!operation.isDone) { float progress request.downloadProgress; onProgress?.Invoke(progress); yield return null; } // 最终进度确保显示100% onProgress?.Invoke(1f); } }进度显示优化技巧优化点实现方法效果平滑过渡使用Mathf.Lerp插值避免进度条跳跃多信息显示同时显示百分比和速度提供更丰富反馈预估时间计算剩余下载时间增强用户预期管理3. 断点续传的核心实现断点续传是专业下载器的标志性功能其核心原理是利用HTTP协议的Range头部字段。实现要点包括记录下载状态保存已下载的字节数支持分段请求从断点处继续下载处理异常中断网络恢复后自动继续IEnumerator ResumeDownload(string url, string savePath, System.Actionfloat onProgress) { // 获取文件信息 FileInfo fileInfo new FileInfo(savePath); ulong existingBytes fileInfo.Exists ? (ulong)fileInfo.Length : 0; // 获取总文件大小 UnityWebRequest headRequest UnityWebRequest.Head(url); yield return headRequest.SendWebRequest(); ulong totalBytes ulong.Parse(headRequest.GetResponseHeader(Content-Length)); // 如果文件已完整直接返回 if (existingBytes totalBytes) { onProgress?.Invoke(1f); yield break; } // 设置Range头部 using (UnityWebRequest request UnityWebRequest.Get(url)) { request.SetRequestHeader(Range, $bytes{existingBytes}-); request.downloadHandler new DownloadHandlerFile(savePath, true); var operation request.SendWebRequest(); while (!operation.isDone) { float progress (existingBytes request.downloadedBytes) / (float)totalBytes; onProgress?.Invoke(progress); yield return null; } } }断点续传的边界情况处理服务器不支持Range请求时的回退方案文件被修改后的校验机制磁盘空间不足的预警处理4. 高级功能实现与优化4.1 后台下载支持对于移动平台应用切换到后台时下载可能被暂停。实现后台下载需要考虑// iOS后台任务配置 Application.backgroundDownloadPriority ThreadPriority.High; // Android前台服务通知 if (Application.platform RuntimePlatform.Android) { using (AndroidJavaClass unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (AndroidJavaObject activity unityPlayer.GetStaticAndroidJavaObject(currentActivity)) using (AndroidJavaObject service new AndroidJavaObject(com.example.DownloadService)) { service.Call(startForeground, activity); } }4.2 分段下载加速大文件可以分割为多个部分同时下载显著提升速度获取文件总大小计算每个分段的范围创建多个UnityWebRequest并行下载合并分段文件IEnumerator ParallelDownload(string url, string savePath, int parts 4) { // 获取文件总大小 UnityWebRequest headRequest UnityWebRequest.Head(url); yield return headRequest.SendWebRequest(); ulong totalBytes ulong.Parse(headRequest.GetResponseHeader(Content-Length)); ulong partSize totalBytes / (ulong)parts; // 创建临时目录 string tempDir Path.Combine(Application.temporaryCachePath, download_parts); Directory.CreateDirectory(tempDir); // 启动多个下载协程 ListUnityWebRequest requests new ListUnityWebRequest(); for (int i 0; i parts; i) { ulong start (ulong)i * partSize; ulong end (i parts - 1) ? totalBytes - 1 : start partSize - 1; string partPath Path.Combine(tempDir, $part_{i}); UnityWebRequest request UnityWebRequest.Get(url); request.SetRequestHeader(Range, $bytes{start}-{end}); request.downloadHandler new DownloadHandlerFile(partPath); requests.Add(request); request.SendWebRequest(); } // 等待所有下载完成 while (requests.Any(r !r.isDone)) { yield return null; } // 合并文件 using (FileStream fs new FileStream(savePath, FileMode.Create)) { for (int i 0; i parts; i) { string partPath Path.Combine(tempDir, $part_{i}); byte[] bytes File.ReadAllBytes(partPath); fs.Write(bytes, 0, bytes.Length); File.Delete(partPath); } } Directory.Delete(tempDir); }4.3 错误处理与重试机制健壮的下载器需要完善的错误处理网络异常检测超时、连接失败等服务器错误处理5xx状态码智能重试策略指数退避算法IEnumerator DownloadWithRetry(string url, string savePath, int maxRetries 3, System.Actionfloat onProgress null) { int retryCount 0; float retryDelay 1f; while (retryCount maxRetries) { try { yield return ResumeDownload(url, savePath, onProgress); yield break; // 成功则退出 } catch (System.Exception e) { retryCount; Debug.LogWarning($下载失败({retryCount}/{maxRetries}): {e.Message}); if (retryCount maxRetries) { yield return new WaitForSeconds(retryDelay); retryDelay * 2; // 指数退避 } } } Debug.LogError(下载失败已达最大重试次数); }5. 工程实践与性能优化在实际项目中应用下载器时还需要考虑以下工程化问题内存管理最佳实践及时释放UnityWebRequest资源使用using语句确保资源清理大文件下载时避免额外的内存分配性能监控指标指标监控方法优化方向下载速度计算字节数/时间网络连接质量CPU占用Profiler监控减少主线程负担内存使用内存分析工具及时释放资源平台特定注意事项iOS后台任务限制Android存储权限处理WebGL的跨域限制// Android存储权限检查示例 #if UNITY_ANDROID bool HasStoragePermission() { using (AndroidJavaClass environment new AndroidJavaClass(android.os.Environment)) using (AndroidJavaObject storage environment.CallStaticAndroidJavaObject(getExternalStorageDirectory)) { string path storage.Callstring(getAbsolutePath); using (AndroidJavaClass context new AndroidJavaClass(android.content.Context)) using (AndroidJavaObject activity new AndroidJavaClass(com.unity3d.player.UnityPlayer).GetStaticAndroidJavaObject(currentActivity)) { int permission activity.Callint(checkSelfPermission, context.GetStaticstring(WRITE_EXTERNAL_STORAGE)); return permission 0; // 0表示PERMISSION_GRANTED } } } #endif在实现完整下载器后可以考虑将其封装为可复用的Asset方便在不同项目中快速集成。一个好的下载器组件应该提供可配置的并发下载数优先级队列管理下载速度限制选项详细的回调事件系统实际项目中我们曾遇到一个2.8GB视频文件下载的需求初始实现经常在80%左右失败。通过引入断点续传和分段下载最终成功率提升到99%以上用户反馈显著改善。关键发现是移动网络环境下短时间连接不稳定是常态而非例外因此重试机制和断点续传不是可选功能而是必备特性。

相关新闻