关于 unity-scene-assets
面向 Unity 项目的场景与资源管理模式技能,涵盖场景架构、Addressables 加载策略、加载界面和资源生命周期。帮助开发者处理异步加载、内存释放及从 Resources 迁移。
主要功能
- 01单场景、分关卡与叠加场景策略
- 02比较直接引用、Resources 与 Addressables
- 03按关卡、类型或频率组织 Addressables 组
- 04协调异步场景加载与进度界面
- 05管理预加载、延迟加载及句柄释放
- 06提供 Resources 迁移至 Addressables 的步骤
适用场景
- 01规划持久场景与游戏内容场景结构
- 02为关卡或动态内容选择资源加载方式
- 03实现带进度显示的场景切换
- 04排查 Addressables 资源释放与内存生命周期
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nice-wolf-studio/ unity-claude-skills --skill unity-scene-assets安装后在 Claude Code 中输入/unity-scene-assets确认已加载。
附带文件references/addressables-scene-patterns.md
详细介绍
场景与资源管理——决策模式
前置技能:
unity-foundations/references/prefabs-and-scenes.md(SceneManager API、叠加加载)、unity-async-patterns(Addressables 句柄生命周期、异步加载)、unity-game-architecture(引导模式)
这些模式旨在解决最常见的资源管理问题:Claude 硬编码 Resources.Load、忽略异步加载,也不考虑内存生命周期。
模式:场景架构策略
适用情况:为真实游戏(而非原型)规划项目场景结构
决策:
- 单场景——游戏创作马拉松、原型、小型游戏。所有内容都在一个场景中。无需加载,也没有复杂性。但很快就会不够用。
- 每关一个场景——线性进度(平台游戏、益智游戏)。在关卡之间调用
LoadScene(name, LoadSceneMode.Single)。分离清晰,但没有DontDestroyOnLoad就无法共享状态。 - 叠加场景组合——开放世界、持久 HUD、共享系统。始终加载“Boot”或“Persistent”场景,游戏和 UI 场景以叠加方式加载。灵活性最高,复杂度也最高。
脚手架(叠加场景协调器):
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneCoordinator : MonoBehaviour
{
[SerializeField] private string persistentSceneName = "Persistent";
private string _currentContentScene;
public static SceneCoordinator Instance { get; private set; }
void Awake()
{
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetStatic() => Instance = null;
/// <summary>
/// Load a content scene additively, unloading the previous one.
/// The persistent scene stays loaded.
/// </summary>
public async Awaitable LoadContentScene(string sceneName)
{
// Unload previous content scene
if (!string.IsNullOrEmpty(_currentContentScene))
{
await SceneManager.UnloadSceneAsync(_currentContentScene);
}
// Load new content scene
await SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
_currentContentScene = sceneName;
// Set active scene for lighting and new object spawning
SceneManager.SetActiveScene(SceneManager.GetSceneByName(sceneName));
}
}
注意:SceneManager.SetActiveScene 决定采用哪个场景的光照设置,以及新实例化的对象放置在哪个场景中。若忘记调用,对象就会生成在持久场景中(光照贴图和导航网格不正确)。持久场景必须位于 Build Settings 的索引 0 处。每个叠加场景也必须包含在 Build Settings 中。
模式:资源加载策略
适用情况:选择运行时加载资源的方式
决策:
- 直接引用(
[SerializeField])——适用于所有资源始终驻留在内存中的小型项目。最简单。资源随场景一起加载。无需手动管理生命周期。 - Resources.Load——旧方案。新项目应避免使用。启动时会索引整个 Resources 文件夹(速度慢),并将其全部内容包含在构建中(体积臃肿)。
- Addressables——适用于中大型项目、动态内容、DLC 和远程资源。支持异步加载、引用计数和标签。需要了解其生命周期。
脚手架(AssetReference 模式):
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class EnemySpawner : MonoBehaviour
{
[SerializeField] private AssetReference enemyPrefabRef; // Assign in Inspector
private AsyncOperationHandle<GameObject> _handle;
public async Awaitable<GameObject> SpawnEnemy(Vector3 position)
{
// Load the asset (ref-counted -- safe to call multiple times)
_handle = enemyPrefabRef.LoadAssetAsync<GameObject>();
var prefab = await _handle.Task;
return Instantiate(prefab, position, Quaternion.identity);
}
void OnDestroy()
{
// Release when no longer needed
if (_handle.IsValid())
Addressables.Release(_handle);
}
}
注意:Resources.Load 是同步的,会将 Resources 文件夹中的所有资源都包含在构建中(即使有些资源未使用),且没有卸载策略。迁移方式:将 Resources.Load<T>("path") 替换为 Addressables.LoadAssetAsync<T>("path")。Inspector 中的 AssetReference 字段让你无需使用字符串键即可选择 Addressable 资源——优先使用这种方式,而非基于字符串的加载。
模式:Addressables 组策略
适用情况:将资源整理到 Addressable 组中,以便构建和加载
决策:
- 按场景/关卡划分——适用于基于关卡的游戏。一起加载某关卡的所有资源,离开时卸载。内存生命周期清晰。
- 按类型划分——将所有角色、VFX、音频分别放入不同组。适合在多个场景间共享的资源。
- 按使用频率划分——核心(始终加载:UI、玩家、常见 VFX)、按需加载(敌人变体、关卡专属资源)、稀有(Boss、过场动画、季节性内容)。
脚手架(基于标签的加载):
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections.Generic;
public class LevelAssetLoader : MonoBehaviour
{
private AsyncOperationHandle<IList<GameObject>> _levelAssetsHandle;
public async Awaitable PreloadLevel(string levelLabel)
{
// Load all assets tagged with the level label
_levelAssetsHandle = Addressables.LoadAssetsAsync<GameObject>(
levelLabel,
asset => Debug.Log($"Loaded: {asset.name}")
);
await _levelAssetsHandle.Task;
}
public void UnloadLevel()
{
if (_levelAssetsHandle.IsValid())
Addressables.Release(_levelAssetsHandle);
}
}
注意:组太小、数量过多会增加目录开销(每个组都会产生 AssetBundle 元数据)。组太少、规模过大则会迫使你加载不需要的资源。大多数项目以 5–20 个组为目标。使用 Addressables Event Viewer(Window > Asset Management > Addressables > Event Viewer)进行性能分析,确认加载和卸载时机。
模式:加载画面协调
适用场景:在游戏章节之间切换并加载资源时
决策:
- AsyncOperation.allowSceneActivation —— 简单的淡出至黑屏。加载至 90%,显示加载画面,并在准备就绪后激活。采用单场景方案。
- Additive loading screen scene —— 将持久化的加载界面置于独立场景中。适用于带进度条和提示信息的复杂切换。
脚手架(带进度的场景切换):
public class SceneTransition : MonoBehaviour
{
[SerializeField] private float minimumLoadScreenTime = 1f; // Prevent flash
public async Awaitable TransitionTo(string sceneName, System.Action<float> onProgress = null)
{
// Show loading screen
await SceneManager.LoadSceneAsync("LoadingScreen", LoadSceneMode.Additive);
float startTime = Time.realtimeSinceStartup;
// Begin loading target scene (paused at 90%)
var loadOp = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
loadOp.allowSceneActivation = false;
// Unload current gameplay scene in parallel
if (!string.IsNullOrEmpty(_currentContentScene))
{
var unloadOp = SceneManager.UnloadSceneAsync(_currentContentScene);
while (!unloadOp.isDone)
{
await Awaitable.NextFrameAsync(destroyCancellationToken);
}
}
// Wait for target to reach 90% (ready to activate)
while (loadOp.progress < 0.9f)
{
// Normalize: AsyncOperation.progress maxes at 0.9 when allowSceneActivation=false
float normalizedProgress = Mathf.Clamp01(loadOp.progress / 0.9f);
onProgress?.Invoke(normalizedProgress);
await Awaitable.NextFrameAsync(destroyCancellationToken);
}
// Enforce minimum display time
float elapsed = Time.realtimeSinceStartup - startTime;
if (elapsed < minimumLoadScreenTime)
{
await Awaitable.WaitForSecondsAsync(
minimumLoadScreenTime - elapsed, destroyCancellationToken);
}
onProgress?.Invoke(1f);
// Activate the scene
loadOp.allowSceneActivation = true;
while (!loadOp.isDone)
await Awaitable.NextFrameAsync(destroyCancellationToken);
_currentContentScene = sceneName;
SceneManager.SetActiveScene(SceneManager.GetSceneByName(sceneName));
// Unload loading screen
await SceneManager.UnloadSceneAsync("LoadingScreen");
}
private string _currentContentScene;
}
注意事项:当 allowSceneActivation = false 时,AsyncOperation.progress 的最大值为 0.9。对其进行归一化:Mathf.Clamp01(op.progress / 0.9f)。将 allowSceneActivation 设为 true 时,场景会立即激活——不会有额外延迟。最短加载时间应使用 Time.realtimeSinceStartup(而不是受 timeScale 影响的 Time.time)。
模式:资源生命周期协调
适用场景:确保资源在游戏开始前加载,并在不再需要时卸载
决策:
- 进入场景时预加载 —— 在加载阶段加载所有必需资源,并在退出场景时释放。内存占用可预测,不会在运行时卡顿。最适合基于关卡的游戏。
- 按需延迟加载 —— 首次需要时加载,缓存引用,并在退出场景时释放。初始加载时间更短,但首次使用时可能出现帧卡顿。最适合开放世界。
脚手架(带进度的资源预加载器):
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections.Generic;
public class AssetPreloader : MonoBehaviour
{
private readonly List<AsyncOperationHandle> _handles = new();
/// <summary>
/// Preload a list of Addressable keys. Reports progress 0-1.
/// </summary>
public async Awaitable Preload(
IList<string> keys,
System.Action<float> onProgress = null,
CancellationToken token = default)
{
int loaded = 0;
int total = keys.Count;
foreach (string key in keys)
{
token.ThrowIfCancellationRequested();
var handle = Addressables.LoadAssetAsync<Object>(key);
_handles.Add(handle);
await handle.Task;
loaded++;
onProgress?.Invoke((float)loaded / total);
}
}
/// <summary>Release all preloaded assets.</summary>
public void ReleaseAll()
{
foreach (var handle in _handles)
{
if (handle.IsValid())
Addressables.Release(handle);
}
_handles.Clear();
}
void OnDestroy() => ReleaseAll();
}
注意事项:如果已实例化的对象仍引用已加载的资源,此时释放 Addressable 句柄,轻则导致材质变成粉色或丢失,重则导致崩溃。释放资源句柄前,务必销毁所有实例。如果需要自动跟踪,请使用 Addressables.InstantiateAsync,而不是手动执行 LoadAssetAsync + Instantiate;之后使用 Addressables.ReleaseInstance 清理。
从 Resources 迁移到 Addressables 的检查清单
| 步骤 | 操作 |
|---|---|
| 1 | 安装 Addressables 包(com.unity.addressables) |
| 2 | 打开 Window > Asset Management > Addressables > Groups |
| 3 | 如果出现提示,则创建默认设置 |
| 4 | 将资源从 Resources/ 文件夹移至常规资源文件夹 |
| 5 | 将资源标记为 Addressable(勾选 Inspector 复选框或拖入组中) |
| 6 | 将 Resources.Load<T>("path") 替换为 Addressables.LoadAssetAsync<T>("path") |
| 7 | 添加句柄跟踪和 Addressables.Release() 调用 |
| 8 | 将 Resources.LoadAll<T>() 替换为基于标签的 Addressables.LoadAssetsAsync |
| 9 | 删除空的 Resources/ 文件夹 |
| 10 | 使用 Addressables Event Viewer 进行测试,确认没有泄漏 |
保留在 Resources 中: 仅限编辑器使用的资源、测试夹具、Addressables 初始化前所需的资源(启动画面)。
相关技能
- unity-foundations/references/prefabs-and-scenes.md —— SceneManager API、附加加载(不要重复)
- unity-async-patterns —— Addressables AsyncOperationHandle 生命周期、异步取消(不要重复)
- unity-game-architecture —— 启动场景引导程序、用于场景管理的 Service Locator
- unity-performance —— 内存分析、Addressables 内存跟踪