Cocos Creator 手游开发与性能优化实践

分享我第一次开发游戏遇到的性能卡顿问题

Posted by Warden_Gfs on January 29, 2023

随着移动游戏行业的快速发展,玩家对游戏体验的要求也越来越高。特别是对于基于 Cocos Creator 引擎开发的区块链手游,在支持丰富功能和表现力的同时,性能优化成为了不可回避的一大挑战。流畅的动画、稳定的帧率、合理的内存管理不仅能够提升玩家的沉浸感,同时也是衡量游戏质量的重要指标。

在本次项目中,我们隶属网易雷火事业部,以一个前端开发者的角度初次使用 Cocos Creator 开发了一款区块链手游。在开发过程中,我们不仅需要应对技术上的陌生感,还需要解决性能瓶颈,优化游戏的流畅度和响应速度。本文重点分享我们在性能优化方面的实践经验,尤其是针对长列表页面、资源管理、DrawCall 优化等方面的具体解决方案。

整体架构

在项目架构设计上,我们将游戏的主要功能模块进行了清晰划分,以提高开发效率和代码可维护性。以下为本次项目的整体架构图:

1

架构说明

  1. GUI 层
    • 管理所有界面和预制体资源,包括各类游戏页面、字体、弹框系统等。
    • 提供统一的图片资源(SpriteFrame)管理,支持图集优化和资源预加载。
  2. 路由层
    • 负责页面的路由跳转和加载逻辑,如直接加载 Prefab 或调用 Director.loadScene 切换场景。
    • 支持模拟前端路由的方式加载页面,提高页面切换的灵活性。
  3. 动画层
    • 通过动画剪辑、骨骼动画和动画事件实现动态的场景表现力。
    • 提供 tween 缓动系统,支持流畅的动画过渡效果。
  4. 音效层
    • 集中管理全局音频资源,包括背景音乐和音效。
    • 提供音频的播放、暂停、音量控制等功能。
  5. 工具类模块
    • 提供常用资源加载工具、格式化工具、本地存储操作等,提升开发效率。
    • 集成计时器、网络请求封装等功能。
  6. 基础系统
    • 包括渲染引擎、物理引擎和动画引擎的底层支持。
    • 提供高效的图形渲染和物理模拟能力。

通过这种模块化的架构设计,每个功能模块职责清晰,既方便开发与维护,也为性能优化提供了明确的方向。


长列表页面的优化

问题描述

长列表页面(如商城、背包等)需要显示大量的物品卡片或列表项。如果直接加载所有数据,渲染压力会非常大,导致以下问题:

  • FPS(帧率)下降:一次性加载大量预制体和图片资源会导致帧率骤降至 20~30 帧。
  • DrawCall 激增:每个独立的预制体或图片资源都需要单独的绘制调用,导致渲染效率低下。
  • 滚动卡顿:页面滚动时,频繁的加载和渲染操作导致交互体验不流畅。

解决方案 1:资源优化

  1. 图集打包
    将零散的图片资源合并到图集中(使用 SpriteAtlas),减少纹理切换,降低 DrawCall。例如,将商城物品的图标资源统一打包到一个图集中。

  2. 图片压缩
    使用符合目标平台(如手机)的压缩格式(如 .webp.pkm),减少图片资源的大小,降低内存占用。

  3. 资源预加载与延迟加载

    • 预加载:提前加载大资源(如背景图等静态资源),避免加载时的卡顿。
    • 延迟加载:使用 schedule 定时器分批加载列表项,每次加载一部分,减轻瞬时的渲染压力。

解决方案 2:虚拟长列表

虚拟长列表是一种常见的高性能列表渲染方案,适用于需要显示大量列表项的场景。其核心思想是复用有限的节点来渲染无限的列表项

具体实现步骤:

  1. 计算列表高度
    设定每个列表项的固定尺寸和位置,计算整个列表的总高度,并调整列表容器的尺寸。

  2. 创建固定数量的节点
    只创建屏幕可见范围内的节点数量(如 屏幕可见项数 + 2),以保证滚动过程中不会出现空白。

  3. 监听滚动事件
    根据滚动的偏移量动态计算当前可见项的索引范围。

  4. 复用节点
    当某个列表项滚动出屏幕时,将其移动到屏幕另一侧并更新内容,实现节点的复用。

效果对比:

  • 优化前:直接加载所有列表项,帧率 25~30 帧,DrawCall 高达 300+。
  • 优化后:使用虚拟长列表方案,帧率稳定在 60 帧,DrawCall 降至 80~100。

DrawCall 优化

问题描述

DrawCall 是渲染管线中向 GPU 发出的绘制调用,每次调用都会有一定的性能开销。如果游戏中有大量独立的图片或动态对象,DrawCall 数量可能会飙升,导致渲染性能下降。

优化方法

  1. 静态合并
    • 将多个静态对象合并为一个对象,减少渲染批次。例如,将背景装饰元素合并为一个静态图层。
  2. 动态图集
    • 动态将零散的图片合并到图集中,减少纹理切换的开销。可以使用 Cocos Creator 提供的 Dynamic Atlas 功能。
  3. 减少组件数量
    • 避免在节点上挂载过多的渲染组件(如 SpriteLabel),减少 GPU 的绘制开销。

动态对象管理

问题描述

频繁创建和销毁对象会引发垃圾回收(GC),进而导致帧率波动和内存抖动。尤其是对高频操作的对象(例如子弹、特效等),需要格外注意。

优化方法

  1. 对象池
    通过对象池管理需要频繁创建和销毁的对象,将其回收复用,避免反复分配内存。例如:

    const bulletPool = new NodePool();
    const bullet = bulletPool.size() > 0 ? bulletPool.get() : instantiate(bulletPrefab);
    
  2. 延迟销毁
    延迟销毁不再需要的对象,在适当的时机统一释放内存,降低垃圾回收频率。


其他优化手段

  1. 减少复杂计算
    update 函数中避免进行复杂的数学计算,将不必要的逻辑移到其他时机执行,减少 CPU 占用。

  2. 避免内存泄漏
    • 清理未使用的事件监听器和定时器。
    • 对不再使用的资源及时释放内存。
  3. 优化动画性能
    • 合理规划动画的帧数和时长,避免过于复杂的帧动画。
    • 使用 GPU 加速的骨骼动画或网格动画,提升性能。

性能优化的成效

经过多轮优化后,以下是项目性能的最终表现:

  • 帧率(FPS):由优化前的 25~30 帧提升至稳定的 60 帧。
  • DrawCall:由优化前的 300+ 次降至 80~100 次。
  • 内存占用:通过资源压缩和对象池管理,内存使用率显著降低。

以下为优化后的性能表现:

2

游戏优化后的录屏展示

以下是优化后的游戏录屏演示,展示了流畅的竖屏游戏交互体验:


总结与展望

在本次项目开发中,我们通过资源优化、虚拟长列表、DrawCall 优化和动态对象管理等多种手段,有效解决了性能瓶颈,大幅提升了游戏的流畅度和稳定性。这一过程不仅帮助我们掌握了 Cocos Creator 的性能调优技巧,也为未来作为一个前端的游戏开发者提供了一些基础经验。