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 中的重复元素

一句话总结就是:

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

相关推荐
丁小未1 天前
基于MVVM框架的XUUI 扩展的UI管理系统教程
unity·mvvm·ui框架·xuui·ui管理器
丁小未2 天前
基于MVVM框架的XUUI HelloWorld 新手教程
unity·性能优化·c#·游戏引擎
丁小未2 天前
基于MVVM框架的XUUI MoreComplex案例
unity·c#
EQ-雪梨蛋花汤2 天前
【Unity笔记】VR 一体机画面锯齿、模型边缘闪烁、接缝抖动排查:MSAA、Mipmap、Render Scale、Z-Fighting 全流程记录
笔记·unity·vr
松树戈2 天前
【Godot4精进之路】03~Godot编辑器常用界面介绍
编辑器·游戏引擎·godot
YigAin2 天前
Unity Spine 资源出现白边的解决办法
unity·游戏引擎·spine
郝学胜-神的一滴3 天前
中级OpenGL教程 013:渲染器类架构设计与逐帧渲染流程详解
开发语言·c++·unity·游戏引擎·图形渲染·opengl·unreal
_helen_5203 天前
unreal engine 虚拟现实 + airsim
游戏引擎·vr·虚幻
真鬼1235 天前
【Unity WebGL】内嵌网页与双向通信
unity·游戏引擎·webgl
zyh______5 天前
C#语法糖(按照实用性排序)
unity·c#