公司动态
Cocos Creator飞机大战实战:模块化架构、对象池优化与多平台广告集成
1. 项目概述从源码到可运营的飞机大战最近有不少朋友在找Cocos Creator开发的飞机大战小游戏源码而且特别强调要“支持流量主”和“可编译各大平台”。这其实反映了一个很实际的需求大家不再满足于仅仅学习一个游戏Demo而是希望拿到一个能直接跑起来、能接入广告变现、并且能一键发布到微信小游戏、抖音小游戏、原生平台等渠道的完整项目。我手头正好有一个基于Cocos Creator 3.x版本迭代了多次的飞机大战项目今天就来把它彻底拆解一遍从代码结构、核心玩法实现到流量主接入和全平台发布把里里外外都讲清楚。这个项目麻雀虽小五脏俱全。它不仅仅是一个教学范例更是一个可以直接上线的产品原型。核心玩法是经典的纵版卷轴射击玩家控制飞机躲避敌机弹幕并击落它们获取分数和道具。但在此基础上我们集成了完整的游戏框架资源管理、场景切换、UI系统、音效管理、数据持久化本地存档、以及最重要的——广告流量主接入模块。使用Cocos Creator 3.8.1开发确保了对OpenHarmony、微信小游戏、字节小游戏、Web、iOS、Android等平台的良好支持。无论你是想学习Cocos Creator的实战开发还是想快速拥有一个能上线运营的小游戏基底这个项目都能提供直接的参考。2. 项目整体架构与设计思路拿到一个源码项目第一步不是直接看代码而是先理解它的整体架构。一个好的架构能让后续的功能扩展、问题排查和平台适配事半功倍。我们这个飞机大战项目采用了在Cocos Creator社区中比较流行且实用的模块化设计。2.1 核心目录结构解析打开项目资源管理器你会看到类似下面的结构。我强烈建议在开始任何修改前先熟悉这个结构。assets/ ├── scripts/ # 所有游戏逻辑脚本 │ ├── manager/ # 管理器模块单例 │ │ ├── GameManager.ts # 游戏总控状态、分数、生命周期 │ │ ├── AudioManager.ts # 音效统一管理 │ │ ├── UIManager.ts # UI面板打开/关闭、弹窗管理 │ │ ├── PoolManager.ts # 对象池用于飞机、子弹、特效 │ │ └── AdManager.ts # 广告管理封装各平台差异 │ ├── entity/ # 游戏实体 │ │ ├── Player.ts # 玩家飞机逻辑移动、射击、受击 │ │ ├── Enemy.ts # 敌机基类包含不同敌机类型子类 │ │ ├── Bullet.ts # 子弹基类玩家子弹、敌机子弹 │ │ └── Prop.ts # 道具加生命、火力升级等 │ ├── ui/ # UI面板控制脚本 │ │ ├── GameUI.ts # 游戏内HUD分数、生命值 │ │ ├── StartPanel.ts # 开始界面 │ │ ├── OverPanel.ts # 结束界面 │ │ └── SettingPanel.ts # 设置界面 │ └── utils/ # 工具类 │ ├── Constants.ts # 全局常量标签、层、事件名 │ └── StorageUtil.ts # 本地存储封装 ├── resources/ # 动态加载的资源预制体、音效等 ├── scenes/ # 游戏场景如start, game, loading └── ... (其他Cocos标准目录)这种分层的模块化设计有几个明显好处。首先高内聚低耦合GameManager负责游戏流程AudioManager只管声音AdManager专注广告。修改一个模块比如广告接口变了不会轻易影响到游戏核心逻辑。其次易于维护和协作新人接手能快速定位功能对应的脚本。最后便于平台适配比如AdManager.ts里会用条件编译或动态加载来区分微信小游戏和抖音小游戏的广告API游戏逻辑完全不用关心这些。2.2 对象池PoolManager的设计考量飞机大战中子弹、敌机、爆炸特效都是高频创建和销毁的对象。如果每一发子弹、每一个敌机都走完整的instantiate实例化和destroy销毁流程在低端手机或Web平台很快就会引起严重的性能卡顿和内存抖动。因此对象池是这类射击游戏的性能生命线。我们的PoolManager是一个通用的单例管理器。它的核心思路是“回收再利用”。当需要创建一个敌机时不是直接new而是向对象池“申请”。池子里如果有之前回收的、闲置的敌机节点就把它拿出来重置状态位置、血量、速度后“激活”使用如果池子空了才真正实例化一个新的。当敌机被击毁或飞出屏幕不是直接销毁它而是调用“回收”方法将其隐藏、重置并放回池子等待下次使用。实操心得在设计对象池时一个常见的坑是忘记彻底重置回收对象的状态。比如敌机被回收时如果它的速度、血量、当前动画状态没有重置下次取出来就会带着上一次的“残影”继续运行导致诡异的Bug。我们的做法是在每个实体如Enemy.ts内部提供一个reset()方法在回收和取出时由PoolManager调用。2.3 事件驱动通信游戏里各个部分需要通信玩家得分了要通知UI更新飞机死了要弹出结束界面点击复活按钮要触发广告并通知游戏逻辑。如果让这些模块互相直接引用、调用方法代码会变成一团乱麻耦合度极高。我们采用了基于Cocos Creator内置的EventTarget系统构建的全局事件中心通常集成在GameManager或一个单独的EventManager中。例如在Player.ts中当玩家击中敌机// Player.ts this.node.emit(Constants.EventType.ADD_SCORE, {score: 100}); // 发射加分事件在GameUI.ts中监听这个事件// GameUI.ts - onLoad 方法中 this.node.on(Constants.EventType.ADD_SCORE, this.updateScore, this);这样Player完全不知道GameUI的存在它只负责“广播”事件。GameUI自己决定是否监听以及如何响应。这种模式让增加新功能比如击中敌机时播放一个音效变得非常容易只需要在AudioManager里再监听同一个事件即可无需修改Player的代码。3. 核心玩法模块深度拆解理解了架构我们深入到游戏最核心的部分玩家控制、敌机逻辑、碰撞与伤害。这部分代码写得好不好直接决定了游戏的手感和体验。3.1 玩家控制与手感调优玩家飞机的控制代码在Player.ts中核心是update方法里的移动逻辑。我们通常支持两种操作触摸拖拽移动端和键盘控制PC端/调试。// Player.ts - update 方法片段 update(deltaTime: number) { // 移动端触摸控制 if (Input.isMousePressed) { let touchPos view.getUIMousePosition(); // 获取触摸点UI坐标 let worldPos this.uiCamera.screenToWorld(touchPos); // 转换到世界坐标 // 使用缓动Lerp让飞机移动更平滑而不是瞬间跳过去 this.node.position Vec3.lerp(new Vec3(), this.node.position, worldPos, this.moveLerpFactor); } // 键盘控制WASD或方向键 let moveDir Vec3.ZERO; if (Input.isKeyPressed(KeyCode.KEY_A)) moveDir.x - 1; if (Input.isKeyPressed(KeyCode.KEY_D)) moveDir.x 1; if (Input.isKeyPressed(KeyCode.KEY_W)) moveDir.y 1; if (Input.isKeyPressed(KeyCode.KEY_S)) moveDir.y - 1; if (!moveDir.equals(Vec3.ZERO)) { moveDir.normalize(); // 归一化避免斜向移动更快 this.node.position.add(moveDir.multiplyScalar(this.moveSpeed * deltaTime)); } // 边界限制防止飞机飞出屏幕 this.clampPosition(); }手感调优关键点移动平滑度直接设置position到触摸点会显得生硬。使用Vec3.lerp线性插值进行平滑过渡moveLerpFactor参数如0.1控制跟随快慢。值越小跟随越平滑但有延迟值越大响应越快但可能抖动。需要根据游戏风格反复测试。移动速度与deltaTime一定要用this.moveSpeed * deltaTime来计算帧间位移。这能保证在不同帧率30FPS或60FPS的设备上飞机的移动速度是恒定的。边界处理clampPosition()函数确保飞机的x, y坐标始终在屏幕可视范围内。计算时要注意考虑飞机精灵的锚点anchor和尺寸确保是机身中心还是机头不超出边界。3.2 敌机生成与行为树简化版敌机系统是游戏趣味性的来源。我们设计了多种敌机类型普通机、快速机、Boss每种有不同的血量、移动模式、开火方式。在EnemySpawner敌机生成器脚本中我们通常使用一个计时器配合波次配置表来生成敌机。更高级的做法是引入一个简化的行为树或状态机来控制单个敌机的行为。例如一个Boss敌机可能的行为序列是入场 - 盘旋 - 发射扇形弹幕 - 冲锋 - 发射追踪导弹 - 循环。我们在EnemyBoss.ts中可以用一个状态枚举和update逻辑来实现// EnemyBoss.ts enum BossState { ENTER, CIRCLE, FAN_SHOOT, CHARGE, HOMING_SHOOT } private _state: BossState BossState.ENTER; private _stateTimer: number 0; update(deltaTime: number) { this._stateTimer deltaTime; switch (this._state) { case BossState.ENTER: // 从屏幕上方移动到指定位置 if (this._stateTimer 2.0) { // 2秒后进入下一状态 this.changeState(BossState.CIRCLE); } break; case BossState.CIRCLE: // 实现圆周运动 this.node.position this._circleCenter.add(new Vec3(Math.cos(this._stateTimer), Math.sin(this._stateTimer)).multiplyScalar(this._circleRadius)); if (this._stateTimer 5.0) { this.changeState(BossState.FAN_SHOOT); } break; // ... 其他状态 } }注意事项敌机的行为逻辑不要写得太“死”。尽量将行为参数化比如圆周运动的中心、半径、速度弹幕的子弹数量、角度、发射间隔等做成可在编辑器里调整的属性用property装饰器。这样策划或你自己调整关卡难度时不需要修改代码只需在Cocos Creator的属性检查器中拖拖滑块就能完成。3.3 碰撞检测与伤害系统碰撞是射击游戏的核心。Cocos Creator提供了物理引擎和碰撞组件两种方式。对于飞机大战这种2D游戏使用碰撞组件Collider更轻量、更直观。我们为玩家、敌机、子弹、道具都添加相应的碰撞组件如BoxCollider2D或CircleCollider2D并设置好分组Group。关键在于Player.ts、Enemy.ts、Bullet.ts中的onCollisionEnter回调函数。// Bullet.ts (玩家子弹) onCollisionEnter(other: Collider2D, self: Collider2D) { // 判断碰撞到的对象分组 if (other.group Constants.CollisionGroup.ENEMY) { let enemy other.node.getComponent(Enemy); if (enemy) { enemy.takeDamage(this._damage); // 调用敌机的受伤方法 this.recycleToPool(); // 子弹命中后回收进对象池 // 播放命中特效 EffectManager.instance.playHitEffect(this.node.position); } } }深度解析伤害计算与传递为什么是enemy.takeDamage(this._damage)而不是直接在子弹脚本里减少敌机血量这体现了关注点分离原则。子弹只负责通知“我打中你了伤害值是X”。具体怎么处理伤害减血、播放受击动画、判断死亡是敌机自己的职责。这样设计后未来如果想增加“无敌状态”、“伤害吸收盾”等效果只需要修改Enemy.takeDamage方法子弹逻辑完全不用动。同理_damage这个值也可以根据玩家当前的火力等级进行动态计算实现丰富的成长体系。4. 流量主广告系统集成实战支持流量主是这个小游戏源码的核心价值之一。广告接入的难点不在于调用API而在于如何优雅地设计使其对游戏代码的侵入性最小并且能灵活适配不同平台。4.1 广告管理器AdManager抽象设计我们的目标是游戏逻辑比如GameManager只需要调用AdManager.instance.showRewardedVideo(revive)就能播放一个激励视频并在广告播放成功后由AdManager通知游戏逻辑“revive复活这个动作完成了”。至于这个广告是微信的、抖音的、还是其他平台的游戏逻辑不关心。为此我们设计了一个抽象层。AdManager.ts作为总入口内部根据编译平台条件动态加载或实例化不同的平台适配器。// AdManager.ts export class AdManager { private static _instance: AdManager null; private _adapter: IPlatformAdAdapter; // 平台适配器接口 public static get instance(): AdManager { if (!this._instance) { this._instance new AdManager(); this._instance.init(); } return this._instance; } private init() { // 根据平台初始化不同的适配器 #if WECHAT_MINI_GAME this._adapter new WeChatAdAdapter(); #elif BYTEDANCE_MINI_GAME this._adapter new ByteDanceAdAdapter(); #else this._adapter new DefaultAdAdapter(); // 用于PC调试的空实现 #endif this._adapter.initialize(); } public showRewardedVideo(adId: string, onSuccess: Function, onFail?: Function) { if (!this._adapter.isRewardedVideoLoaded()) { // 如果广告未加载好可以给用户提示或者直接调用onFail UIManager.instance.showToast(广告加载中请稍候); this._adapter.loadRewardedVideo(() { this._adapter.showRewardedVideo(adId, onSuccess, onFail); }); return; } this._adapter.showRewardedVideo(adId, onSuccess, onFail); } // ... 其他广告类型方法showBanner, showInterstitial等 } // 平台适配器接口 interface IPlatformAdAdapter { initialize(): void; isRewardedVideoLoaded(): boolean; loadRewardedVideo(callback?: Function): void; showRewardedVideo(adId: string, onSuccess: Function, onFail?: Function): void; // ... 其他广告方法 }4.2 微信小游戏广告接入详解以微信小游戏平台为例我们实现WeChatAdAdapter。微信的广告API是全局的wx.createRewardedVideoAd。// WeChatAdAdpater.ts export class WeChatAdAdpater implements IPlatformAdAdapter { private _rewardedVideoAd: any null; private _videoAdId: string your_ad_unit_id_here; // 从微信后台获取 initialize() { // 注意微信小游戏环境判断 if (typeof wx ! undefined wx.createRewardedVideoAd) { this._rewardedVideoAd wx.createRewardedVideoAd({ adUnitId: this._videoAdId }); // 监听广告错误 this._rewardedVideoAd.onError((err: any) { console.error(激励视频广告加载/播放失败, err); }); // 预加载 this.loadRewardedVideo(); } } isRewardedVideoLoaded(): boolean { return this._rewardedVideoAd ! null; } loadRewardedVideo(callback?: Function) { if (this._rewardedVideoAd) { this._rewardedVideoAd.load().then(() { callback callback(); }).catch((err: any) { console.error(预加载激励视频失败, err); }); } } showRewardedVideo(adId: string, onSuccess: Function, onFail?: Function) { if (!this._rewardedVideoAd) { onFail onFail(广告未初始化); return; } // 监听用户看完广告获得奖励 this._rewardedVideoAd.onClose((res: any) { // 注意res.isEnded 表示用户是否完整观看了视频 if (res res.isEnded) { onSuccess(); // 观看完成触发成功回调 } else { // 用户中途关闭了广告 onFail onFail(广告未播放完毕); } }); this._rewardedVideoAd.show().catch(() { // 如果show失败尝试重新加载后再展示 this._rewardedVideoAd.load() .then(() this._rewardedVideoAd.show()) .catch((err: any) { onFail onFail(广告展示失败); }); }); } }避坑指南广告加载与异常处理广告单元ID每个平台的广告后台如微信小游戏后台、字节跳动开发者平台都需要创建广告位获得唯一的adUnitId。切记不要在代码中硬编码应该做成配置表或从服务器动态获取方便后期更换。加载时机广告加载需要时间。最佳实践是在游戏启动时、关卡加载间隙等时机预加载激励视频和插屏广告。调用show之前一定要用isRewardedVideoLoaded()检查状态如果未加载好先调用load并给用户友好提示如“广告加载中”加载成功后再自动触发展示。直接调用show在未加载好时会失败。关闭回调与奖励发放onClose回调是发放奖励的唯一依据。必须判断res.isEnded微信或等效字段。只有用户完整看完广告才能发放游戏内奖励如复活、金币。绝对不能在广告一打开就发放奖励否则会被平台判定为违规导致广告权限被关闭。测试与正式环境各平台都提供测试专用的广告ID在开发阶段使用它们不会产生实际计费也能看到完整的广告流程。4.3 广告场景与用户体验平衡接入广告不是为了赶走用户而是为了在提供免费游戏的同时获得合理收益。如何放置广告至关重要。激励视频Rewarded Video用于正向激励。经典场景游戏复活玩家飞机被击毁后提供“观看广告复活”选项。关卡结算奖励翻倍通关后奖励金币或道具提供“看广告翻倍”选项。获取稀缺资源每日免费宝箱次数用完后看广告可额外开启。技巧复活广告的按钮要醒目但不要强制弹出遮挡画面。通常是在游戏结束界面作为一个与“重新开始”、“返回主页”并列的选项。插屏广告Interstitial Ad用于自然间隔点。经典场景游戏关卡之间从一关切换到下一关的加载界面之后。返回主菜单时从游戏内退出到开始界面时。技巧频率不能太高避免每次死亡或返回都弹插屏会引起用户反感。可以设计为“每玩三局弹出一次”或“仅在分数超过一定阈值后弹出”。Banner广告横幅广告用于长期曝光。通常放在游戏主页面的顶部或底部。技巧在核心游戏进行中即Game场景不要显示Banner广告它会遮挡游戏区域影响操作和体验。只在非游戏的主界面、商店界面等地方显示。在我们的源码中UIManager和GameManager会协作管理这些广告触发点。例如在OverPanel.ts结束界面的show方法中会根据一个随机数或局数计数决定是否展示插屏广告。而复活按钮的点击事件则直接调用AdManager.instance.showRewardedVideo(revive, this.onReviveSuccess.bind(this))。5. 多平台编译与发布流程“可编译各大平台”是另一个硬性要求。Cocos Creator的强大之处就在于其“一次开发多平台发布”的能力。但“能编译”和“能顺利上线”之间还有不少配置和适配工作。5.1 平台相关配置要点在Cocos Creator的项目设置和构建发布面板中需要针对不同平台进行配置。微信/抖音小游戏AppID必须填写从对应平台后台获取的正确AppID。游戏包名通常与AppID关联需保持一致。小游戏启动路径一般保持默认即可。**注意这些平台的构建产物是一个.wx或.bytedance的目录需要使用对应平台的开发者工具微信开发者工具、抖音开发者工具打开这个目录进行进一步的预览、调试和上传。原生平台iOS/Android包名Bundle Identifier / Package Name格式必须正确如com.yourcompany.planewar。这是应用的唯一标识上架商店的关键。应用名称、版本号、图标根据商店要求设置。构建模板选择default或link纯引擎。对于简单小游戏default模板即可它包含了必要的原生壳。加密脚本如果担心代码被反编译可以勾选“加密脚本”并设置一个密钥。务必保管好密钥丢失后无法恢复。Web Mobile标题浏览器标签页上显示的名字。服务器地址如果游戏资源需要从服务器加载在此填写。本地调试可留空。内嵌数据建议勾选将脚本和资源打包成一个单一的.html文件部署最简单。5.2 条件编译处理平台差异不同平台的API、能力、屏幕适配规则可能不同。我们使用Cocos Creator的条件编译功能来编写平台特定的代码。这在之前的AdManager中已经看到#if WECHAT_MINI_GAME。另一个常见场景是分享功能。微信小游戏有wx.shareAppMessage抖音小游戏有tt.share。// ShareManager.ts public shareGame() { #if WECHAT_MINI_GAME wx.shareAppMessage({ title: 超好玩的飞机大战快来挑战, imageUrl: assets/share.jpg // 分享图路径 }); #elif BYTEDANCE_MINI_GAME tt.share({ channel: video, title: 一起来玩飞机大战, // ... 抖音分享参数 }); #else // 其他平台如Web可以复制链接或提示 console.log(分享功能仅在特定平台可用); #endif }构建发布实操步骤菜单栏 - 项目 - 构建发布打开构建面板。在左上角下拉框选择目标平台如“微信小游戏”。填写该平台必填的配置项如AppID。点击构建。构建完成后点击生成对于小游戏平台或编译对于原生平台需要提前配置好Android SDK/NDK或Xcode环境。对于小游戏用对应开发者工具打开生成目录进行调试和上传。对于原生平台编译后会生成Xcode项目或Android APK/ABI可进行真机调试和商店发布。5.3 屏幕适配与性能优化不同平台的设备分辨率碎片化严重。Cocos Creator的Canvas组件提供了Fit Height,Fit Width等适配策略。对于飞机大战这种竖屏游戏通常选择Fit Height模式并设计一个固定的设计分辨率宽度如750px。这样在不同宽高比的手机上游戏内容都能在垂直方向上完整显示水平方向两侧可能会有黑边或扩展背景这是可接受的。性能优化是保证低端机流畅运行的关键Draw Call合并确保UI和游戏场景中材质、纹理相同的静态精灵尽量放在同一个节点下或者使用自动图集Auto Atlas功能将碎图打包。减少透明重叠大量半透明的Sprite如爆炸特效重叠会显著增加Overdraw。控制同时出现的爆炸数量或使用粒子系统替代序列帧动画。对象池如前所述这是必须的。纹理压缩针对Android和iOS平台在构建时启用对应的纹理压缩格式如ASTC, PVRTC, ETC2能大幅减少包体和内存占用。释放无用资源在场景切换时如从游戏场景回到开始菜单手动释放游戏场景中加载的、不再需要的资源如敌机预制体、背景纹理调用resources.release或assetManager.release。6. 常见问题排查与调试技巧在实际开发和编译过程中你肯定会遇到各种问题。这里记录一些高频问题的排查思路。6.1 编译与运行问题问题现象可能原因解决方案构建微信小游戏后用开发者工具打开白屏/报错1. 未在微信开发者工具中设置“详情-本地设置-不校验合法域名”。2. 项目路径包含中文或特殊字符。3. 构建时勾选了“分离引擎”但代码中有依赖引擎模块的动态加载。1. 勾选“不校验合法域名”。2. 将项目移到纯英文路径下。3. 取消勾选“分离引擎”或检查动态加载代码。原生平台Android/iOS编译失败1. 环境未配置好SDK, NDK, Xcode。2. Cocos Creator版本与NDK版本不兼容。3. 包名格式错误。1. 检查Cocos Creator偏好设置中的原生开发环境路径。2. 查阅官方文档使用推荐的NDK版本如r21e。3. 检查包名是否符合规范如com.xxx.xxx。游戏在真机上非常卡顿1. 未使用对象池大量Instantiate/Destroy。2. 单帧内Draw Call过高。3. 脚本中存在耗时操作如复杂计算在update中每帧执行。1. 确保所有频繁生成的对象都使用了对象池。2. 使用Cocos Creator的“分析器”查看Draw Call和性能瓶颈。3. 优化算法或将耗时操作分散到多帧执行。6.2 广告相关问题问题现象可能原因解决方案激励视频广告无法加载或播放1. 广告单元ID填写错误或未生效。2. 未在对应平台后台开通广告位或账户异常。3. 测试设备不在广告测试白名单中仅限测试阶段。4. 网络问题。1. 仔细核对广告单元ID确保从正确平台的后台复制。2. 登录平台开发者后台检查广告功能状态和账户状态。3. 在后台将测试设备的ID如微信的OpenID加入测试名单。4. 检查设备网络连接。广告播放成功但奖励未发放1. 广告关闭回调中未正确判断isEnded字段。2. 发放奖励的代码逻辑有误或未执行。3. 游戏状态在广告播放期间被重置。1. 确保只在isEnded为true时才调用奖励回调。2. 在奖励回调函数内加日志确认函数被触发并执行。3. 广告播放前暂停游戏逻辑播放后恢复防止状态冲突。Banner广告不显示或位置错误1. 容器节点尺寸为0或未激活。2. 平台API调用时机不对如在onLoad中调用但Canvas未适配完成。3. 样式设置如top, left错误。1. 确保放置Banner的节点有正确尺寸且active为true。2. 在start或第一个update中或监听Canvas的size-changed事件后再创建Banner。3. 参考平台文档确认样式参数的单位和含义。6.3 游戏逻辑与体验问题问题碰撞检测不灵敏或穿透排查首先在Cocos Creator编辑器中打开“场景”面板上的“物理调试”或“碰撞检测调试”绘制查看碰撞体的形状和位置是否与精灵显示一致。常见原因是碰撞体大小设置不当或者精灵的锚点Anchor导致其视觉中心与碰撞体中心不重合。解决调整碰撞组件Collider的Offset偏移和Size尺寸属性使其匹配精灵的可见区域。对于高速移动的物体如子弹可以考虑在update中使用射线检测Raycast进行更精确的帧间碰撞判断。问题游戏在不同设备上速度不一致根本原因移动、动画等逻辑没有乘以deltaTime帧时间差。deltaTime是上一帧到当前帧的时间间隔在高帧率如60FPS设备上值小低帧率如30FPS设备上值大。用deltaTime参与计算可以保证“距离速度*时间”这个公式在任何帧率下都成立。检查搜索所有update(dt)方法检查飞机移动速度、子弹速度、敌机移动、动画播放速度等是否都使用了* dt或 dt。问题本地存储存档在部分安卓手机失效原因Cocos Creator的sys.localStorage在Web和小游戏平台是稳定的但在某些原生Android系统上如果应用被强制清除数据或权限异常可能会失败。加固方案对存储的数据增加校验和备份机制。例如存储时不仅存数据还存一个简单的校验码如对数据字符串做MD5哈希的前几位。读取时先校验如果校验失败尝试读取一个备份存档。关键数据如最高分可以考虑在游戏正常退出时主动同步一次。