屏幕上有十万棵树,游戏为什么还没有爆内存?

2026-08-11 09:12:38 RAIZ

屏幕上有十万棵树,游戏为什么还没有爆内存?


睿智创新RAIZ,一体化IT服务提供商

开放世界里的一片森林,远看只是风景,放进内存却是一笔账。

假设每棵树都保存自己的网格、贴图、材质、碰撞数据、树种参数、坐标、旋转和生长状态。单棵树的数据并不夸张,乘上十万以后,内存很快被相同内容填满。

AI如果按照“创建一个 Tree 对象”的直觉生成代码,往往会把创建树所需的全部参数塞进实例。小场景完全正常,大地图一加载,几乎相同的松树就各自背着一份相同模型和材质进入内存。

玩家看到的是十万棵树,系统真正需要保存的,却不是十万份树的全部信息。

先分清每棵树哪里相同,哪里不同

一棵树的数据可以拆成两类。

第一类是内在状态:同一种松树共用的网格、贴图、材质、碰撞模板、燃烧规则和默认音效。这些数据体积大,却不会因为树种在地图上的位置不同而改变。

第二类是外在状态:坐标、旋转、缩放、当前生命、是否燃烧、季节染色和网络同步编号。这些数据属于具体实例,必须分别保存。

如果把两类状态混在 Tree 对象里,每棵树就会重复携带大资源。享元模式的思路,是把内在状态做成可共享对象,把外在状态留给轻量实例,并在使用时把两者组合起来。

record TreeType(
    Mesh Mesh,
    Texture Bark,
    Material Material,
    ColliderShape Collider,
    BurnRule BurnRule
);

struct TreeInstance
{
    public TreeType Type;
    public Vector3 Position;
    public Quaternion Rotation;
    public float Scale;
    public float Health;
}

class TreeTypeFactory
{
    private readonly Dictionary<TreeTypeId, TreeType> shared = new();

    public TreeType Get(TreeTypeId id)
        => shared.GetOrCreate(id, LoadTreeType);
}

十万棵树仍然存在,因为它们的位置和状态不同。但如果地图只有20种树型,昂贵资源只需要20份。


睿智创新RAIZ,一体化IT服务提供商

享元不是缓存的另一个名字

两者经常使用字典,看起来很像,但关注点不同。

缓存解决的是“已经计算或加载过的结果,下次不要再做一遍”。缓存条目可以因容量不足被淘汰,之后重新计算也没有问题。

享元解决的是“许多逻辑对象中存在相同状态,应该共享同一份表示”。它是对象模型的一部分。若共享的 TreeType 被意外修改,所有引用它的树都会受到影响。

这意味着享元对象通常应该不可变,或者至少把可变范围控制得非常清楚。不能为了让某棵树变成雪地颜色,就直接修改共享材质;否则全地图同类型树会一起变白。正确做法可能是把季节参数放在实例状态、材质实例或分组共享层中。

AI生成一个资源字典很容易,却不一定会主动检查共享对象是否仍然可变。让它使用享元时,必须同时要求列出共享数据的写入者和生命周期。

享元、对象池和实例化渲染分别省了什么

这三个概念常被放在同一场景里,却解决不同成本。

享元减少重复状态。它让十万棵树共享20份树种资源,主要降低内存占用和资源管理复杂度。

对象池减少反复创建与销毁。子弹离开屏幕后回到池中,下次复用同一个实例,主要降低分配、回收和初始化成本。对象池并不保证子弹共享模型数据,也不要求对象不可变。

实例化渲染减少提交给GPU的绘制开销。许多实例共用同一网格和材质,只提交不同变换参数,主要优化渲染管线。它通常依赖类似享元的数据组织,但属于更具体的图形技术。

缓存减少重复加载或计算。例如最近使用的贴图留在显存中,避免再次解码。

一个大型场景可能同时使用四者:树种资源采用享元,临时破坏碎片进入对象池,树木批次使用实例化渲染,远处区域资源由缓存和流式加载管理。

设计模式没有和引擎技术竞争。它提供的是识别重复状态的思考方式。


睿智创新RAIZ,一体化IT服务提供商

共享越多,错误的影响范围也越大

共享可以节省资源,也会放大写入错误。

假设所有“秋季枫树”共享一个 Material。某个任务需要把一棵目标树高亮,程序直接把材质颜色改成金色。结果整片森林瞬间发光。

这类问题不是享元模式失效,而是外在状态误写进了内在状态。判断一个字段能否共享,可以问:它是否会因实例身份、位置、玩家进度或时间而变化?任何答案为“会”的字段,都不该轻易放入全局共享对象。

另一个风险是共享键设计错误。如果 TreeTypeId 只包含树种,却忘了平台画质、季节材质或物理精度,那么本来不同的资源会被错误合并。反过来,如果键包含坐标和实例编号,共享率又会降为零。

享元工厂的键,本质上是在定义“哪些对象可以被认为拥有相同内在状态”。这需要业务和引擎知识,不是自动套一个 Dictionary 就能得到。

从十万棵树扩展到伤害数字

享元也适合许多短小却数量巨大的对象。

战斗中的伤害数字可能共享字体图集、材质和动画曲线,只保留数值、位置、颜色类别和出现时间。粒子共享纹理、着色器和发射规则,只保存粒子自己的位置、速度和寿命。地图上的路灯共享模型与灯光模板,只保存变换和开关状态。

但“看起来一样”不等于“一定要共享”。如果共享查找、间接访问和生命周期管理的成本,高于被节省的数据,直接创建小对象反而更简单。

例如只有十个对象,每个对象重复的只是两个整数,享元工厂、键和引用可能占用更多空间,也增加调试路径。模式的收益要建立在数量、重复度和资源体积之上。


睿智创新RAIZ,一体化IT服务提供商

内存没有爆,不等于帧率不会爆

把十万棵树变轻,只解决了其中一部分问题。

如果CPU每帧遍历十万个实例更新风摆,如果所有树都参与精确碰撞,如果每棵树仍然单独提交一次绘制,帧率依然会崩。享元优化的是数据重复,不会自动完成空间裁剪、LOD、批处理和并行更新。

大型项目需要把成本拆开看:

  • 内存成本来自资源和实例状态各占多少。
  • CPU成本来自更新、查找与状态切换。
  • GPU成本来自绘制次数、顶点数量和材质切换。
  • 加载成本来自磁盘、解压和上传显存。

AI可能看到“十万棵树”就同时给出对象池、享元和GPU实例化,却没有说明每一项针对哪条指标。开发者应该要求它先列出测量结果,再给出对应优化,而不是把所有性能术语堆在一起。

让AI设计共享对象时,给它一张状态清单

一个更可靠的提示方式是:

“地图包含十万棵树、20种树型。网格、贴图、基础材质、碰撞模板和燃烧规则按树型共享;位置、旋转、缩放、生命、燃烧进度和任务高亮属于实例。共享对象必须不可变;季节切换需要分组更新,单棵高亮不能修改共享材质。请估算重构前后的状态份数,并区分享元、对象池和实例化渲染的职责。”

这样AI不只是生成模式骨架,还能围绕可验证的状态边界工作。

AI很擅长统计重复字段、生成工厂、迁移构造调用和制作内存基准。人必须决定哪些状态真的等价,以及共享修改的爆炸半径是否可接受。

最后的判断

玩家看到十万棵树,是因为每棵树都有自己的位置与生命;内存没有保存十万份森林,是因为相同树种不必重复携带同一套模型、贴图和规则。

享元模式不是减少世界里的对象,而是减少对象重复携带的世界。

注:转载文章来源于网络,版权归原作者或企业所有,侵删!



我要咨询