1. 项目概述Cocos Creator本地文件读写不是“直接操作硬盘”而是受限于运行环境的桥梁工程“Cocos Creator如何读写本地文件”——这个标题背后藏着一个绝大多数新手会踩坑的认知盲区Cocos Creator本身不提供跨平台的、无限制的本地文件系统API。它不是Node.js也不是桌面端Electron应用更不是原生Android/iOS工程。它的核心定位是“跨平台游戏/交互内容引擎”而“本地文件读写”这个动作在Web、小程序、原生App这三大主流发布平台上其能力边界、安全策略、实现路径天差地别。我带过不少刚从Unity或原生开发转过来的开发者第一反应就是fs.readFile或FileOutputStream结果在微信小游戏里连cc.sys.localStorage都报错最后发现连cc.sys.isNative都返回false——因为默认构建的是Web版本。所以这个标题真正要解决的问题不是“怎么调用一个函数”而是“如何在Cocos Creator的哲学下绕过沙箱限制、适配多端差异、安全可靠地完成数据持久化”。关键词“Cocos Creator”“本地文件”“读写”三个词组合起来指向的是一套环境感知型数据持久化方案设计。它适合三类人一是正在做离线功能如关卡编辑器、用户存档、配置热更新的中高级开发者二是被H5缓存机制搞晕、想搞懂cc.sys.localStorage和cc.sys.getWritablePath()到底啥关系的新手三是需要把Web版快速移植到iOS/Android原生包却发现文件路径全乱套的项目负责人。这篇文章不会教你复制粘贴几行代码就完事而是带你一层层剥开Cocos Creator的运行时外壳看清每个平台的数据落盘逻辑最终给出一套可直接抄作业、带避坑指南的实操方案。2. 内容整体设计与思路拆解为什么不能统一用一个API——从运行时沙箱本质说起2.1 核心矛盾引擎抽象层 vs 平台原生限制Cocos Creator的架构设计遵循“一次编写多端部署”理念但它绝非魔法。它的JavaScript层JSB和C底层之间隔着一道由平台决定的“能力翻译层”。这道层决定了你写的JS代码最终调用的是哪个平台的原生API而这些API的权限由操作系统和运行环境强制划定。Web平台HTML5完全运行在浏览器沙箱内。浏览器出于安全考虑禁止JS直接访问用户硬盘。你能操作的只有localStorage、sessionStorage、IndexedDB以及通过input typefile触发的用户主动选择文件。cc.sys.getWritablePath()在Web上返回的是一个模拟路径如/cocos-data/实际数据全存在IndexedDB里根本不存在物理文件。微信小游戏/字节跳动小程序等运行在小程序容器内有独立的wx.getFileSystemManager()或tt.getFileSystemManager()。它们提供了比Web更强大的文件IO能力支持创建目录、读写二进制文件但路径必须是wx.env.USER_DATA_PATH这类受控路径且文件大小有严格限制微信目前单文件2MB总空间50MB。cc.sys.isNative在这里返回false但cc.sys.platform是cc.sys.WECHAT_GAME这是关键识别信号。原生平台iOS/Android这才是真正能“读写本地文件”的地方。Cocos Creator通过JSB桥接将JS调用翻译成Objective-C/Swift或Java/Kotlin的原生文件操作。cc.sys.getWritablePath()此时返回的是真实的沙盒路径iOS是Documents目录Android是getExternalFilesDir你可以自由创建子目录、读写任意格式文件。但注意iOS对Documents目录有iCloud备份策略大文件放这里可能被拒审Android 10又引入了Scoped StoragegetExternalStorageDirectory已被废弃。提示cc.sys.isNative这个API常被误用。它只表示当前是否运行在Cocos Creator原生打包的App里即通过build命令生成的.ipa/.apk而不是指“能否用原生文件API”。在微信小游戏里isNative为false但你依然能用小程序自己的文件系统——关键在于cc.sys.platform的值。2.2 方案选型逻辑三层抽象按需降级基于上述差异我们设计了一套“三层抽象”的读写方案最高层统一接口封装推荐所有项目使用定义DataStorage类内部根据cc.sys.platform自动路由到不同实现cc.sys.WEB→ 使用cc.sys.localStorage字符串键值对 cc.loader.loadRes加载资源cc.sys.WECHAT_GAME→ 使用wx.getFileSystemManager()文件系统cc.sys.IPHONE/cc.sys.ANDROID→ 使用jsb.fileUtils原生文件工具中间层平台特化实现必须针对各平台单独编码每个平台的实现都要处理其特有的约束WeblocalStorage容量仅5MB且只能存字符串大对象必须JSON.stringifyencodeURIComponent编码读取时反向解码。小程序wx.getFileSystemManager().writeFile是异步的但readFileSync不存在必须用readFilePromise包装路径必须用wx.env.USER_DATA_PATH拼接不能硬编码。原生jsb.fileUtils的writeStringToFile只能写文本写二进制得用writeDataToFilegetWritablePath()返回路径末尾不带/拼接子路径时必须手动加/否则iOS会报错。最底层兜底方案应对极端情况当某平台API不可用如用户禁用了微信小程序文件权限自动降级到内存缓存Map对象并记录日志提醒开发者。绝不让整个存档系统崩溃。这套设计的核心思想是把平台差异性锁死在数据层业务逻辑层永远只调用DataStorage.save(player, data)和DataStorage.load(player)。我参与过一个教育类App最初用localStorage硬编码后来要上微信小程序三天改崩两次最后重构成这套三层结构后续接入抖音小程序只花了2小时。2.3 为什么不用第三方库——轻量与可控性的权衡网上有些教程推荐用cordova-plugin-file或capacitor-plugin-file但对Cocos Creator项目来说这是典型的“杀鸡用牛刀”。原因有三体积膨胀一个cordova-plugin-file插件会引入整个Cordova框架而Cocos Creator原生构建根本不依赖Cordova强行集成会导致iOS构建失败找不到CDVPlugin头文件。生命周期冲突Cordova插件的deviceready事件与Cocos Creator的cc.game.onStart时机不一致容易出现“插件未就绪就调用文件API”的竞态问题。维护黑洞插件更新滞后于Cocos Creator版本。去年有个项目用了capacitor-plugin-file升级到Cocos Creator 3.8后插件的readAsText方法返回undefined查了两天才发现是Capacitor 4.x的API变更而Cocos Creator 3.8的JSB桥接层没同步更新。所以我的经验是原生能力够用就坚决不用第三方。Cocos Creator官方提供的jsb.fileUtils、cc.sys、小程序官方API已经覆盖95%的场景。剩下5%用纯JS方案兜底如内存缓存用户提示比引入一个黑盒插件更可控、更易调试。3. 核心细节解析与实操要点从路径、编码到权限每一个细节都是坑3.1 路径拼接看似简单实则处处是雷路径问题是本地文件读写里最隐蔽的“静默杀手”。它不会报错但会让你的数据永远找不到。Web平台cc.sys.getWritablePath()返回/cocos-data/但这只是逻辑路径。真实数据存在IndexedDB的cocos_data_store数据库里。你调用cc.sys.getWritablePath() save.json得到/cocos-data/save.json这个字符串只是个Key不是物理路径。所以Web平台根本不需要拼接路径直接用cc.sys.localStorage.setItem(save, JSON.stringify(data))即可。小程序平台wx.env.USER_DATA_PATH返回类似wxfile://.../user_data/的URL但wx.getFileSystemManager()要求路径是绝对路径且必须以wxfile://开头。错误做法wx.env.USER_DATA_PATH save.json→wxfile://.../user_data/save.json正确但如果你写成wx.env.USER_DATA_PATH /save.json多了一个/在某些安卓机型上会变成wxfile://.../user_data//save.json导致writeFile失败。正确姿势是用path.join(wx.env.USER_DATA_PATH, save.json)或手动确保只有一处/分隔符。原生平台cc.sys.getWritablePath()在iOS返回/var/mobile/Containers/Data/Application/XXXX/Documents在Android返回/data/user/0/com.company.game/files。关键点来了这个路径末尾不带/。如果你写cc.sys.getWritablePath() save.jsoniOS上会变成/Documentssave.json少了个/直接报错No such file or directory。而Android可能侥幸成功造成平台差异性Bug。标准解法是定义一个joinPath工具函数function joinPath(base: string, ...paths: string[]): string { let result base; for (const p of paths) { if (p) { // 移除base末尾的/移除p开头的/再拼接 result result.replace(/\/$/, ) / p.replace(/^\//, ); } } return result; } // 使用const fullPath joinPath(cc.sys.getWritablePath(), saves, player.json);注意cc.sys.getSafeUrl这个API在Cocos Creator 3.x里已废弃不要用。它曾用于生成Web资源URL但和文件读写无关混淆使用会导致cc.loader.load加载失败。3.2 编码与序列化JSON不是万能的二进制才是真相很多开发者以为“存档就是JSON.stringify(obj)”这在Web和小程序上勉强可行但在原生平台尤其是存图片、音频、加密数据时会出大问题。文本数据JSON/配置Web和小程序用JSON.stringify没问题但要注意Date对象、undefined、function会被忽略。最佳实践是用JSON.stringify(obj, null, 2)带缩进存档方便调试用JSON.parse(str)读取。但必须包裹try...catch因为用户可能手动编辑了JSON文件导致语法错误。二进制数据图片、音频、加密存档这是jsb.fileUtils的主战场。writeStringToFile只能写UTF-8文本写二进制必须用writeDataToFile。而writeDataToFile接收的是ArrayBuffer或Uint8Array。常见错误错误1jsb.fileUtils.writeStringToFile(data, path)→data是ArrayBuffer但API期望string静默失败。错误2new Uint8Array(data).buffer→data已经是ArrayBuffer再new Uint8Array是多余操作还可能改变数据。正确流程从网络加载图片cc.loader.load(res/img.png, cc.Texture2D)→ 得到cc.Texture2D对象。获取像素数据const texture img.getComponent(cc.Sprite).spriteFrame.texture; const data texture.getPixelData();→data是Uint8Array。写入文件jsb.fileUtils.writeDataToFile(data.buffer, fullPath);注意是data.buffer不是data。跨平台兼容性陷阱同一个ArrayBuffer在Web上用Blob保存在小程序用wx.getFileSystemManager().writeFile({data: arrayBuffer})在原生用jsb.fileUtils.writeDataToFile(arrayBuffer, path)。但arrayBuffer本身是通用的所以你的DataStorage类可以这样设计async saveBinary(key: string, data: ArrayBuffer): Promiseboolean { const path this.getPath(key); switch (cc.sys.platform) { case cc.sys.WEB: // Web不支持直接存ArrayBuffer转成base64存localStorage或用IndexedDB const base64 this.arrayBufferToBase64(data); cc.sys.localStorage.setItem(key, base64); return true; case cc.sys.WECHAT_GAME: return new Promise((resolve) { wx.getFileSystemManager().writeFile({ filePath: path, data: data, success: () resolve(true), fail: () resolve(false) }); }); default: return jsb.fileUtils.writeDataToFile(data, path); } }3.3 权限与异常处理用户拒绝你的App崩溃文件操作是高风险操作必须假设每一次调用都可能失败。小程序权限微信小游戏没有单独的“文件存储权限”但wx.getFileSystemManager()的writeFile在首次调用时如果用户手机存储空间不足会静默失败。必须检查fail回调并在UI上友好提示“存档失败请检查手机存储空间”。不能只打印console.error。原生平台权限AndroidCocos Creator 3.x默认targetSdkVersion是30Android 11Scoped Storage强制启用。jsb.fileUtils的getWritablePath()返回的是App专属目录无需申请WRITE_EXTERNAL_STORAGE权限。但如果你错误地用了getExternalStorageDirectory()就必须在AndroidManifest.xml里声明权限且Android 11该权限已失效。结论永远用getWritablePath()别碰外部存储。统一异常处理模板所有文件操作必须包裹try...catch并区分错误类型try { if (cc.sys.platform cc.sys.WEB) { cc.sys.localStorage.setItem(key, value); } else if (cc.sys.platform cc.sys.WECHAT_GAME) { await new Promise((resolve, reject) { wx.getFileSystemManager().writeFile({ filePath: fullPath, data: value, success: resolve, fail: reject }); }); } else { jsb.fileUtils.writeStringToFile(value, fullPath); } } catch (e) { // e可能是字符串Web、Object小程序、Error原生 const errorMsg e instanceof Error ? e.message : String(e); console.error([DataStorage] Save failed for ${key}:, errorMsg); // 触发全局存档失败事件UI层可弹窗提示 cc.systemEvent.emit(storage-save-failed, { key, error: errorMsg }); }4. 实操过程与核心环节实现从零开始搭建一个跨平台存档系统4.1 环境准备与基础类搭建我们以Cocos Creator 3.8.2为基准这是目前最稳定的LTS版本创建一个名为DataStorage的单例管理类。它不依赖任何外部库纯TypeScript实现。第一步创建DataStorage.ts文件// assets/scripts/utils/DataStorage.ts const { ccclass, property } cc._decorator; ccclass export default class DataStorage { private static _instance: DataStorage null; public static getInstance(): DataStorage { if (!DataStorage._instance) { DataStorage._instance new DataStorage(); } return DataStorage._instance; } private constructor() { // 私有构造防止new } // 存档根目录根据不同平台动态计算 private getRootPath(): string { switch (cc.sys.platform) { case cc.sys.WEB: return ; // Web不使用路径 case cc.sys.WECHAT_GAME: return wx.env.USER_DATA_PATH; case cc.sys.IPHONE: case cc.sys.ANDROID: return cc.sys.getWritablePath(); default: return ; } } // 路径拼接工具防错版 private joinPath(...paths: string[]): string { let result ; for (let i 0; i paths.length; i) { const p paths[i]; if (!p) continue; if (i 0) { result p.replace(/\/$/, ); } else { result / p.replace(/^\//, ); } } return result; } // 获取完整文件路径 private getPath(key: string): string { const root this.getRootPath(); if (cc.sys.platform cc.sys.WEB) { return key; // Web用key当key } return this.joinPath(root, saves, ${key}.json); } }第二步实现核心读写方法// 接续上面的DataStorage类 export default class DataStorage { // ... 构造函数和工具方法 ... // 保存JSON数据通用 public saveT(key: string, data: T): boolean { try { const str JSON.stringify(data, null, 2); switch (cc.sys.platform) { case cc.sys.WEB: cc.sys.localStorage.setItem(key, str); break; case cc.sys.WECHAT_GAME: wx.getFileSystemManager().writeFile({ filePath: this.getPath(key), data: str, encoding: utf8, success: () {}, fail: (err) console.error(WX save fail:, err) }); break; case cc.sys.IPHONE: case cc.sys.ANDROID: if (!jsb.fileUtils) { console.warn(jsb.fileUtils not available); return false; } jsb.fileUtils.writeStringToFile(str, this.getPath(key)); break; default: console.warn(Unsupported platform for save:, cc.sys.platform); return false; } return true; } catch (e) { console.error(Save ${key} failed:, e); return false; } } // 加载JSON数据通用 public loadT(key: string): T | null { try { let str: string | null null; switch (cc.sys.platform) { case cc.sys.WEB: str cc.sys.localStorage.getItem(key); break; case cc.sys.WECHAT_GAME: try { const res wx.getFileSystemManager().readFileSync(this.getPath(key), utf8); str res as string; } catch (e) { // 同步读取可能失败尝试异步 console.warn(Sync read failed, fallback to async); return null; // 异步需另写方法 } break; case cc.sys.IPHONE: case cc.sys.ANDROID: if (!jsb.fileUtils) return null; str jsb.fileUtils.getStringFromFile(this.getPath(key)); break; default: return null; } if (!str) return null; return JSON.parse(str) as T; } catch (e) { console.error(Load ${key} failed:, e); return null; } } // 异步加载小程序专用避免阻塞主线程 public async loadAsyncT(key: string): PromiseT | null { if (cc.sys.platform ! cc.sys.WECHAT_GAME) { return this.load(key); } try { const res await new Promiseany((resolve, reject) { wx.getFileSystemManager().readFile({ filePath: this.getPath(key), encoding: utf8, success: resolve, fail: reject }); }); return JSON.parse(res.data) as T; } catch (e) { console.error(Async load ${key} failed:, e); return null; } } }第三步在游戏启动时初始化// assets/scripts/app/AppStart.ts const { ccclass } cc._decorator; ccclass export default class AppStart extends cc.Component { start() { // 游戏启动时检查存档目录是否存在原生平台 if (cc.sys.isNative jsb.fileUtils) { const savesPath cc.sys.join(cc.sys.getWritablePath(), saves); if (!jsb.fileUtils.isDirectoryExist(savesPath)) { jsb.fileUtils.createDirectory(savesPath); } } // 加载玩家存档 const playerData DataStorage.getInstance().load{level: number, coins: number}(player); if (playerData) { console.log(Player loaded:, playerData); } else { console.log(No player data, starting new game); } } }4.2 高级功能二进制存档与资源热更新存档不只是JSON有时你需要存一张用户截图、一段录音或者一个加密的存档文件。实现截图存档功能// 在DataStorage类中添加 public async saveScreenshot(key: string): Promiseboolean { if (!cc.sys.isNative) { console.warn(Screenshot save only supported on native platforms); return false; } try { // 创建渲染纹理捕获当前屏幕 const renderTexture new cc.RenderTexture(); const camera cc.Camera.main; if (!camera) return false; // 设置渲染纹理大小 const size cc.view.getVisibleSize(); renderTexture.initWithSize(size.width, size.height); // 渲染到纹理 camera.render(renderTexture); // 获取像素数据RGBA, 4 bytes per pixel const data renderTexture.readPixels(); const width renderTexture.width; const height renderTexture.height; // 转换为PNG格式需要额外的PNG编码库此处简化为RAW // 实际项目中推荐用pngjs或pixijs的utils.TextureUtils.encode但需自行集成 // 这里我们直接存RAW数据后缀用.bin const fullPath this.joinPath(this.getRootPath(), screenshots, ${key}.bin); // 写入文件 jsb.fileUtils.writeDataToFile(data.buffer, fullPath); console.log(Screenshot saved to:, fullPath); return true; } catch (e) { console.error(Save screenshot failed:, e); return false; } }资源热更新替代传统cc.loader.loadRes// 从服务器下载新资源并本地缓存 public async downloadAndCache(url: string, key: string): Promisestring | null { try { // 1. 下载文件 const response await fetch(url); if (!response.ok) throw new Error(HTTP ${response.status}); const arrayBuffer await response.arrayBuffer(); const fullPath this.getPath(key); // 2. 根据平台写入 switch (cc.sys.platform) { case cc.sys.WEB: // Web无法直接存二进制用IndexedDB或转base64存localStorage小文件 const base64 this.arrayBufferToBase64(arrayBuffer); cc.sys.localStorage.setItem(key, base64); return data:application/octet-stream;base64,${base64}; case cc.sys.WECHAT_GAME: wx.getFileSystemManager().writeFile({ filePath: fullPath, data: arrayBuffer, success: () console.log(Download cached), fail: (e) console.error(Cache fail:, e) }); return fullPath; default: jsb.fileUtils.writeDataToFile(arrayBuffer, fullPath); return fullPath; } } catch (e) { console.error(Download and cache failed:, e); return null; } } private arrayBufferToBase64(buffer: ArrayBuffer): string { let binary ; const bytes new Uint8Array(buffer); const len bytes.byteLength; for (let i 0; i len; i) { binary String.fromCharCode(bytes[i]); } return btoa(binary); }4.3 实测性能与容量测试别让存档拖垮你的游戏我用一个真实项目数据告诉你存档大小和频率直接影响用户体验。存档类型数据量Web (localStorage)微信小游戏 (USER_DATA_PATH)iOS/Android (Documents)玩家基础数据~2KB✅ 稳定5MB上限✅ 稳定50MB上限✅ 稳定无硬限制关卡编辑器数据~500KB❌ 溢出QuotaExceededError✅ 可存单文件2MB✅ 可存建议分片用户截图1080p~3MB❌ 不支持❌ 单文件超限✅ 可存但需压缩实测结论Web平台localStorage绝对不能存超过1MB的数据。超过后Chrome会抛QuotaExceededErrorFirefox会静默截断。解决方案用IndexedDB替代Cocos Creator 3.x内置了cc.sys.localStorage的indexedDB后备方案但需手动开启在project.config.json里设置useIndexedDB: true。小程序平台单次writeFile最大2MB但USER_DATA_PATH总空间50MB。如果你的存档是10MB必须分片如save_001.bin,save_002.bin并在加载时合并。原生平台Documents目录在iOS会被iCloud备份苹果审核指南明确指出“不应备份用户生成的大文件”。正确做法iOS上用NSCachesDirectory对应cc.sys.getTempPath()Android上用getCacheDir()这些路径不备份且系统会自动清理。注意cc.sys.getTempPath()在iOS返回/Library/Caches在Android返回/data/user/0/com.company.game/cache。它比getWritablePath()更适合存临时、可丢弃的数据如截图缓存、下载中的资源。getWritablePath()应只用于用户明确要保存的数据如游戏进度。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的Bug5.1 典型问题速查表问题现象可能原因排查步骤解决方案cc.sys.getWritablePath()返回空字符串cc.sys.isNative为false当前是Web构建1. 检查构建面板的“平台”是否选为“Android/iOS”2. 查看控制台cc.sys.platform值重新构建为原生平台或改用cc.sys.getSafeUrlWeb资源微信小游戏存档失败控制台无报错wx.getFileSystemManager()调用时机过早wx对象未初始化1. 在onLaunch回调后调用2. 检查wx是否为undefined在cc.game.on(game-start, ...)后初始化DataStorage或用setTimeout延迟100msiOS存档文件写入成功但读取为空路径拼接错误导致写入/Documentsplayer.json少/1.console.log输出完整路径2. 用Xcode连接设备查看Documents目录实际文件严格使用joinPath工具函数确保路径格式正确Android存档在部分机型丢失getExternalFilesDir被废弃新API未适配1. 检查cc.sys.getWritablePath()返回值2. 查看AndroidManifest.xml是否有过时权限删除所有WRITE_EXTERNAL_STORAGE声明只用getWritablePath()JSON.parse报Unexpected token u in JSON at position 0存档文件为空或损坏getItem返回null1.console.log输出原始字符串2. 检查localStorage里对应key是否存在在load方法里增加if (!str5.2 独家避坑技巧来自血泪教训技巧1永远用cc.sys.platform而不是cc.sys.isNative来判断文件能力我曾经在一个项目里用isNative判断是否走原生路径结果在微信小游戏里isNative是false但platform是WECHAT_GAME导致文件操作直接跳过存档全丢了。记住isNative只代表“是不是Cocos原生App”platform才代表“当前运行在哪种环境”。技巧2小程序路径必须用wx.env.USER_DATA_PATH不能用wx.env.USER_DATA_PATH /有一次我在路径拼接时写了wx.env.USER_DATA_PATH / save.json在iOS微信上正常但在华为鸿蒙系统上USER_DATA_PATH末尾自带/结果变成//save.jsonwriteFile静默失败。解决方案用path.posix.join或自己写的joinPath它会自动去重/。技巧3原生平台写入前先创建父目录jsb.fileUtils.writeStringToFile不会自动创建不存在的目录。如果你写/Documents/saves/player.json但saves目录不存在就会失败。必须在写入前检查并创建const dir cc.sys.join(cc.sys.getWritablePath(), saves); if (!jsb.fileUtils.isDirectoryExist(dir)) { jsb.fileUtils.createDirectory(dir); }技巧4Web平台存档用localStorageIndexedDB双保险localStorage容量小IndexedDB复杂。我的方案是小数据10KB用localStorage大数据用IndexedDB并封装统一接口。Cocos Creator 3.x的cc.sys.localStorage底层已自动切换但需确保project.config.json里useIndexedDB: true。技巧5存档版本管理避免旧版数据污染新版游戏更新后存档结构可能变化如新增字段。直接JSON.parse旧数据会报错。解决方案在存档对象里加version字段加载时做迁移interface PlayerData { version: number; level: number; coins: number; } public loadPlayer(): PlayerData | null { const data this.loadPlayerData(player); if (!data) return null; // 版本迁移 if (data.version 2) { data.coins data.coins || 0; // v1没有coins字段设默认值 data.version 2; this.save(player, data); // 自动保存新版本 } return data; }5.3 调试神器跨平台文件查看器开发时你不可能每次都在真机上用Xcode或ADB看文件。我自建了一个简易的“存档查看器”场景创建一个DebugStorageScene里面放一个ScrollView和RichText。在start里遍历cc.sys.getWritablePath()下的saves目录原生或读取localStorage所有keyWeb。点击某个存档用JSON.stringify(data, null, 2)格式化显示。支持“编辑并保存”按钮直接修改JSON后回写。这个工具在团队协作时救了大命——策划可以直接在手机上改数值不用等程序员打包。6. 性能优化与未来演进当你的游戏用户突破百万6.1 大数据量存档的优化策略当你的游戏有100万用户平均每个用户存档5MB总数据量就是500TB。这时候光靠本地文件就不够了。增量存档Delta Save不存全量只存变化。例如玩家只改了金币数就只存{ coins: 1000 }服务端合并时用Object.assign(old, delta)。Cocos Creator里实现很简单public saveDelta(key: string, delta: Partialany): boolean { const old this.loadany(key) || {}; const merged Object.assign({}, old, delta); return this.save(key, merged); }压缩存档对JSON存档用lz-string库压缩Web/小程序原生平台用zlib需自己编译JSB模块。实测1MB JSON可压到300KB节省70%空间。分片存储把一个大存档拆成多个小文件player_meta.json,player_inventory.json,player_skills.json按需加载避免一次读取整个大文件阻塞主线程。6.2 云同步的平滑过渡方案本地存档是基础但用户换手机后数据就丢了。云同步是终极方案但不能一刀切。渐进式云同步第一阶段本地存档 云端备份用户点击“