将预制体动态加载到场景当中,包括其依赖项(贴图,网格,材质等)

Resource.Load()

这是Unity内置的一种资源管理方式,现在已经不推荐,除非是项目测试Demo,或者小型项目中可以用用

内存情况:

所有 Resources 文件夹中的资源在构建时会被合并到一个序列化文件中(resources.assets,在打包后的Date目录下)。首次调用 Resources.Load 时,Unity 会解析这个序列化文件,加载所需的资源到内存。后续加载同一资源时,如果资源已在内存中,则直接引用,不会重复加载

依赖项加载

当调用Resources.Load加载预制体时,Unity不仅会加载该预制体本身,还会自动加载其直接引用的所有依赖资源(如材质、贴图、模型等)

验证

下面是使用MemoryProfiler插件验证的过程:

Resource中加载一个预制体FireBall,这个物体上有FireFist精灵,和粒子系统间接引用的FireCore材质

public class ResourceLoader : MonoBehaviour
{
    GameObject prefab;
    void Start()
    {
        prefab = Resources.Load("Skill/FireBall") as GameObject;        
    }
   
}

如图,预制体直接依赖项被加载至内存,而间接依赖项FireCore并没有被加载(间接依赖项是在GameObject.Instantiate实例化之后才会被加载

内存释放

只能手动清理或者场景切换的时候清理

释放单个资源:Resources.UnloadAsset()

释放某个未被引用的资源(例如未实例化的预制体、材质、纹理等)。

  • 注意:

    • 如果资源已被实例化(例如通过 Instantiate 生成 GameObject),则必须销毁所有实例后,才能成功释放。

    • 如果资源被其他对象引用(例如材质被某个 GameObject 使用),则无法释放。

GameObject prefab = Resources.Load<GameObject>("Prefabs/Enemy");
Resources.UnloadAsset(prefab); // 仅当prefab未被实例化或引用时生效

释放所有未使用资源:Resources.UnloadUnusedAssets()

释放所有未被引用的资源(包括未被实例化的预制体、未被引用的材质/纹理等)。

  • 使用场景

    • 切换场景后,自动调用释放旧场景的资源

    • 手动清理

// 销毁所有实例
Destroy(enemyInstance);
// 触发垃圾回收(可选)
System.GC.Collect();
// 释放未使用资源
Resources.UnloadUnusedAssets();

静态路径资源

指的是直接拖拽到Inspector面板上Public字段的资源

内存情况

这种资源的内存情况就比较简单了,基本默认随场景一起加载,包括其依赖项(不清楚是不是直接依赖,有兴趣的可以自己去试试)

非激活状态的场景物体引用

如果这个物体在场景中处于非激活状态,那么资源还会加载到内存吗?答案是会的

验证:

简单写了个测试代码如下,预制体RockBall依赖精灵RockBall

public class PrefabLoader : MonoBehaviour
{
    public GameObject prefab;
}

结论

即使物体在场景中处于非激活状态,资源还是会加载到内存。

所以各位在使用对象池的时候尽量让池中的物体使用同样的依赖项,不然如果是不一样的贴图或者材质,内存占用会比较大。

一个idea:使用SO文件储存静态引用到Resources目录下

由上可知,Resources目录不会加载间接引用,博主灵机一动:诶,如果我使用SO容器记录每一个预制体的引用,然后使用Resources.Load来加载这个SO容器,那不就既可以灵活加载资源,又避免了Resources加载内存占用的问题了?

话不多嗦,开始实践:

实现过程

[CreateAssetMenu(fileName = "AssetsManager", menuName = "Asset/AssetsManager")]
public class AssetsManager : ScriptableObject
{
    private static AssetsManager _assetsManager;
    public static AssetsManager instance
    {
        get
        {
            if (!_assetsManager)
            {
                if (!(_assetsManager = Resources.Load("AssetsManager") as AssetsManager))
                    Debug.LogError("no asset Manager in path");
            }
            return _assetsManager;
        }
    }
    [Header("预制体1")]
    public GameObject prefab1;
    [Header("预制体2")]
    public GameObject prefab2;
}

然后在Resources目录下建立这个SO文件,调用处使用AssetsManager.instance

public class SO_PrefabLoader : MonoBehaviour
{
    GameObject prefab1;
    GameObject prefab2;
    void Start()
    {
        prefab1 = AssetsManager.instance.prefab1;
    }
}

SO文件中拖拽的prefab1是自定义文件夹Prefabs下的WaterBall,其有间接依赖项材质Water

prefab2是是自定义文件夹Prefabs下的Thunder,有其同名的多个直接依赖

如果能够灵活加载内存,那么内存里面只会存在prefab1WaterBall,否则两个预制体都会存在

结果

可以看到WaterBall的直接依赖都在,但是没有间接依赖,符合Resources.Load的特性

不幸的是,预制体Thunder也在,而且其直接依赖也在内存中(▼ヘ▼)

结论

博主的想法失败了,可以得出结论:

如果预制体的引用在 SO 中是通过 public GameObject prefab 直接拖拽赋值的(而非动态路径加载),那么Unity会隐式调用Resources.Load进行加载,而且SO文件中的所有引用的预制体都会被加载

使用AB包

AssetsBundle,作为一个方式灵活,支持热更的资源打包方式,通过将资源分布在不同的AB包中可以减少运行时的内存压力,可以动态地加载和卸载AB包,继而有选择地加载内容。

可以去看看Unity AssetsBundle 详解_unity assetbundle-CSDN博客

内存情况:

打包时

当将一个预制体打包到AB包中时,Unity会:

  1. 序列化预制体本身:将预制体的GameObject结构、组件、脚本参数等数据存入AB包。

  2. 自动打包所有依赖项:预制体引用的资源(如材质、纹理、模型、动画等)都会被隐式打包到同一个AB包中,除非这些依赖项已被明确分配到其他AB包。(所以为了避免冗余资源打包,需要进行资源分包管理

加载时

  • LoadFromFile(包括异步加载,

    • 直接从磁盘读取,内存占用较小(仅存储文件头信息)

    • 建议异步加载避免主线程堵塞

    • 适用于不压缩或LZ4压缩格式的AB包

    • 网络加载与之类似

  • LoadFromMemory

    • 适用于需要加密或动态生成的AB包

    • 将整个AB包数据加载到内存中,内存占用高(文件大小 × 1.3~1.5倍)

验证

将多个预制体打包在同一个ab格式的包skill.ab下,其中预制体WaterBall包含间接依赖项材质WaterCore

public class AB_Loader : MonoBehaviour
{
    GameObject prefab;
    private void Start()
    {
        AssetBundle assetBundle = AssetBundle.LoadFromFile("Assets/AssetBundles/Windows/skill.ab");
        prefab = assetBundle.LoadAsset<GameObject>("WaterBall");
    }
}

从上图可以看到,内存当中的skill.ab占用仅8.5kb,仅存储了了头信息

可以看到,间接依赖项WaterCore也被加载到内存中

内存释放

(1) AssetBundle.Unload(bool unloadAllLoadedObjects)

  • Unload(true):卸载AB包并销毁所有从中加载的资源 风险:若其他对象引用这些资源,会导致“Missing”错误(留下神秘粉色材质)

  • Unload(false):仅卸载AB包文件,保留已加载的资源 风险:资源残留,需手动调用Resources.UnloadUnusedAssets()

(2) 建议实践方法

  • 加载资源后立即调用AssetBundle.Unload(false)释放AB包文件内存

  • 确保无引用后,通过Resources.UnloadUnusedAssets()释放资源

  • 使用引用计数或依赖管理框架(如Addressables)管理复杂依赖

使用Addressable管理

鉴于Unity提供的ab包API实在有些简陋,就连生成ab包都要手动写Editor代码,ab包也需要手动卸载,于是就有了Addressable插件

【游戏开发探究】Unity Addressables资源管理方式用起来太爽了,资源打包、加载、热更变得如此轻松(Addressable Asset System | 简称AA)_unity aa-CS