在 Unity 开发中, 享元模式(Flyweight Pattern)用于解决大量详细对象导致的内存占用过高问题. 它通过”共享”相同或相似的对象(称为”享元”), 将对象的”内部状态”(固定不变的部分)与”外部状态”(随环境变化的部分)分离, 从而减少对象创建的数量, 节省内存.
 

举个具体场景: 森林场景中的树木

假设你要开发一个开放世界游戏, 森林里需要生成 10000 棵树. 如果每棵树都创建独立的 GameObject , 并包含独立的模型\材质\碰撞体等组件, 会导致:
  • 内存爆炸 (重复加载相同模型和材质)
  • 性能下降 (过多 Draw Call 和 内存占用)
 
但实际上, 这些树的模型\材质\基础属性(如高度范围\碰撞体大小) 是相同的(内部状态), 只有位置\旋转\具体高度是不同的(外部状态). 享元模式可以让所有树共享相同的 “模型资源”, 只存储各自不同的”位置信息”, 从而大幅减少内存消耗.
 

享元模式的核心角色

  1. 抽象享元(Flyweight): 定义享元对象的接口, 声明内部状态的获取方法和外部状态的操作方法.
  1. 具体享元(ConcreteFlyweight): 实现抽象享元, 存储内部状态(如模型\材质等共享资源).
  1. 享元工厂(Flyweight Factory): 负责创建和管理享元对象, 确保相同内部状态的享元只被创建一次(共享).
  1. 客户端(Client): 创建对象时, 通过工厂获取享元, 同时提供外部状态(如位置\旋转).
 

代码实现步骤

1.定义抽象享元(树的共享接口)
抽象享元声明内部状态(共享资源)和外部状态(动态数据)的操作方法:
 
2.实现具体享元(共享树资源)
具体享元存储内部状态(模型\材质等共享资源), 并实现 Spawn 方法(使用外部状态生成实例):
 
3.实现享元工厂(管理共享对象)
享元工厂负责缓存已创建的享元, 确保相同类型的树只创建一个享元对象(共享资源):
 
4.客户端使用(生成大量树)
客户端通过工厂获取享元, 传入外部状态(位置\旋转)生成树, 无需关心资源是否重复加载:
 

核心逻辑解析: 内部状态 vs 外部状态

  • 内部状态: 属于享元对象本身, 固定不变且可共享(如 PineTreeFlyweight 的模型\材质). 由享元工厂管理, 全局只加载一次.
  • 外部状态: 随实例变化, 不可共享(如每棵树的位置\旋转\随机缩放). 由客户端在调用时传入, 每个实例独立存储.
 

享元模式的核心优势

  1. 减少内存占用: 10000棵树只需要2个享元对象(Pine和Oak), 共享模型和材质, 避免 10000次重复加载资源.
  1. 提升性能: 减少资源加载次数和内存碎拼, 降低 Draw Call (相同材质可合并批次).
  1. 简化管理: 共享资源由工厂统一管理, 便于统一修改(如替换所有松树的本质, 只需改 PineTreeFlyweight ).
 

不适合用享元模式的场景

  • 对象数量少: 如果只需要生成10棵树, 共享带来的收益远小于代码复杂度.
  • 内部状态极少/外部状态极多: 若大部分状态都是随实例变化的(外部状态), 共享的意义不大.
 

在 Unity 中的其他应用场景

  • 粒子系统: 大量相同的粒子效果(如雨滴\雪花)共享粒子模板, 只动态设置位置和生命周期.
  • 敌人 AI: 同类型敌人(如 ”哥布林”)共享 AI逻辑(内部状态), 但各自的生命值\位置(外部状态)不同.
  • UI元素: 大量重复的 UI组件(如背包各自)共享预制体和样式(内部状态), 只动态设置位置和内容(外部状态).
 
总结: 享元模式就像”活字印刷术” ——— 相同的字模(内部状态)可以重复使用, 只需改变排列位置(外部状态), 无需为每个字都刻一个新模. 在Unity中, 它是优化大量相似对象性能的”内存杀手”.