# 基于具体场景的VR移动端加载方案优化

根据您提供的具体应用场景，我将设计一个精确匹配您需求的资源加载方案。

## 应用场景分析

您的VR应用具有明确的结构：

1. **启动器（主工程）**：包含默认等待关卡和教学关卡
2. **故事内容（热更新）**：独立美术工程打包的故事资源
3. **固定流程**：等待关卡 → 教学关卡 → 故事内容 → 等待关卡（闭环）
4. **资源分离**：基础资源在主包，故事资源通过热更新

## 优化加载方案

针对这种结构，我推荐以下分层加载方案：

### 1. 整体架构设计

```
├── 主工程 (启动器)
│   ├── 等待关卡 (永久常驻)
│   ├── 教学关卡 (按需加载)
│   └── 过渡关卡/环境 (常驻，但可激活/隐藏)
│
└── 热更新内容
    ├── 故事A
    ├── 故事B
    └── 故事C...
```

### 2. 资源加载管理器实现

```csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;

/// <summary>
/// VR应用流程和资源管理器
/// </summary>
public class VRExperienceManager : MonoBehaviour
{
    #region 单例实现
    private static VRExperienceManager _instance;
    public static VRExperienceManager Instance => _instance;
    
    private void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        _instance = this;
        DontDestroyOnLoad(gameObject);
        
        InitializeManager();
    }
    #endregion
    
    #region 序列化字段
    [Header("关卡配置")]
    [SerializeField] private string _waitingSceneKey = "waiting_scene";
    [SerializeField] private string _tutorialSceneKey = "tutorial_scene";
    [SerializeField] private string _transitionEnvironmentKey = "transition_environment";
    
    [Header("VR设置")]
    [SerializeField] private Transform _playerRig;
    [SerializeField] private float _fadeTransitionDuration = 0.8f;
    [SerializeField] private bool _enableDynamicQuality = true;
    
    [Header("引用")]
    [SerializeField] private VRTransitionEnvironment _transitionEnvironment;
    [SerializeField] private InitializationProgress _initProgress;
    [SerializeField] private MemoryMonitor _memoryMonitor;
    #endregion
    
    #region 私有字段
    // 场景加载状态
    private AsyncOperationHandle<SceneInstance> _currentSceneHandle;
    private AsyncOperationHandle<SceneInstance> _preloadedSceneHandle;
    private string _currentSceneKey;
    private string _preloadedSceneKey;
    private bool _isScenePreloaded = false;
    
    // 故事管理
    private StoryMetadata _currentStory;
    private int _currentStorySceneIndex = -1;
    private List<StoryMetadata> _availableStories = new List<StoryMetadata>();
    
    // 应用状态
    private enum AppState { Initializing, WaitingRoom, Tutorial, Story, Transitioning }
    private AppState _currentState = AppState.Initializing;
    
    // 资源管理
    private Dictionary<string, AsyncOperationHandle> _persistentHandles = new Dictionary<string, AsyncOperationHandle>();
    private bool _isTransitionEnvironmentLoaded = false;
    #endregion
    
    #region 初始化
    private void InitializeManager()
    {
        // 注册内存警告回调
        if (_memoryMonitor != null)
        {
            _memoryMonitor.OnMemoryCritical += HandleLowMemory;
        }
        
        // 初始化Addressables
        Addressables.InitializeAsync().Completed += (op) => 
        {
            // 加载过渡环境（常驻资源）
            LoadTransitionEnvironment();
            
            // 开始应用初始化流程
            StartCoroutine(InitializeApplicationFlow());
        };
    }
    
    private IEnumerator InitializeApplicationFlow()
    {
        _currentState = AppState.Initializing;
        
        // 1. 确保等待关卡已加载（应该是应用启动场景）
        if (SceneManager.GetActiveScene().name != _waitingSceneKey)
        {
            Debug.LogWarning("应用未从等待场景启动，正在加载等待场景...");
            // 加载等待场景
            var waitingSceneLoad = Addressables.LoadSceneAsync(_waitingSceneKey, LoadSceneMode.Single);
            yield return waitingSceneLoad;
            _currentSceneHandle = waitingSceneLoad;
            _currentSceneKey = _waitingSceneKey;
        }
        else
        {
            // 记录当前场景
            _currentSceneKey = _waitingSceneKey;
            // 创建等待场景的句柄（不实际加载，只为了后续能统一处理）
            _currentSceneHandle = Addressables.LoadSceneAsync(_waitingSceneKey, LoadSceneMode.Single, false);
        }
        
        // 2. 获取可用的故事列表
        yield return StartCoroutine(FetchAvailableStories());
        
        // 3. 预加载教学场景
        if (_enablePreloading && HasSufficientMemory())
        {
            StartCoroutine(PreloadScene(_tutorialSceneKey));
        }
        
        // 4. 更新状态
        _currentState = AppState.WaitingRoom;
        
        // 5. 通知初始化完成
        _initProgress?.SetComplete();
        
        Debug.Log("VR体验管理器初始化完成");
    }
    
    private IEnumerator FetchAvailableStories()
    {
        _availableStories.Clear();
        
        // 从服务器或本地缓存获取可用故事
        // 此处简化为示例，实际应从配置或服务器获取
        var storyListHandle = Addressables.LoadAssetAsync<StoryListData>("story_list");
        yield return storyListHandle;
        
        if (storyListHandle.Status == AsyncOperationStatus.Succeeded)
        {
            var storyList = storyListHandle.Result;
            _availableStories = new List<StoryMetadata>(storyList.stories);
            Debug.Log($"已加载 {_availableStories.Count} 个可用故事");
        }
        else
        {
            Debug.LogError("无法加载故事列表!");
        }
        
        // 释放列表资源
        Addressables.Release(storyListHandle);
    }
    
    private void LoadTransitionEnvironment()
    {
        if (_isTransitionEnvironmentLoaded)
            return;
            
        var handle = Addressables.LoadAssetAsync<GameObject>(_transitionEnvironmentKey);
        handle.Completed += (op) => 
        {
            if (op.Status == AsyncOperationStatus.Succeeded)
            {
                var instance = Instantiate(op.Result);
                DontDestroyOnLoad(instance);
                _transitionEnvironment = instance.GetComponent<VRTransitionEnvironment>();
                _isTransitionEnvironmentLoaded = true;
                
                // 隐藏过渡环境
                _transitionEnvironment.gameObject.SetActive(false);
                
                // 保存句柄以便常驻内存
                _persistentHandles["transitionEnvironment"] = handle;
            }
            else
            {
                Debug.LogError("无法加载过渡环境!");
                Addressables.Release(handle);
            }
        };
    }
    #endregion
    
    #region 场景转换公共API
    /// <summary>
    /// 进入教学关卡
    /// </summary>
    public void EnterTutorial()
    {
        if (_currentState == AppState.WaitingRoom)
        {
            StartCoroutine(TransitionToScene(_tutorialSceneKey));
        }
        else
        {
            Debug.LogWarning("只能从等待室进入教学!");
        }
    }
    
    /// <summary>
    /// 开始故事体验
    /// </summary>
    public void StartStoryExperience(string storyId)
    {
        // 检查状态是否允许
        if (_currentState != AppState.Tutorial && _currentState != AppState.WaitingRoom)
        {
            Debug.LogWarning("必须从等待室或教学关卡启动故事!");
            return;
        }
        
        // 查找故事
        var story = _availableStories.Find(s => s.id == storyId);
        if (story == null)
        {
            Debug.LogError($"找不到ID为 {storyId} 的故事!");
            return;
        }
        
        // 设置当前故事
        _currentStory = story;
        _currentStorySceneIndex = 0;
        
        // 启动故事的第一个场景
        string firstSceneKey = story.sceneKeys[0];
        StartCoroutine(TransitionToScene(firstSceneKey));
    }
    
    /// <summary>
    /// 进入故事的下一个场景
    /// </summary>
    public void NextStoryScene()
    {
        if (_currentState != AppState.Story || _currentStory == null)
        {
            Debug.LogWarning("当前不在故事体验中!");
            return;
        }
        
        // 检查是否有下一个场景
        if (_currentStorySceneIndex < _currentStory.sceneKeys.Count - 1)
        {
            _currentStorySceneIndex++;
            string nextSceneKey = _currentStory.sceneKeys[_currentStorySceneIndex];
            StartCoroutine(TransitionToScene(nextSceneKey));
            
            // 预加载下一个场景（如果有）
            if (_enablePreloading && _currentStorySceneIndex < _currentStory.sceneKeys.Count - 1)
            {
                string preloadSceneKey = _currentStory.sceneKeys[_currentStorySceneIndex + 1];
                StartCoroutine(PreloadScene(preloadSceneKey));
            }
        }
        else
        {
            // 已是最后一个场景，完成故事体验
            FinishStoryExperience();
        }
    }
    
    /// <summary>
    /// 完成故事体验，返回等待室
    /// </summary>
    public void FinishStoryExperience()
    {
        if (_currentState == AppState.Story)
        {
            // 清理故事状态
            _currentStory = null;
            _currentStorySceneIndex = -1;
            
            // 返回等待室
            StartCoroutine(TransitionToScene(_waitingSceneKey));
        }
    }
    
    /// <summary>
    /// 中断当前体验，返回等待室
    /// </summary>
    public void ReturnToWaitingRoom()
    {
        StartCoroutine(TransitionToScene(_waitingSceneKey));
    }
    #endregion
    
    #region 场景加载实现
    private IEnumerator TransitionToScene(string sceneKey)
    {
        // 更新状态
        _currentState = AppState.Transitioning;
        
        // 显示过渡环境
        _transitionEnvironment.gameObject.SetActive(true);
        
        // 开始淡入过渡
        yield return _transitionEnvironment.FadeIn(_fadeTransitionDuration);
        
        // 检查是否有预加载的场景
        if (_isScenePreloaded && _preloadedSceneKey == sceneKey)
        {
            // 使用预加载的场景
            yield return StartCoroutine(ActivatePreloadedScene());
        }
        else
        {
            // 直接加载新场景
            yield return StartCoroutine(LoadNewScene(sceneKey));
        }
        
        // 更新应用状态
        UpdateAppState(sceneKey);
        
        // 调整玩家位置
        PositionPlayerInScene(sceneKey);
        
        // 淡出过渡环境
        yield return _transitionEnvironment.FadeOut(_fadeTransitionDuration);
        
        // 隐藏过渡环境
        _transitionEnvironment.gameObject.SetActive(false);
        
        // 执行资源清理
        StartCoroutine(PerformDelayedCleanup());
        
        // 如果是故事场景，尝试预加载下一个场景
        if (_currentState == AppState.Story && _enablePreloading && HasSufficientMemory())
        {
            if (_currentStorySceneIndex < _currentStory.sceneKeys.Count - 1)
            {
                string nextSceneKey = _currentStory.sceneKeys[_currentStorySceneIndex + 1];
                StartCoroutine(PreloadScene(nextSceneKey));
            }
        }
    }
    
    private IEnumerator LoadNewScene(string sceneKey)
    {
        // 显示加载进度
        _transitionEnvironment.ShowLoadingUI(true);
        
        // 释放之前预加载的场景（如果有）
        if (_isScenePreloaded && _preloadedSceneHandle.IsValid())
        {
            Addressables.Release(_preloadedSceneHandle);
            _isScenePreloaded = false;
            _preloadedSceneKey = null;
        }
        
        // 1. 下载依赖
        var downloadHandle = Addressables.DownloadDependenciesAsync(sceneKey);
        while (!downloadHandle.IsDone)
        {
            float progress = downloadHandle.PercentComplete * 0.3f; // 权重30%
            _transitionEnvironment.UpdateProgress(progress, $"下载资源... {Mathf.FloorToInt(progress * 100)}%");
            yield return null;
        }
        Addressables.Release(downloadHandle);
        
        // 2. 加载新场景
        var loadHandle = Addressables.LoadSceneAsync(sceneKey, LoadSceneMode.Single);
        while (!loadHandle.IsDone)
        {
            float progress = 0.3f + loadHandle.PercentComplete * 0.7f; // 权重70%
            _transitionEnvironment.UpdateProgress(progress, $"加载场景... {Mathf.FloorToInt(progress * 100)}%");
            yield return null;
        }
        
        // 检查加载是否成功
        if (loadHandle.Status != AsyncOperationStatus.Succeeded)
        {
            Debug.LogError($"场景 {sceneKey} 加载失败!");
            // 返回等待室
            yield return StartCoroutine(LoadNewScene(_waitingSceneKey));
            yield break;
        }
        
        // 释放当前场景句柄
        if (_currentSceneHandle.IsValid())
        {
            Addressables.Release(_currentSceneHandle);
        }
        
        // 更新当前场景引用
        _currentSceneHandle = loadHandle;
        _currentSceneKey = sceneKey;
        
        // 隐藏加载UI
        _transitionEnvironment.ShowLoadingUI(false);
    }
    
    private IEnumerator ActivatePreloadedScene()
    {
        // 显示简化的加载UI
        _transitionEnvironment.ShowLoadingUI(true, "准备场景...");
        
        // 激活预加载的场景
        var activateHandle = _preloadedSceneHandle.Result.ActivateAsync();
        while (!activateHandle.isDone)
        {
            _transitionEnvironment.UpdateProgress(activateHandle.progress);
            yield return null;
        }
        
        // 释放当前场景句柄
        if (_currentSceneHandle.IsValid())
        {
            Addressables.Release(_currentSceneHandle);
        }
        
        // 更新引用
        _currentSceneHandle = _preloadedSceneHandle;
        _currentSceneKey = _preloadedSceneKey;
        
        // 重置预加载状态
        _isScenePreloaded = false;
        _preloadedSceneKey = null;
        _preloadedSceneHandle = default;
        
        // 隐藏加载UI
        _transitionEnvironment.ShowLoadingUI(false);
    }
    
    private IEnumerator PreloadScene(string sceneKey)
    {
        // 检查内存状况
        if (!HasSufficientMemory())
        {
            Debug.Log($"内存不足，取消预加载场景 {sceneKey}");
            yield break;
        }
        
        // 取消之前的预加载
        if (_isScenePreloaded && _preloadedSceneHandle.IsValid())
        {
            Addressables.Release(_preloadedSceneHandle);
            _isScenePreloaded = false;
        }
        
        Debug.Log($"开始预加载场景: {sceneKey}");
        
        // 后台下载依赖
        var downloadHandle = Addressables.DownloadDependenciesAsync(sceneKey);
        yield return downloadHandle;
        Addressables.Release(downloadHandle);
        
        // 再次检查内存状况
        if (!HasSufficientMemory())
        {
            Debug.Log($"下载依赖后内存不足，取消预加载场景 {sceneKey}");
            yield break;
        }
        
        // 后台加载场景但不激活
        var loadHandle = Addressables.LoadSceneAsync(sceneKey, LoadSceneMode.Single, false);
        yield return loadHandle;
        
        // 更新预加载状态
        _preloadedSceneHandle = loadHandle;
        _preloadedSceneKey = sceneKey;
        _isScenePreloaded = true;
        
        Debug.Log($"场景预加载完成: {sceneKey}");
    }
    
    private void UpdateAppState(string sceneKey)
    {
        // 根据场景更新应用状态
        if (sceneKey == _waitingSceneKey)
        {
            _currentState = AppState.WaitingRoom;
        }
        else if (sceneKey == _tutorialSceneKey)
        {
            _currentState = AppState.Tutorial;
        }
        else
        {
            _currentState = AppState.Story;
        }
    }
    
    private void PositionPlayerInScene(string sceneKey)
    {
        // 查找场景中的玩家起始点
        var spawnPoint = GameObject.FindGameObjectWithTag("PlayerSpawnPoint");
        if (spawnPoint != null && _playerRig != null)
        {
            _playerRig.position = spawnPoint.transform.position;
            _playerRig.rotation = spawnPoint.transform.rotation;
        }
    }
    
    private IEnumerator PerformDelayedCleanup()
    {
        // 等待场景稳定
        yield return new WaitForSeconds(2.0f);
        
        // 清理未使用资源
        var unloadOp = Resources.UnloadUnusedAssets();
        yield return unloadOp;
        
        // 执行垃圾回收
        GC.Collect();
    }
    #endregion
    
    #region 内存与性能管理
    [Header("预加载设置")]
    [SerializeField] private bool _enablePreloading = true;
    [SerializeField] private float _memoryThresholdForPreloading = 0.7f; // 70%以上内存使用率时不预加载
    
    private bool HasSufficientMemory()
    {
        if (!_enablePreloading)
            return false;
            
        if (_memoryMonitor != null)
        {
            float memoryUsage = _memoryMonitor.GetMemoryUsage();
            return memoryUsage < _memoryThresholdForPreloading;
        }
        
        // 默认保守处理
        return true;
    }
    
    private void HandleLowMemory(float usagePercent)
    {
        Debug.LogWarning($"内存不足警告! 使用率: {usagePercent:P2}");
        
        // 如果有预加载的场景，释放它以节省内存
        if (_isScenePreloaded && _preloadedSceneHandle.IsValid())
        {
            Debug.Log("释放预加载场景以节省内存");
            Addressables.Release(_preloadedSceneHandle);
            _isScenePreloaded = false;
            _preloadedSceneKey = null;
        }
        
        // 强制内存清理
        StartCoroutine(PerformEmergencyCleanup());
    }
    
    private IEnumerator PerformEmergencyCleanup()
    {
        // 释放未使用的资源
        var unloadOp = Resources.UnloadUnusedAssets();
        yield return unloadOp;
        
        // 执行垃圾回收
        GC.Collect();
        
        // 如果还是不足，可以降低质量设置
        if (_enableDynamicQuality && _memoryMonitor.GetMemoryUsage() > 0.85f)
        {
            int currentQuality = QualitySettings.GetQualityLevel();
            if (currentQuality > 0)
            {
                QualitySettings.SetQualityLevel(currentQuality - 1, true);
                Debug.Log($"降低质量等级至 {QualitySettings.names[currentQuality - 1]}");
            }
        }
    }
    #endregion
    
    #region 辅助方法
    public List<StoryMetadata> GetAvailableStories()
    {
        return new List<StoryMetadata>(_availableStories);
    }
    
    public StoryMetadata GetCurrentStory()
    {
        return _currentStory;
    }
    
    public void SetPlayerRig(Transform playerRig)
    {
        _playerRig = playerRig;
    }
    #endregion
}
```

### 3. VR过渡环境实现

```csharp
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

/// <summary>
/// VR友好的过渡环境控制器
/// </summary>
public class VRTransitionEnvironment : MonoBehaviour
{
    [Header("视觉设置")]
    [SerializeField] private Material _fadeScreenMaterial;
    [SerializeField] private MeshRenderer _fadeScreenRenderer;
    [SerializeField] private Transform _environmentRoot;
    [SerializeField] private GameObject _loadingUI;
    [SerializeField] private Image _progressBar;
    [SerializeField] private TextMeshProUGUI _progressText;
    [SerializeField] private TextMeshProUGUI _statusText;
    
    [Header("音频设置")]
    [SerializeField] private AudioSource _transitionAudioSource;
    [SerializeField] private AudioClip _fadeInSound;
    [SerializeField] private AudioClip _fadeOutSound;
    
    [Header("舒适度设置")]
    [SerializeField] private float _vignetteFadeSpeed = 1.5f;
    [SerializeField] private Color _backgroundColor = Color.black;
    [SerializeField] private Color _vignetteColor = new Color(0, 0, 0, 0.8f);
    
    // 初始化
    private void Awake()
    {
        // 确保初始状态
        _fadeScreenMaterial.SetFloat("_FadeAmount", 0);
        _fadeScreenMaterial.SetColor("_Color", _backgroundColor);
        _fadeScreenMaterial.SetColor("_VignetteColor", _vignetteColor);
        
        if (_loadingUI != null)
            _loadingUI.SetActive(false);
    }
    
    /// <summary>
    /// 执行淡入过渡
    /// </summary>
    public IEnumerator FadeIn(float duration)
    {
        // 播放过渡音效
        if (_transitionAudioSource != null && _fadeInSound != null)
        {
            _transitionAudioSource.clip = _fadeInSound;
            _transitionAudioSource.Play();
        }
        
        float startTime = Time.time;
        float elapsed = 0;
        
        while (elapsed < duration)
        {
            elapsed = Time.time - startTime;
            float t = Mathf.Clamp01(elapsed / duration);
            
            // 应用渐进式过渡
            _fadeScreenMaterial.SetFloat("_FadeAmount", Mathf.Lerp(0, 1, t));
            _fadeScreenMaterial.SetFloat("_VignetteAmount", Mathf.Lerp(0, 1, t * 1.5f)); // 光晕比淡入稍快
            
            yield return null;
        }
        
        // 确保最终状态
        _fadeScreenMaterial.SetFloat("_FadeAmount", 1);
        _fadeScreenMaterial.SetFloat("_VignetteAmount", 1);
    }
    
    /// <summary>
    /// 执行淡出过渡
    /// </summary>
    public IEnumerator FadeOut(float duration)
    {
        // 播放过渡音效
        if (_transitionAudioSource != null && _fadeOutSound != null)
        {
            _transitionAudioSource.clip = _fadeOutSound;
            _transitionAudioSource.Play();
        }
        
        float startTime = Time.time;
        float elapsed = 0;
        
        while (elapsed < duration)
        {
            elapsed = Time.time - startTime;
            float t = Mathf.Clamp01(elapsed / duration);
            
            // 应用渐进式过渡
            _fadeScreenMaterial.SetFloat("_FadeAmount", Mathf.Lerp(1, 0, t));
            // 光晕保持较长时间，然后快速淡出
            _fadeScreenMaterial.SetFloat("_VignetteAmount", elapsed < duration * 0.7f ? 1 : Mathf.Lerp(1, 0, (elapsed - duration * 0.7f) / (duration * 0.3f)));
            
            yield return null;
        }
        
        // 确保最终状态
        _fadeScreenMaterial.SetFloat("_FadeAmount", 0);
        _fadeScreenMaterial.SetFloat("_VignetteAmount", 0);
    }
    
    /// <summary>
    /// 显示或隐藏加载UI
    /// </summary>
    public void ShowLoadingUI(bool show, string status = null)
    {
        if (_loadingUI != null)
            _loadingUI.SetActive(show);
            
        if (status != null && _statusText != null)
            _statusText.text = status;
            
        // 重置进度条
        if (show && _progressBar != null)
            _progressBar.fillAmount = 0;
    }
    
    /// <summary>
    /// 更新加载进度
    /// </summary>
    public void UpdateProgress(float progress, string status = null)
    {
        if (_progressBar != null)
            _progressBar.fillAmount = progress;
            
        if (_progressText != null)
            _progressText.text = $"{Mathf.Round(progress * 100)}%";
            
        if (status != null && _statusText != null)
            _statusText.text = status;
    }
}
```

### 4. 内存监控器

```csharp
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 内存使用监控器
/// </summary>
public class MemoryMonitor : MonoBehaviour
{
    [Header("监控设置")]
    [SerializeField] private float _monitorInterval = 3.0f;
    [SerializeField] private float _criticalMemoryThreshold = 0.85f; // 85%内存使用率视为危险
    [SerializeField] private bool _showDebugInfo = false;
    
    [Header("UI引用")]
    [SerializeField] private Text _memoryText;
    
    // 事件
    public event Action<float> OnMemoryCritical;
    
    private void Start()
    {
        if (_showDebugInfo)
        {
            StartCoroutine(MonitorMemoryRoutine());
        }
        else
        {
            // 隐藏调试UI
            if (_memoryText != null)
                _memoryText.gameObject.SetActive(false);
                
            // 仍然监控内存，但不显示
            StartCoroutine(BackgroundMonitorRoutine());
        }
    }
    
    private IEnumerator MonitorMemoryRoutine()
    {
        var wait = new WaitForSeconds(_monitorInterval);
        
        while (true)
        {
            float usage = GetMemoryUsage();
            UpdateMemoryText(usage);
            
            // 检查是否达到临界值
            if (usage > _criticalMemoryThreshold)
            {
                OnMemoryCritical?.Invoke(usage);
            }
            
            yield return wait;
        }
    }
    
    private IEnumerator BackgroundMonitorRoutine()
    {
        var wait = new WaitForSeconds(_monitorInterval * 2); // 更低频率的后台监控
        
        while (true)
        {
            float usage = GetMemoryUsage();
            
            // 只在接近危险值时触发
            if (usage > _criticalMemoryThreshold)
            {
                OnMemoryCritical?.Invoke(usage);
            }
            
            yield return wait;
        }
    }
    
    /// <summary>
    /// 获取当前内存使用率
    /// </summary>
    public float GetMemoryUsage()
    {
        #if UNITY_ANDROID && !UNITY_EDITOR
        try
        {
            using (var activityManager = new AndroidJavaObject("android.app.ActivityManager"))
            using (var memoryInfo = new AndroidJavaObject("android.app.ActivityManager$MemoryInfo"))
            {
                activityManager.Call("getMemoryInfo", memoryInfo);
                long availMem = memoryInfo.Get<long>("availMem");
                long totalMem = memoryInfo.Get<long>("totalMem");
                
                return 1f - ((float)availMem / totalMem);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"获取Android内存信息失败: {e.Message}");
            // 回退到通用方法
            return (float)GC.GetTotalMemory(false) / (SystemInfo.systemMemorySize * 1024 * 1024);
        }
        #else
        // 简化的非Android平台实现
        return (float)GC.GetTotalMemory(false) / (SystemInfo.systemMemorySize * 1024 * 1024);
        #endif
    }
    
    private void UpdateMemoryText(float usage)
    {
        if (_memoryText != null)
        {
            // 显示内存使用率
            _memoryText.text = $"内存: {(usage * 100):F1}%";
            
            // 根据使用率变色
            if (usage > _criticalMemoryThreshold)
                _memoryText.color = Color.red;
            else if (usage > _criticalMemoryThreshold * 0.8f)
                _memoryText.color = Color.yellow;
            else
                _memoryText.color = Color.green;
        }
    }
}
```

### 5. 故事和场景数据结构

```csharp
using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 故事元数据
/// </summary>
[Serializable]
public class StoryMetadata
{
    public string id;
    public string title;
    public string description;
    public string thumbnailKey;
    public string bundleId;
    public List<string> sceneKeys = new List<string>();
    public string entrySceneKey => sceneKeys.Count > 0 ? sceneKeys[0] : "";
    public long estimatedSize; // 字节数
    public bool isDownloaded;
}

/// <summary>
/// 故事列表数据资源
/// </summary>
[CreateAssetMenu(fileName = "StoryListData", menuName = "VR Experience/Story List Data")]
public class StoryListData : ScriptableObject
{
    public List<StoryMetadata> stories = new List<StoryMetadata>();
}
```

### 6. 初始化进度展示

```csharp
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

/// <summary>
/// 应用初始化进度显示
/// </summary>
public class InitializationProgress : MonoBehaviour
{
    [SerializeField] private Image _progressFill;
    [SerializeField] private TextMeshProUGUI _statusText;
    [SerializeField] private TextMeshProUGUI _versionText;
    [SerializeField] private GameObject _startButton;
    [SerializeField] private float _animationSpeed = 0.5f;
    
    private void Start()
    {
        if (_versionText != null)
        {
            _versionText.text = $"版本 {Application.version}";
        }
        
        if (_startButton != null)
        {
            _startButton.SetActive(false);
        }
        
        SetProgress(0, "正在初始化...");
    }
    
    public void SetProgress(float progress, string status = null)
    {
        if (_progressFill != null)
        {
            StartCoroutine(AnimateProgress(_progressFill.fillAmount, progress));
        }
        
        if (_statusText != null && !string.IsNullOrEmpty(status))
        {
            _statusText.text = status;
        }
    }
    
    private IEnumerator AnimateProgress(float from, float to)
    {
        float elapsed = 0;
        float duration = Mathf.Abs(to - from) / _animationSpeed;
        
        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            float t = Mathf.Clamp01(elapsed / duration);
            _progressFill.fillAmount = Mathf.Lerp(from, to, t);
            yield return null;
        }
        
        _progressFill.fillAmount = to;
    }
    
    public void SetComplete()
    {
        SetProgress(1, "初始化完成");
        
        if (_startButton != null)
        {
            _startButton.SetActive(true);
        }
    }
}
```

## 启动流程图解

根据您提供的具体需求，以下是完整的启动和运行流程图解：

```
┌─────────────────┐
│ 应用启动        │
└───────┬─────────┘
        ▼
┌─────────────────┐      ┌─────────────────┐
│ 等待关卡        │──────│ 初始化          │
│ (默认场景)      │      │ - 加载配置      │
└───────┬─────────┘      │ - 获取故事列表  │
        │                │ - 加载过渡环境  │
        │                └─────────────────┘
        ▼
┌─────────────────┐
│ 教学关卡        │◄─────┐
│ (按需加载)      │      │
└───────┬─────────┘      │
        │                │
        ▼                │
┌─────────────────┐      │
│ 故事内容        │      │
│ (热更新资源)    │      │
└───────┬─────────┘      │
        │                │
        ▼                │
┌─────────────────┐      │
│ 等待关卡        │──────┘
│ (返回初始状态)  │
└─────────────────┘
```

## 资源组织建议

### 1. 主工程 Addressables 组织

```
1. 基础常驻资源组 (Bundle Mode: Pack Together)
   - 等待关卡场景
   - 过渡环境预制体
   - UI框架资源
   - 公共材质和着色器

2. 教学关卡资源组 (Bundle Mode: Pack Together)
   - 教学关卡场景
   - 教学专用模型和纹理
   - 教学UI和提示资源

3. 故事目录资源组 (Bundle Mode: Pack Together)
   - 故事列表数据
   - 故事缩略图
   - 故事描述资源
```

### 2. 热更新故事资源组织

每个故事应作为独立的美术工程，其Addressables组织如下：

```
1. 故事核心资源组 (Bundle Mode: Pack Together)
   - 场景描述数据
   - 故事配置
   - 核心机制脚本

2. 故事场景资源组 (每个场景一个组, Bundle Mode: Pack Together)
   - 场景1资源
   - 场景2资源
   - ...

3. 故事共享资源组 (Bundle Mode: Pack Together)
   - 多个场景共用的角色模型
   - 共享纹理和材质
   - 共享音频
```

## 内存优化策略

针对您的移动端VR应用，特别推荐以下内存优化策略：

### 1. 避免场景重复资源

确保每个故事中的共享资源只加载一次：

```csharp
// 在VRExperienceManager.cs中添加
private Dictionary<string, AsyncOperationHandle> _storySharedAssets = new Dictionary<string, AsyncOperationHandle>();

private IEnumerator LoadStorySharedAssets(string storyId)
{
    // 加载故事共享资源
    string sharedAssetKey = $"{storyId}_shared_assets";
    
    var handle = Addressables.LoadAssetAsync<SharedAssetReferences>(sharedAssetKey);
    yield return handle;
    
    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        var sharedAssets = handle.Result;
        
        // 加载并保持引用
        foreach (var assetRef in sharedAssets.references)
        {
            var assetHandle = assetRef.LoadAssetAsync<UnityEngine.Object>();
            yield return assetHandle;
            
            // 保存句柄以保持资源加载状态
            _storySharedAssets[assetRef.AssetGUID] = assetHandle;
        }
    }
    
    // 保存引用以便故事结束时释放
    _storySharedAssets["sharedAssetList"] = handle;
}

private void UnloadStorySharedAssets()
{
    foreach (var handle in _storySharedAssets.Values)
    {
        Addressables.Release(handle);
    }
    _storySharedAssets.Clear();
}
```

### 2. 渐进式细节加载

对于大型场景，可以先加载基础结构，然后逐步加载细节：

```csharp
// 在场景脚本中添加
private IEnumerator LoadSceneDetails()
{
    // 1. 首先加载关键游戏玩法元素
    yield return StartCoroutine(LoadGameplayElements());
    
    // 2. 短暂延迟让场景稳定
    yield return new WaitForSeconds(0.5f);
    
    // 3. 然后加载次要视觉元素
    StartCoroutine(LoadVisualEnhancements());
    
    // 4. 最后在玩家不注意时加载装饰元素
    Invoke("LoadDecorativeElements", 5.0f);
}
```

### 3. 动态内存回收

```csharp
// 添加到VRExperienceManager.cs
private Coroutine _memoryMonitorCoroutine;

private void StartMemoryMonitoring()
{
    if (_memoryMonitorCoroutine != null)
        StopCoroutine(_memoryMonitorCoroutine);
        
    _memoryMonitorCoroutine = StartCoroutine(MonitorAndRecoverMemory());
}

private IEnumerator MonitorAndRecoverMemory()
{
    var wait = new WaitForSeconds(10f); // 每10秒检查一次
    
    while (true)
    {
        float memoryUsage = _memoryMonitor.GetMemoryUsage();
        
        // 根据内存使用情况采取不同策略
        if (memoryUsage > 0.8f) // 80%以上
        {
            // 紧急内存回收
            yield return StartCoroutine(PerformEmergencyCleanup());
        }
        else if (memoryUsage > 0.7f) // 70-80%
        {
            // 轻度内存回收
            yield return Resources.UnloadUnusedAssets();
        }
        
        yield return wait;
    }
}
```

## 总结建议

基于您描述的具体VR应用场景，我推荐以下关键策略：

1. **采用常驻过渡环境**：
    
    - 避免单独的过渡场景，而是使用DontDestroyOnLoad的过渡环境
    - 为各种转换场景提供统一、舒适的VR体验
2. **闭环流程优化**：
    
    - 利用线性闭环特性预加载下一个阶段资源
    - 利用返回等待室时彻底清理内存
3. **热更新策略**：
    
    - 主工程尽量精简，只包含启动器、教学和过渡必要资源
    - 各故事作为独立更新单元，便于单独下载和更新
4. **性能安全网**：
    
    - 内存监控和自动清理机制确保稳定性
    - 动态质量调整应对不同设备性能
5. **VR舒适度考虑**：
    
    - 所有转场效果考虑VR用户舒适度
    - 加载过程中避免视觉/位置突变

这套方案针对您的移动端VR项目进行了精确定制，既考虑了热更新内容的特点，又兼顾了VR体验的流畅性和舒适度，适合您描述的线性闭环体验流程。