资源管理
Unity 的资源管理是游戏开发中至关重要的环节,直接影响游戏的性能、内存占用和用户体验。本章将深入讲解 Prefab、AssetBundle 和 Addressables 三种主要的资源管理方式,帮助你选择最适合项目需求的方案。
Prefab 预制体
Prefab(预制体)是 Unity 中最基础的资源复用机制,它允许你创建、配置和存储游戏对象及其所有组件、属性值和子游戏对象,作为可重复使用的资源。
Prefab 概念
Prefab 本质上是一个蓝图或模板,存储在 Project 窗口中。当你在场景中创建一个 Prefab 实例时,Unity 会创建一个指向 Prefab 源的引用链接。这意味着:
- 修改 Prefab 源文件,所有场景中的实例会自动更新
- 修改实例属性,可以覆盖 Prefab 的默认值而不影响其他实例
- 嵌套 Prefab 支持在 Prefab 中包含其他 Prefab,实现更复杂的复用
创建 Prefab
方式1:从场景创建
- 在 Hierarchy 中配置好游戏对象
- 将对象拖拽到 Project 窗口的文件夹中
- 原对象变为蓝色,表示已成为 Prefab 实例
方式2:创建空 Prefab
- 在 Project 窗口右键 > Create > Prefab
- 双击打开 Prefab 编辑模式
- 添加组件和子对象
Prefab 实例与变体
using UnityEngine;
public class PrefabExample : MonoBehaviour
{
public GameObject enemyPrefab;
void Start()
{
// 实例化 Prefab
GameObject enemy1 = Instantiate(enemyPrefab, transform.position, Quaternion.identity);
// 设置实例名称
enemy1.name = "Enemy_01";
// 修改实例属性(不影响 Prefab 源)
enemy1.transform.localScale = new Vector3(2f, 2f, 2f);
// 获取实例上的组件
var health = enemy1.GetComponent<HealthSystem>();
health.maxHealth = 200; // 覆盖默认值
}
}
Prefab 变体
Prefab 变体允许你基于现有 Prefab 创建新版本,继承父 Prefab 的所有属性:
Enemy (基础 Prefab)
├── Enemy_Fast (变体:速度更快)
├── Enemy_Tank (变体:血量更高)
└── Enemy_Flying (变体:可以飞行)
创建变体:
- 在 Project 窗口选择源 Prefab
- 右键 > Create > Prefab Variant
- 修改变体的属性
运行时实例化
public class Spawner : MonoBehaviour
{
public GameObject prefab;
public int spawnCount = 10;
public float spawnRadius = 5f;
public float spawnInterval = 1f;
void Start()
{
StartCoroutine(SpawnRoutine());
}
IEnumerator SpawnRoutine()
{
for (int i = 0; i < spawnCount; i++)
{
SpawnEnemy();
yield return new WaitForSeconds(spawnInterval);
}
}
void SpawnEnemy()
{
// 在随机位置生成
Vector2 randomCircle = Random.insideUnitCircle * spawnRadius;
Vector3 spawnPosition = transform.position + new Vector3(randomCircle.x, 0, randomCircle.y);
// 实例化
GameObject instance = Instantiate(prefab, spawnPosition, Quaternion.identity);
// 可选:设置父对象
instance.transform.SetParent(transform);
}
}
Prefab 实用技巧
1. Prefab 模式
双击 Prefab 进入 Prefab 模式,可以独立编辑 Prefab 而不影响场景:
- 编辑模式:修改 Prefab 源文件
- 覆盖管理:在 Inspector 中查看和恢复实例的属性覆盖
- 嵌套编辑:可以编辑嵌套的子 Prefab
2. Prefab 覆盖
当修改实例属性时,会创建覆盖(Override):
// 检查实例是否有覆盖
GameObject prefabInstance; // 假设已赋值
// 查看修改的属性
var modifications = PrefabUtility.GetPropertyModifications(prefabInstance);
foreach (var mod in modifications)
{
Debug.Log($"属性: {mod.propertyPath}, 值: {mod.value}");
}
// 重置实例到 Prefab 状态
PrefabUtility.RevertObjectOverride(prefabInstance, InteractionMode.UserAction);
// 应用修改到 Prefab
PrefabUtility.ApplyObjectOverride(prefabInstance, prefabPath, InteractionMode.UserAction);
3. 运行时动态加载 Prefab
using UnityEngine;
public class ResourceLoader : MonoBehaviour
{
// 方式1:使用 Resources 文件夹(不推荐大型项目)
public void LoadFromResources()
{
GameObject prefab = Resources.Load<GameObject>("Prefabs/Enemy");
Instantiate(prefab);
}
// 方式2:使用 AssetDatabase(仅编辑器)
#if UNITY_EDITOR
public void LoadFromAssetDatabase()
{
GameObject prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(
"Assets/Prefabs/Enemy.prefab"
);
Instantiate(prefab);
}
#endif
}
AssetBundle
AssetBundle 是 Unity 的资源打包和分发系统,允许将资源打包成独立的文件,在运行时动态加载。相比 Resources 文件夹,AssetBundle 提供了更好的内存管理和更灵活的资源分发方式。
为什么使用 AssetBundle?
| 特性 | Resources | AssetBundle |
|---|---|---|
| 内存管理 | 启动时加载索引,无法卸载 | 可以精确控制加载和卸载 |
| 资源更新 | 必须更新整个应用 | 可独立更新资源包 |
| 初始包大小 | 增加应用体积 | 可作为 DLC 下载 |
| 平台适配 | 所有平台共用 | 可针对不同平台打包 |
创建 AssetBundle
1. 设置 AssetBundle 名称
在 Inspector 底部设置资源的 AssetBundle 名称和变体:
AssetBundle 名称格式: lower_case_with_underscores
例如: characters/player
变体: hd, sd (可选)
2. 构建 AssetBundle
using UnityEngine;
using UnityEditor;
using System.IO;
public class AssetBundleBuilder
{
[MenuItem("Build/Build AssetBundles")]
static void BuildAllAssetBundles()
{
// 输出路径
string assetBundleDirectory = "Assets/AssetBundles";
// 创建目录
if (!Directory.Exists(assetBundleDirectory))
{
Directory.CreateDirectory(assetBundleDirectory);
}
// 构建 AssetBundle
BuildPipeline.BuildAssetBundles(
assetBundleDirectory,
BuildAssetBundleOptions.None,
BuildTarget.StandaloneWindows64
);
Debug.Log("AssetBundles 构建完成");
}
}
3. 构建选项
// 常用构建选项
BuildAssetBundleOptions options =
BuildAssetBundleOptions.None | // 默认选项
BuildAssetBundleOptions.ChunkBasedCompression | // LZ4 压缩
BuildAssetBundleOptions.DeterministicAssetBundle; // 确定性构建
// 不同压缩方式
// None: 无压缩,最大,加载最快
// ChunkBasedCompression: LZ4 压缩,平衡大小和速度(推荐)
// DisableWriteTypeTree: 不写入类型树,减小文件大小但影响兼容性
加载 AssetBundle
同步加载
using UnityEngine;
using System.IO;
public class AssetBundleLoader : MonoBehaviour
{
public string bundleName = "characters";
public string assetName = "Player";
void Start()
{
LoadAssetSync();
}
void LoadAssetSync()
{
// AssetBundle 路径
string path = Path.Combine(Application.streamingAssetsPath, bundleName);
// 加载 AssetBundle
AssetBundle bundle = AssetBundle.LoadFromFile(path);
if (bundle == null)
{
Debug.LogError($"无法加载 AssetBundle: {bundleName}");
return;
}
// 加载资源
GameObject prefab = bundle.LoadAsset<GameObject>(assetName);
if (prefab != null)
{
Instantiate(prefab);
}
// 卸载 AssetBundle(但不卸载已加载的资源)
bundle.Unload(false);
}
}
异步加载
public class AsyncAssetBundleLoader : MonoBehaviour
{
public string bundleName;
public string assetName;
void Start()
{
StartCoroutine(LoadAssetAsync());
}
IEnumerator LoadAssetAsync()
{
string path = Path.Combine(Application.streamingAssetsPath, bundleName);
// 异步加载 AssetBundle
AssetBundleCreateRequest bundleRequest = AssetBundle.LoadFromFileAsync(path);
yield return bundleRequest;
AssetBundle bundle = bundleRequest.assetBundle;
if (bundle == null)
{
Debug.LogError("加载 AssetBundle 失败");
yield break;
}
// 异步加载资源
AssetBundleRequest assetRequest = bundle.LoadAssetAsync<GameObject>(assetName);
yield return assetRequest;
if (assetRequest.asset != null)
{
Instantiate(assetRequest.asset);
}
// 卸载
bundle.Unload(false);
}
}
从服务器加载
using UnityEngine.Networking;
public class RemoteAssetBundleLoader : MonoBehaviour
{
public string url = "https://your-server.com/assetbundles/characters";
public string assetName;
void Start()
{
StartCoroutine(LoadFromServer());
}
IEnumerator LoadFromServer()
{
// 从 URL 加载 AssetBundle
using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"下载失败: {request.error}");
yield break;
}
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
if (bundle != null)
{
GameObject prefab = bundle.LoadAsset<GameObject>(assetName);
Instantiate(prefab);
bundle.Unload(false);
}
}
}
}
依赖管理
AssetBundle 之间的依赖关系需要手动管理:
using UnityEngine;
public class DependencyLoader : MonoBehaviour
{
void LoadWithDependencies()
{
// 加载依赖清单
AssetBundle manifestBundle = AssetBundle.LoadFromFile(
Path.Combine(Application.streamingAssetsPath, "StreamingAssets")
);
AssetBundleManifest manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
// 获取依赖
string[] dependencies = manifest.GetAllDependencies("characters");
// 先加载依赖
foreach (string dependency in dependencies)
{
string path = Path.Combine(Application.streamingAssetsPath, dependency);
AssetBundle.LoadFromFile(path);
}
// 然后加载主 AssetBundle
AssetBundle mainBundle = AssetBundle.LoadFromFile(
Path.Combine(Application.streamingAssetsPath, "characters")
);
// 加载资源
GameObject prefab = mainBundle.LoadAsset<GameObject>("Player");
Instantiate(prefab);
}
}
AssetBundle 内存管理
public class AssetBundleMemoryManager : MonoBehaviour
{
private Dictionary<string, AssetBundle> loadedBundles = new Dictionary<string, AssetBundle>();
private Dictionary<string, GameObject> loadedAssets = new Dictionary<string, GameObject>();
// 加载并缓存
public IEnumerator LoadAndCache(string bundleName, string assetName)
{
if (loadedBundles.ContainsKey(bundleName))
{
yield break; // 已加载
}
string path = Path.Combine(Application.streamingAssetsPath, bundleName);
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);
yield return request;
if (request.assetBundle != null)
{
loadedBundles[bundleName] = request.assetBundle;
AssetBundleRequest assetRequest = request.assetBundle.LoadAssetAsync<GameObject>(assetName);
yield return assetRequest;
if (assetRequest.asset != null)
{
loadedAssets[assetName] = assetRequest.asset as GameObject;
}
}
}
// 卸载指定 AssetBundle
public void UnloadBundle(string bundleName, bool unloadAllLoadedObjects = false)
{
if (loadedBundles.TryGetValue(bundleName, out AssetBundle bundle))
{
bundle.Unload(unloadAllLoadedObjects);
loadedBundles.Remove(bundleName);
}
}
// 卸载所有
public void UnloadAllBundles()
{
foreach (var bundle in loadedBundles.Values)
{
bundle.Unload(true);
}
loadedBundles.Clear();
loadedAssets.Clear();
// 强制垃圾回收
Resources.UnloadUnusedAssets();
System.GC.Collect();
}
}
Addressables 资源系统
Addressables 是 Unity 推荐的现代资源管理系统,它在 AssetBundle 之上提供了更高级的抽象,简化了资源加载、内存管理和依赖处理。
Addressables 的优势
- 简化加载:通过地址(Address)加载资源,无需关心物理路径
- 自动依赖管理:自动加载所有依赖资源
- 灵活的托管位置:资源可以放在本地、远程服务器或 CDN
- 内存管理:自动引用计数,智能卸载
- 编辑器集成:可视化工具管理资源分组
安装 Addressables
- 打开 Window > Package Manager
- 选择 Unity Registry
- 搜索 Addressables
- 点击 Install
标记资源为 Addressable
方式1:Inspector
- 选择资源
- 在 Inspector 中勾选 Addressable
- 设置地址名称(默认为资源路径)
方式2:Window > Asset Management > Addressables > Groups
- 打开 Addressables Groups 窗口
- 拖拽资源到窗口中
- 设置标签和分组
基本加载方式
异步加载资源
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections;
public class AddressablesLoader : MonoBehaviour
{
// 地址可以是字符串或 AssetReference
public string assetAddress = "Assets/Prefabs/Player.prefab";
private GameObject loadedAsset;
void Start()
{
StartCoroutine(LoadAssetCoroutine());
}
IEnumerator LoadAssetCoroutine()
{
// 开始加载
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(assetAddress);
// 等待加载完成
yield return handle;
// 检查结果
if (handle.Status == AsyncOperationStatus.Succeeded)
{
loadedAsset = handle.Result;
Instantiate(loadedAsset);
}
else
{
Debug.LogError($"加载失败: {handle.OperationException}");
}
}
void OnDestroy()
{
// 释放资源
if (loadedAsset != null)
{
Addressables.Release(loadedAsset);
}
}
}
使用回调方式
public class AddressablesCallback : MonoBehaviour
{
public string assetAddress;
private AsyncOperationHandle<GameObject> handle;
void Start()
{
handle = Addressables.LoadAssetAsync<GameObject>(assetAddress);
handle.Completed += OnAssetLoaded;
}
void OnAssetLoaded(AsyncOperationHandle<GameObject> handle)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
else
{
Debug.LogError("加载失败");
}
}
void OnDestroy()
{
if (handle.IsValid())
{
Addressables.Release(handle);
}
}
}
使用 async/await
using System.Threading.Tasks;
public class AddressablesAsync : MonoBehaviour
{
public string assetAddress;
async void Start()
{
await LoadAssetAsync();
}
async Task LoadAssetAsync()
{
try
{
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(assetAddress);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
}
catch (System.Exception e)
{
Debug.LogError($"加载异常: {e.Message}");
}
}
}
AssetReference 使用
AssetReference 提供了一种类型安全的方式来引用 Addressable 资源:
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class AssetReferenceExample : MonoBehaviour
{
// 在 Inspector 中直接拖拽赋值
public AssetReferenceGameObject playerPrefab;
public AssetReferenceTexture2D iconTexture;
public AssetReference spriteAsset;
private GameObject playerInstance;
void Start()
{
LoadPlayer();
}
async void LoadPlayer()
{
// 检查引用是否有效
if (!playerPrefab.RuntimeKeyIsValid())
{
Debug.LogError("AssetReference 无效");
return;
}
// 加载并实例化
AsyncOperationHandle<GameObject> handle = playerPrefab.InstantiateAsync(transform);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
playerInstance = handle.Result;
Debug.Log("玩家实例化成功");
}
}
// 加载 Texture
async void LoadIcon()
{
if (iconTexture.RuntimeKeyIsValid())
{
AsyncOperationHandle<Texture2D> handle = iconTexture.LoadAssetAsync();
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
// 使用纹理
GetComponent<Renderer>().material.mainTexture = handle.Result;
}
}
}
void OnDestroy()
{
// 释放实例
if (playerInstance != null)
{
Addressables.ReleaseInstance(playerInstance);
}
// 释放引用
if (iconTexture != null && iconTexture.Asset != null)
{
iconTexture.ReleaseAsset();
}
}
}
加载多个资源
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections.Generic;
public class LoadMultipleAssets : MonoBehaviour
{
public string label = "Enemies";
private List<GameObject> loadedAssets = new List<GameObject>();
private List<AsyncOperationHandle> handles = new List<AsyncOperationHandle>();
void Start()
{
StartCoroutine(LoadAssetsByLabel());
}
IEnumerator LoadAssetsByLabel()
{
// 按标签加载多个资源
AsyncOperationHandle<IList<GameObject>> handle =
Addressables.LoadAssetsAsync<GameObject>(label, OnAssetLoaded);
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log($"加载了 {handle.Result.Count} 个资源");
}
handles.Add(handle);
}
// 每个资源加载完成时调用
void OnAssetLoaded(GameObject asset)
{
GameObject instance = Instantiate(asset);
loadedAssets.Add(instance);
}
void OnDestroy()
{
// 释放实例
foreach (var instance in loadedAssets)
{
if (instance != null)
{
Destroy(instance);
}
}
// 释放句柄
foreach (var handle in handles)
{
Addressables.Release(handle);
}
}
}
场景加载
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
public class AddressableSceneLoader : MonoBehaviour
{
public AssetReference sceneReference;
public string sceneAddress = "Assets/Scenes/GameScene.unity";
private AsyncOperationHandle<SceneInstance> sceneHandle;
// 加载场景(单模式)
public async void LoadSceneSingle()
{
sceneHandle = Addressables.LoadSceneAsync(sceneAddress, LoadSceneMode.Single);
await sceneHandle.Task;
if (sceneHandle.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log("场景加载成功");
}
}
// 加载场景(附加模式)
public async void LoadSceneAdditive()
{
sceneHandle = Addressables.LoadSceneAsync(sceneAddress, LoadSceneMode.Additive);
// 监控进度
while (!sceneHandle.IsDone)
{
Debug.Log($"加载进度: {sceneHandle.PercentComplete * 100}%");
await System.Threading.Tasks.Task.Delay(100);
}
if (sceneHandle.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log("附加场景加载成功");
}
}
// 卸载场景
public async void UnloadScene()
{
if (sceneHandle.IsValid())
{
AsyncOperationHandle<SceneInstance> unloadHandle =
Addressables.UnloadSceneAsync(sceneHandle);
await unloadHandle.Task;
Debug.Log("场景已卸载");
}
}
}
下载和远程资源
Addressables 支持从远程服务器下载资源:
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class RemoteAssetLoader : MonoBehaviour
{
public string remoteAssetAddress;
private AsyncOperationHandle handle;
void Start()
{
DownloadAndLoadAsset();
}
async void DownloadAndLoadAsset()
{
// 检查下载大小
var sizeHandle = Addressables.GetDownloadSizeAsync(remoteAssetAddress);
await sizeHandle.Task;
if (sizeHandle.Status == AsyncOperationStatus.Succeeded)
{
long downloadSize = sizeHandle.Result;
Debug.Log($"需要下载: {FormatBytes(downloadSize)}");
if (downloadSize > 0)
{
// 开始下载
handle = Addressables.LoadAssetAsync<GameObject>(remoteAssetAddress);
// 监控下载进度
while (!handle.IsDone)
{
float progress = handle.GetDownloadStatus().Percent;
Debug.Log($"下载进度: {progress * 100}%");
await System.Threading.Tasks.Task.Delay(100);
}
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
}
}
Addressables.Release(sizeHandle);
}
string FormatBytes(long bytes)
{
string[] suffixes = { "B", "KB", "MB", "GB" };
int i = 0;
float size = bytes;
while (size >= 1024 && i < suffixes.Length - 1)
{
size /= 1024;
i++;
}
return $"{size:F2} {suffixes[i]}";
}
// 清除缓存
public void ClearCache()
{
Addressables.ClearDependencyCacheAsync(remoteAssetAddress);
}
}
Addressables Groups 配置
在 Addressables Groups 窗口中可以配置资源的打包方式:
Group 设置
Group 配置项:
├── Build Path - 构建输出路径(本地/远程)
├── Load Path - 运行时加载路径(本地/远程)
├── Bundle Mode - 打包模式
│ ├── Pack Separately - 每个资源单独打包
│ ├── Pack Together - 打包到一起
│ └── Pack Together By Label - 按标签打包
└── Asset Provider - 资源提供者类型
Profile 设置
Profile 定义了构建和加载路径变量:
本地构建路径: [UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]
本地加载路径: {UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]
远程构建路径: ServerData/[BuildTarget]
远程加载路径: http://[PrivateIpAddress]:[HostingServicePort]
构建和部署
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
public class AddressablesBuilder
{
[MenuItem("Build/Build Addressables")]
static void BuildAddressables()
{
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
// 设置构建目标
AddressableAssetProfileSettings profileSettings = settings.profileSettings;
// 构建
AddressableAssetSettings.BuildPlayerContent();
Debug.Log("Addressables 构建完成");
}
// 清理构建缓存
[MenuItem("Build/Clean Addressables Cache")]
static void CleanCache()
{
AddressableAssetSettings.CleanPlayerContent();
Debug.Log("Addressables 缓存已清理");
}
}
#endif
内存管理最佳实践
public class AddressablesMemoryManager : MonoBehaviour
{
// 跟踪所有加载的句柄
private Dictionary<string, AsyncOperationHandle> loadedHandles = new Dictionary<string, AsyncOperationHandle>();
// 加载并跟踪
public async System.Threading.Tasks.Task<T> LoadAssetAsync<T>(string address) where T : Object
{
if (loadedHandles.ContainsKey(address))
{
return (T)loadedHandles[address].Result;
}
AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(address);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
loadedHandles[address] = handle;
return handle.Result;
}
return null;
}
// 释放指定资源
public void ReleaseAsset(string address)
{
if (loadedHandles.TryGetValue(address, out AsyncOperationHandle handle))
{
Addressables.Release(handle);
loadedHandles.Remove(address);
}
}
// 释放所有资源
public void ReleaseAll()
{
foreach (var handle in loadedHandles.Values)
{
Addressables.Release(handle);
}
loadedHandles.Clear();
}
// 检查内存使用
void LogMemoryUsage()
{
Debug.Log($"加载的资源数量: {loadedHandles.Count}");
// 使用 Addressables 事件查看器
// Window > Asset Management > Addressables > Event Viewer
}
}
资源管理系统对比
| 特性 | Prefab | AssetBundle | Addressables |
|---|---|---|---|
| 学习曲线 | 低 | 高 | 中 |
| 内存控制 | 有限 | 完全控制 | 自动管理 |
| 依赖管理 | 自动 | 手动 | 自动 |
| 热更新 | 不支持 | 支持 | 支持 |
| 远程加载 | 不支持 | 支持 | 支持 |
| 适用项目 | 小型项目 | 大型项目 | 所有项目 |
选择建议
小型项目:
- 使用 Prefab 和 Resources(简单资源)
- 使用 Addressables(需要灵活性)
中型项目:
- 使用 Addressables 作为主要资源管理系统
- 结合 Prefab 进行场景内复用
大型项目:
- 使用 Addressables 管理所有动态加载资源
- 建立完善的资源分组和标签策略
- 实现资源热更新和 CDN 分发
实践示例:完整的资源管理器
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// 统一的资源管理器,支持 Addressables 加载和内存管理
/// </summary>
public class ResourceManager : MonoBehaviour
{
public static ResourceManager Instance { get; private set; }
// 已加载资源的缓存
private Dictionary<string, AsyncOperationHandle> loadedAssets = new Dictionary<string, AsyncOperationHandle>();
// 正在加载中的资源
private Dictionary<string, List<System.Action<Object>>> loadingCallbacks = new Dictionary<string, List<System.Action<Object>>>();
// 实例化对象的跟踪
private Dictionary<string, List<GameObject>> instantiatedObjects = new Dictionary<string, List<GameObject>>();
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
#region 同步加载(内部使用异步)
/// <summary>
/// 加载资源(异步回调)
/// </summary>
public void LoadAsset<T>(string address, System.Action<T> callback) where T : Object
{
StartCoroutine(LoadAssetCoroutine(address, callback));
}
IEnumerator LoadAssetCoroutine<T>(string address, System.Action<T> callback) where T : Object
{
// 检查是否已加载
if (loadedAssets.TryGetValue(address, out AsyncOperationHandle existingHandle))
{
callback?.Invoke(existingHandle.Result as T);
yield break;
}
// 检查是否正在加载
if (loadingCallbacks.ContainsKey(address))
{
loadingCallbacks[address].Add((obj) => callback?.Invoke(obj as T));
yield break;
}
// 开始加载
loadingCallbacks[address] = new List<System.Action<Object>>();
AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(address);
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
loadedAssets[address] = handle;
// 回调
callback?.Invoke(handle.Result);
// 处理等待中的回调
if (loadingCallbacks.TryGetValue(address, out var callbacks))
{
foreach (var cb in callbacks)
{
cb?.Invoke(handle.Result);
}
}
}
else
{
Debug.LogError($"加载资源失败: {address}, 错误: {handle.OperationException}");
callback?.Invoke(null);
}
loadingCallbacks.Remove(address);
}
#endregion
#region 异步加载(async/await)
/// <summary>
/// 异步加载资源
/// </summary>
public async Task<T> LoadAssetAsync<T>(string address) where T : Object
{
// 检查缓存
if (loadedAssets.TryGetValue(address, out AsyncOperationHandle existingHandle))
{
return existingHandle.Result as T;
}
AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(address);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
loadedAssets[address] = handle;
return handle.Result;
}
Debug.LogError($"加载资源失败: {address}");
return null;
}
/// <summary>
/// 批量加载资源
/// </summary>
public async Task<IList<T>> LoadAssetsAsync<T>(string label) where T : Object
{
AsyncOperationHandle<IList<T>> handle = Addressables.LoadAssetsAsync<T>(label, null);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
// 缓存所有加载的资源
foreach (var asset in handle.Result)
{
string address = asset.name;
if (!loadedAssets.ContainsKey(address))
{
// 注意:这里无法获取单个资源的句柄
// 实际项目中可能需要调整策略
}
}
return handle.Result;
}
return null;
}
#endregion
#region 实例化
/// <summary>
/// 加载并实例化 GameObject
/// </summary>
public async Task<GameObject> InstantiateAsync(string address, Transform parent = null)
{
AsyncOperationHandle<GameObject> handle = Addressables.InstantiateAsync(address, parent);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
// 跟踪实例化对象
if (!instantiatedObjects.ContainsKey(address))
{
instantiatedObjects[address] = new List<GameObject>();
}
instantiatedObjects[address].Add(handle.Result);
return handle.Result;
}
return null;
}
/// <summary>
/// 释放实例化对象
/// </summary>
public void ReleaseInstance(GameObject instance)
{
// 从跟踪列表中移除
foreach (var kvp in instantiatedObjects)
{
if (kvp.Value.Remove(instance))
{
break;
}
}
Addressables.ReleaseInstance(instance);
}
#endregion
#region 释放资源
/// <summary>
/// 释放指定资源
/// </summary>
public void ReleaseAsset(string address)
{
if (loadedAssets.TryGetValue(address, out AsyncOperationHandle handle))
{
// 先销毁所有实例
if (instantiatedObjects.TryGetValue(address, out var instances))
{
foreach (var instance in instances.ToArray())
{
if (instance != null)
{
Addressables.ReleaseInstance(instance);
}
}
instantiatedObjects.Remove(address);
}
Addressables.Release(handle);
loadedAssets.Remove(address);
}
}
/// <summary>
/// 释放所有资源
/// </summary>
public void ReleaseAll()
{
// 释放所有实例
foreach (var instances in instantiatedObjects.Values)
{
foreach (var instance in instances)
{
if (instance != null)
{
Addressables.ReleaseInstance(instance);
}
}
}
instantiatedObjects.Clear();
// 释放所有句柄
foreach (var handle in loadedAssets.Values)
{
Addressables.Release(handle);
}
loadedAssets.Clear();
// 强制 GC
Resources.UnloadUnusedAssets();
System.GC.Collect();
}
#endregion
#region 场景加载
/// <summary>
/// 加载场景
/// </summary>
public async Task<SceneInstance> LoadSceneAsync(string address, LoadSceneMode mode = LoadSceneMode.Single)
{
AsyncOperationHandle<SceneInstance> handle = Addressables.LoadSceneAsync(address, mode);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
return handle.Result;
}
return default;
}
/// <summary>
/// 卸载场景
/// </summary>
public async Task UnloadSceneAsync(SceneInstance sceneInstance)
{
AsyncOperationHandle<SceneInstance> handle = Addressables.UnloadSceneAsync(sceneInstance);
await handle.Task;
}
#endregion
#region 下载管理
/// <summary>
/// 获取下载大小
/// </summary>
public async Task<long> GetDownloadSizeAsync(string address)
{
AsyncOperationHandle<long> handle = Addressables.GetDownloadSizeAsync(address);
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Addressables.Release(handle);
return handle.Result;
}
return 0;
}
/// <summary>
/// 清除缓存
/// </summary>
public void ClearCache(string address)
{
Addressables.ClearDependencyCacheAsync(address);
}
#endregion
#region 调试
/// <summary>
/// 获取当前加载的资源数量
/// </summary>
public int GetLoadedAssetCount()
{
return loadedAssets.Count;
}
/// <summary>
/// 获取当前实例化的对象数量
/// </summary>
public int GetInstantiatedCount()
{
int count = 0;
foreach (var instances in instantiatedObjects.Values)
{
count += instances.Count;
}
return count;
}
void OnDestroy()
{
ReleaseAll();
}
#endregion
}
最佳实践
1. 资源规划
- 合理分组:将经常一起使用的资源放在同一个 Group
- 标签策略:使用标签分类资源,方便批量加载
- 命名规范:使用一致的命名规范,如
Assets/Prefabs/Characters/Player.prefab
2. 内存优化
- 及时释放:使用完毕后立即释放资源
- 避免重复加载:使用缓存管理已加载的资源
- 监控内存:使用 Addressables Event Viewer 监控资源使用
3. 加载优化
- 预加载:在合适的时机预加载即将需要的资源
- 异步加载:使用异步加载避免卡顿
- 进度反馈:对于大资源,提供加载进度反馈
4. 调试技巧
// 启用 Addressables 日志
#if UNITY_EDITOR
void EnableAddressablesLogging()
{
UnityEngine.AddressableAssets.Addressables.Log += (message) =>
{
Debug.Log($"[Addressables] {message}");
};
}
#endif
// 使用 Event Viewer 监控
// Window > Asset Management > Addressables > Event Viewer
// 可以实时查看资源的加载、释放和内存使用情况