Unity 资源加载与项目结构速查

这篇按资源问题来查:文件放哪、怎么引用、Resources 怎么用、Addressables 怎么加载释放、Prefab 和 ScriptableObject 注意什么。


快速索引

我想做什么 优先方式
固定依赖资源 Inspector 直接拖引用
少量路径加载 Resources.Load
中大型异步加载 Addressables
原样打包外部文件 StreamingAssets
配置数据 ScriptableObject / 表格
运行时频繁创建对象 Prefab + 对象池
热更新资源 Addressables / YooAssets 等资源系统
避免资源丢引用 保留 .meta,不要系统外乱移动

0. 推荐目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Assets/
_Project/ # 项目自研内容
Runtime/ # 运行时代码
Gameplay/
UI/
Systems/
Editor/ # 编辑器工具代码
Scenes/ # 场景
Prefabs/ # 预制体
ScriptableObjects/ # 配置资产
Materials/ # 材质
Textures/ # 贴图
Audio/ # 音频
Animations/ # 动画
Shaders/ # Shader
Plugins/ # 插件
ThirdParty/ # 第三方库
Resources/ # Resources.Load 用,少放
StreamingAssets/ # 原样打包

1. Inspector 引用

1
2
3
[SerializeField] private GameObject enemyPrefab;  // 固定 Prefab
[SerializeField] private AudioClip hitSfx; // 固定音效
[SerializeField] private Sprite icon; // 固定图片

适合:

1
2
3
4
Prefab 固定依赖
UI 固定图标
音效、材质、配置
场景里明确存在的对象

优点:

1
2
3
4
引用关系可见
不靠字符串路径
构建依赖更清晰
运行时不需要查找

2. Resources.Load

资源路径:

1
Assets/Resources/Enemies/Goblin.prefab

加载写法:

1
GameObject prefab = Resources.Load<GameObject>("Enemies/Goblin"); // 不写 Resources 和扩展名

实例化:

1
GameObject instance = Instantiate(prefab);       // 加载后生成

路径集中管理:

1
2
3
4
public static class ResourcePaths
{
public const string Goblin = "Enemies/Goblin"; // 避免字符串到处散落
}

慎用:

1
2
3
大量资源全放 Resources                 # 包体和内存管理变混乱
路径字符串到处写 # 改名后全项目坏
运行中频繁同步 Load # 可能卡顿

3. Addressables 基础

异步加载:

1
2
3
4
5
6
7
8
9
10
11
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

private AsyncOperationHandle<GameObject> handle;

private async void LoadEnemy()
{
handle = Addressables.LoadAssetAsync<GameObject>("Enemy/Goblin"); // 按地址加载
GameObject prefab = await handle.Task;
Instantiate(prefab);
}

释放:

1
2
3
4
5
6
7
private void OnDestroy()
{
if (handle.IsValid())
{
Addressables.Release(handle); // 加载和释放成对
}
}

实例化 Addressables:

1
2
AsyncOperationHandle<GameObject> instanceHandle =
Addressables.InstantiateAsync("Enemy/Goblin", spawnPoint.position, spawnPoint.rotation);

释放实例:

1
Addressables.ReleaseInstance(instance);       // 释放 Addressables 实例

检查项:

1
2
3
4
5
地址是否唯一
Group 是否规划清楚
公共依赖是否重复打包
Load 后是否 Release
远程资源是否构建并上传

4. StreamingAssets

常见用途:

1
2
3
4
视频文件
本地配置
外部库需要读取的原始文件
首包内置数据

路径:

1
string path = Path.Combine(Application.streamingAssetsPath, "config.json"); // 平台路径

注意:

1
2
3
Android 上 StreamingAssets 在 apk 内,不能直接当普通文件读
需要 UnityWebRequest 或平台适配
StreamingAssets 会原样打包,体积要控制

5. ScriptableObject 配置

定义配置:

1
2
3
4
5
6
7
8
9
using UnityEngine;

[CreateAssetMenu(menuName = "Game/Enemy Config")]
public sealed class EnemyConfig : ScriptableObject
{
public int maxHp = 100; // 敌人血量
public float moveSpeed = 3f; // 移动速度
public GameObject prefab; // 对应 Prefab
}

使用配置:

1
2
3
4
5
6
[SerializeField] private EnemyConfig config;   // Inspector 拖配置资产

private void Spawn()
{
GameObject enemy = Instantiate(config.prefab);
}

注意:

1
2
3
ScriptableObject 适合静态配置
不要把运行时状态直接写回配置资产
运行时需要复制一份数据再改

6. Prefab 管理

1
2
3
4
Prefab 不要引用场景对象              # 实例化后引用容易失效
Prefab 命名清楚 # Enemy_Goblin、UI_BagItem
Prefab Variant 控制层级 # 公共基础 + 差异变体
修改 Prefab 后检查场景实例 # 防止覆盖或丢引用

实例化后初始化:

1
2
Enemy enemy = Instantiate(enemyPrefab, spawnPoint.position, spawnPoint.rotation);
enemy.Init(config); // 生成后传配置

7. 资源释放

普通对象:

1
Destroy(instance);                          // 销毁场景实例

Resources 清理:

1
2
await Resources.UnloadUnusedAssets();       // 卸载未使用资源,通常放加载阶段后
System.GC.Collect(); // 强制 GC,慎用

Addressables:

1
2
Addressables.Release(handle);               // 释放资源句柄
Addressables.ReleaseInstance(instance); // 释放实例

8. .meta 文件

1
2
3
4
5
.meta 保存 GUID
Unity 引用靠 GUID,不只靠文件名
移动资源尽量在 Unity Editor 内操作
Git 必须提交 .meta
删除 .meta 会导致引用丢失

9. 常见坑速查

1
2
3
4
5
6
7
所有资源都塞 Resources              # 包体和加载不可控
Addressables Load 后不 Release # 内存增长
Prefab 引用场景对象 # 实例化后丢引用
运行时修改 ScriptableObject # 编辑器配置可能被污染
改资源路径但字符串没改 # Resources 加载失败
删除或重建 .meta # 引用大面积丢失
StreamingAssets 在 Android 当 File 读 # 读取失败

系列导航