Unity享元模式

在 Unity 开发中,我们经常会遇到这样的场景:
场景中存在大量外观相同、行为相似,但位置、朝向各不相同的对象,例如森林里的树、草地上的草、子弹、NPC 装饰物等。

如果我们为每一个对象都完整地创建一套数据和资源,不仅会造成 内存浪费 ,还会增加 实例化与管理成本

而享元模式(Flyweight Pattern)正是为了解决这类问题而存在的。

享元模式的核心思想是:

将"可以共享的内部状态"抽离出来复用,把"不可共享的外部状态"交给使用者维护。

下面我将结合 Unity 的实际开发场景,用一个「森林生成系统」的例子,完整演示享元模式的设计与使用方式。

1.创建享元对象------承载外部状态

Tree 类并不是树的"类型",而是树的一个实例描述

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tree
{
    private TreeType treeType;  // 共享的内部状态(享元对象)
    private Vector3 position;   // 外部状态:位置
    private Quaternion rotation; // 外部状态:旋转
    private float scale;        // 外部状态:缩放
    private GameObject treeObject; // 实际的游戏对象

    public GameObject TreeObject => treeObject;

    public Tree(TreeType type, Vector3 pos, Quaternion rot, float scale)
    {
        this.treeType = type;
        this.position = pos;
        this.rotation = rot;
        this.scale = scale;
    }

    // 实例化树到场景中
    public GameObject Instantiate(Transform parent = null)
    {
        if (treeType == null) return null;

        treeObject = treeType.InstantiateTree(position, rotation, scale, parent);
        return treeObject;
    }

    // 移除树
    public void Destroy()
    {
        if (treeObject != null)
        {
            GameObject.Destroy(treeObject);
        }
    }
}

2.创建树的类型------真正的享元对象

TreeType 才是享元模式中的核心共享对象

它保存的是:树的名称,颜色,纹理信息,预制体引用

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TreeType
{
    public string Name { get; }      // 树种名称
    public Color Color { get; }      // 树的主要颜色
    public string Texture { get; }   // 纹理名称
    public GameObject Prefab { get; } // 树的预制体

    // 构造函数
    public TreeType(string name, Color color, string texture, GameObject prefab)
    {
        Name = name;
        Color = color;
        Texture = texture;
        Prefab = prefab;

        Debug.Log($"创建树种: {name} (颜色: {color}, 纹理: {texture})");
    }

    // 实例化树的方法
    public GameObject InstantiateTree(Vector3 position, Quaternion rotation, float scale, Transform parent)
    {
        GameObject tree = GameObject.Instantiate(Prefab, position, rotation, parent);
        tree.transform.localScale = Vector3.one * scale;

        // 可以根据颜色和纹理修改材质
        MeshRenderer renderer = tree.GetComponent<MeshRenderer>();
        if (renderer != null)
        {
            // 这里可以设置材质颜色或纹理
            Material newMaterial = new Material(renderer.sharedMaterial);
            newMaterial.color = Color;
            renderer.material = newMaterial;
        }

        return tree;
    }
}

3.创建享元工厂------统一管理共享对象

TreeFactory 的职责是:

  • 管理所有已经创建的 TreeType

  • 保证同一类型只会被创建一次

  • 对外提供统一获取接口

cs 复制代码
using System.Collections.Generic;
using UnityEngine;
public class TreeFactory
{
    private static Dictionary<string, TreeType> treeTypes = new Dictionary<string, TreeType>();

    // 获取或创建树种 - 新增prefab参数
    public static TreeType GetTreeType(string name, Color color, string texture, GameObject prefab = null)
    {
        string key = $"{name}_{color}_{texture}";

        if (!treeTypes.ContainsKey(key))
        {
            // 如果不存在,创建新的享元对象
            if (prefab == null)
            {
                Debug.LogError($"首次创建树种 '{name}' 时必须提供预制体!");
                return null;
            }

            treeTypes[key] = new TreeType(name, color, texture, prefab);
            Debug.Log($"✓ 创建新的树种类型: {name}");
        }
        else
        {
            Debug.Log($"✓ 重用现有的树种类型: {name}");
        }

        return treeTypes[key];
    }

    // 获取已创建的树种数量
    public static int GetTreeTypeCount()
    {
        return treeTypes.Count;
    }

    // 获取特定类型的树种
    public static TreeType GetExistingTreeType(string name, Color color, string texture)
    {
        string key = $"{name}_{color}_{texture}";
        if (treeTypes.ContainsKey(key))
        {
            return treeTypes[key];
        }
        return null;
    }
    public static void Clear()
    {
        treeTypes.Clear();
        Debug.Log("已清除所有树种类型");
    }
}

4.创建一片森林

  • 创建 1000 棵树

  • 实际只创建 2 个 TreeType

  • 每棵树位置、旋转、缩放都不同

cs 复制代码
using System.Collections.Generic;
using UnityEngine;

public class Forest : MonoBehaviour
{
    public GameObject treePrefab; // 树的预制体
    public int treeCount = 1000;  // 要创建的树数量
    public Vector2 areaSize = new Vector2(200, 200); // 森林区域大小
    public Transform forestParent; // 所有树的父对象(可选)

    private List<Tree> trees = new List<Tree>();

    void Start()
    {
        CreateForest();
    }

    void CreateForest()
    {
        Debug.Log("=== 开始创建森林 ===");

        // 确保有预制体
        if (treePrefab == null)
        {
            Debug.LogError("请先指定树的预制体!");
            return;
        }

        // 创建两种树型配置
        TreeType pineTreeType = null;
        TreeType oakTreeType = null;

        // 创建1000棵树,但只有2种树种
        for (int i = 0; i < treeCount; i++)
        {
            TreeType treeType;

            // 随机选择树种(只有2种类型)
            if (UnityEngine.Random.value > 0.5f)
            {
                // 松树 - 共享的享元对象
                if (pineTreeType == null)
                {
                    pineTreeType = TreeFactory.GetTreeType(
                        "松树",
                        Color.green,
                        "pine_texture",
                        treePrefab // 传入预制体
                    );
                }
                treeType = pineTreeType;
            }
            else
            {
                // 橡树 - 共享的享元对象
                if (oakTreeType == null)
                {
                    oakTreeType = TreeFactory.GetTreeType(
                        "橡树",
                        new Color(0.2f, 0.8f, 0.3f),
                        "oak_texture",
                        treePrefab // 传入预制体
                    );
                }
                treeType = oakTreeType;
            }

            // 每棵树有不同的位置、旋转和缩放(外部状态)
            Vector3 randomPosition = new Vector3(
                UnityEngine.Random.Range(-areaSize.x / 2, areaSize.x / 2),
                0,
                UnityEngine.Random.Range(-areaSize.y / 2, areaSize.y / 2)
            );

            Quaternion randomRotation = Quaternion.Euler(
                0,
                UnityEngine.Random.Range(0, 360),
                0
            );

            float randomScale = UnityEngine.Random.Range(0.8f, 1.5f);

            // 创建树对象(此时还没有实例化游戏对象)
            Tree tree = new Tree(treeType, randomPosition, randomRotation, randomScale);
            trees.Add(tree);
        }

        Debug.Log($"=== 创建完成 ===");
        Debug.Log($"总共创建了 {trees.Count} 棵树");
        Debug.Log($"但只创建了 {TreeFactory.GetTreeTypeCount()} 种树种类型");
        Debug.Log($"节省了 {trees.Count - TreeFactory.GetTreeTypeCount()} 个重复对象的创建!");

        // 实际实例化所有树到场景中
        InstantiateAllTrees();
    }

    void InstantiateAllTrees()
    {
        Debug.Log("开始实例化树到场景中...");

        // 创建森林父对象(如果指定)
        Transform parent = forestParent;
        if (parent == null)
        {
            GameObject forestContainer = new GameObject("ForestContainer");
            parent = forestContainer.transform;
        }

        foreach (Tree tree in trees)
        {
            tree.Instantiate(parent);
        }

        Debug.Log($"已实例化 {trees.Count} 棵树到场景中");
    }

    // 清空森林
    void ClearForest()
    {
        foreach (Tree tree in trees)
        {
            tree.Destroy();
        }
        trees.Clear();
        TreeFactory.Clear(); // 如果需要添加清除方法
    }

    void OnDrawGizmos()
    {
        // 绘制森林区域
        Gizmos.color = new Color(0, 1, 0, 0.1f);
        Gizmos.DrawCube(transform.position + Vector3.up * 0.1f,
                       new Vector3(areaSize.x, 0.1f, areaSize.y));
    }
}

5.运行效果对比

使用享元

可以看到帧率最高能达到120帧。

不使用享元

可以看到帧率直接下降了一半多,提升还是很显著的。

总结

在 Unity 中,享元模式非常适合应用在:

  • 大规模场景物体(树、草、岩石)

  • 特效实例(同类型粒子)

  • 子弹、道具、装饰物

  • UI 中的重复元素

一句话总结就是:

当你发现"很多对象看起来一样,只是位置或状态不同"时,享元模式几乎一定值得考虑。

相关推荐
lrh30254 小时前
Custome SRP 17 - FXAA
3d·unity·srp·render pipeline·fxaa·抗锯齿
XR技术研习社6 小时前
第二种升级Quest系统的方案
unity·xr·vr
三和尚7 小时前
AI开发之Cursor的下载安装以及Unity-MCP下载安装到你的个人Unity项目中(一)
unity·ai·游戏引擎·cursor·unity-mcp·unity自动化
__water8 小时前
RHQ《Unity2D图片切割方式》
unity·2d·精灵图切割
DaLiangChen8 小时前
Unity 导览相机实现:键鼠控制自由漫游(WASD 移动 + 右键旋转)
数码相机·unity·游戏引擎
沉默金鱼1 天前
Unity实用技能-UI进度条
ui·unity·游戏引擎
老朱佩琪!1 天前
Unity离线开发经验分享
unity·游戏引擎
Sator11 天前
unity的GPUInstance和GPU动画
unity·游戏引擎
JavaBoy_XJ2 天前
结构型-享元模式
享元模式