Skip to content

当 Egret 进入深水区

本文基于大型 RPG 项目《风起江湖》的真实开发经验,完整总结 Egret 在中大型项目进入“深水区”后的结构性问题与根因。
这不是抱怨,而是理解 Egret 架构边界、制定稳定工程策略的基础。


目录

  1. Egret 的定位与天然边界
  2. 引擎层深水问题
  3. EUI / EXML 深水问题
  4. 资源管理与内存问题
  5. 性能与渲染体系问题
  6. 小游戏 / 现代构建链路兼容性
  7. Egret 大型项目的工程级规避策略
  8. 总结:不是选型错误,而是时代约束

1. Egret 的定位与天然边界

Egret 的原始目标:

  • 轻量级 H5 引擎
  • 以 UI 驱动的小游戏
  • 简单渲染、简单资源、简单逻辑
  • ES5 时代设计(非现代前端)

它不是为以下场景设计的:

  • 大世界 RPG
  • 多系统联动
  • 复杂 UI / 多皮肤工程
  • 大量资源 / 动画 / 地图管理
  • 长期迭代的内容型项目

因此 Egret 在“深水区”爆的问题,不是 bug,而是架构天花板。


2. 引擎层深水问题

2.1 hitTest 性能瓶颈(全局矩阵逆变换)

Egret 的默认 hitTest 做以下操作:

  1. 计算 concatenated matrix
  2. 做逆矩阵
  3. 把 stage 坐标变成 local 坐标
  4. 每一层递归做一次
  5. bounds / mask / scrollRect 都要判断

当树深时,其复杂度趋近:

O(N × depth × matrix-invert)
(实际卡顿就是这么来的)

结果:

  • UI 深层嵌套卡
  • 大地图交互卡
  • 热点节点(如技能图标)频繁 hitTest 卡

解决方案:

  • 自己重写本地坐标递归 hitTest
  • 非旋转节点完全不走矩阵逆变换
  • 仅在叶子调用 $hitTest(stageX, stageY) 兜底
  • 大世界的交互完全放弃 Egret hitTest
    → 用逻辑网格/包围盒实现

这部分你已自己实现。


2.2 Graphics / Shape / Sprite 不适合大规模使用

这些节点的特点:

  • 非矩形
  • bounds 不固定
  • hitTest 成本高
  • dirty 区域不透明

大量使用会导致:

  • 渲染成本升高
  • hitTest 成本无法预测

结论:

UI / 场景尽量不要依赖 Graphics 做复杂内容
(序列帧或贴图更稳定)


2.3 Egret 内部的 “魔法字段 / 魔法常量”

如:

  • $UIComponent
  • $Group[5](touchThrough)
  • $maskedObject
  • $getContentBounds()
  • $renderNode

这些字段极其隐含,文档不全。
要做中大型项目,迟早会碰到底层。


3. EUI / EXML 深水问题

3.1 EXML → JS → eval(小游戏的大坑)

EXML 的流程:

XML → AST → JS → eval → skin class → skinParts 绑定

根本问题:

  • 小游戏环境(微信等)禁止 eval
  • 子皮肤未加载 → eval 直接报错
  • 必须预先注册所有皮肤
  • 无法按需加载 EXML
  • 动态皮肤几乎不可用

dev 与 release 工作流完全不同。


3.2 EXML 构建链路老旧

EXML 本质上是:

  • JavaScript 动态 class
  • 生命周期绑定复杂
  • skinParts 绑定写死
  • 必须静态注册
  • 无 JSON 化
  • 断点难调
  • 无现代 UI DSL(对比 Cocos / Laya)

要把 EXML 改为 JSON =
重写 EUI 一半以上功能,不现实。


3.3 UI 生命周期不可控(核心问题)

UI 存在以下问题:

  • removeChild 不会释放资源
  • Image.source = xxx 无法被监控
  • skin → texture 的关系无法追踪
  • theme → skin 的依赖完全静态化
  • 皮肤引用没有回调、没有生命周期 hook

→ 想写 UI 资源引用计数,几乎不可能。
→ UI 越多,显存越难控制。

解决方法:

  • UI 全部常驻内存(换稳定)
  • default.thm.json 裁剪到最小
  • 拆 theme → 按模块加载
  • 编辑器 UI 不放入游戏包

4. 资源管理与内存问题

4.1 RES 系统 = 两个 Map(几乎只够 demo 用)

Egret 的 RES:

map<string, any>  
map<string, Texture>

无:

  • 引用计数
  • 纹理访问记录
  • 图集 → 小图的依赖关系
  • 自动释放
  • 资源版本管理
  • 常驻/临时资源区分

造成的问题:

  • 显存不可控
  • 资源泄漏频繁
  • 图集无法安全卸载
  • UI 与动画共享纹理难管理

你已自行开发:

  • MovieClipManager(ref/unref)
  • TTL 清理
  • 访问时间戳
  • 图集 ref=0→延迟 1 分钟回收

这是正确做法。


4.2 Egret 无法自动释放 UI texture

原因:

  • Image.source 没有 ref/unref
  • 皮肤动态替换没回调
  • 关闭 UI 不会释放纹理

结果:

UI 最好全部常驻内存,否则你自己都不知道什么时候该回收。


5. 性能与渲染体系问题

5.1 renderNode 太老旧,缺乏现代优化

没有:

  • batching
  • dirty region
  • mask 裁剪优化
  • 层级裁剪
  • 自定义 shader(基本不可行)
  • WebGL2 特性

完全不适合做:

  • 大量特效
  • 复杂 UI
  • 高频动画

应对:

  • 简化 UI 层级
  • 尽量使用贴图而非动态绘制
  • 特效做成序列帧而非 shader

6. 小游戏 / 构建链路问题

6.1 微信小游戏禁止 eval → EXML 必须预编译

导致:

  • dev/release 执行链路割裂
  • 动态皮肤全废掉
  • 默认 build 体系必须 hack
  • theme 必须静态注册

6.2 Egret 构建工具停留在 ES5 时代

  • manifest.json 必须手动维护
  • 无现代 bundler 支持
  • 引擎 build 慢、不可控
  • 打包结果难调试

你最终的方案:

  • tsc -w 取代 Egret build
  • 手动维护 manifest
  • 自己写 Promise RES API

这是最佳路线。


7. Egret 大型项目的工程级规避策略

结合《风起江湖》的实践:

7.1 UI 策略

  • UI 尽量少、尽量常驻
  • theme 按模块拆分
  • default.thm.json 发布前裁剪
  • 不动态加载 EXML
  • 避免深层嵌套

7.2 hitTest 策略

  • 大地图不靠显示树 hitTest
  • 自定义本地递归 hitTest
  • 降低矩阵逆变换频率
  • 非旋转对象走快路径

7.3 资源策略

  • 全局资源需要 ref/unref
  • TTL + 时间戳清理
  • 动画单独管理
  • 图集 ref=0 延迟释放
  • UI 资源常驻避免复杂性

7.4 构建策略

  • 完全抛弃 Egret build
  • tsc -w + 手动 manifest
  • 自定义 asset pipeline
  • 不依赖 EXML 的运行时加载

7.5 项目级策略

“Egret 负责渲染,不负责架构。”

系统层、任务层、剧情层、世界逻辑全部写在引擎外部。

→ Egret 只用于渲染,不参与项目复杂性。


8. 总结:不是选型错误,而是时代约束

2022 年的实际情况:

  • Cocos Studio 生态混乱
  • Cocos Creator UI/构建不成熟
  • Laya 名气不足、风险高
  • Egret 当时是 H5 里最稳的选择

你今天遇到的问题,是因为:

项目的“野心”比 Egret 的“天花板”高得多。

这不是你的错误,是 Egret 的时代属性。


最终建议

通过以下策略,你仍可以让 Egret 成为“足够稳的渲染层”:

  • UI 少而稳
  • 资源自建管理
  • theme 裁剪
  • 构建链路自控
  • hitTest 重写
  • 内容/系统层与引擎解耦

让 Egret 不阻碍你的大世界叙事,而只是一个可靠的渲染底板。


风起江湖 · 资料站