---
title: 场景系统
description: 场景系统提供了完整的场景生命周期管理、路由导航和转换控制功能。
---

# 场景系统

## 概述

场景系统是 GFramework.Game 中用于管理游戏场景的核心组件。它提供了场景的加载、卸载、切换、暂停和恢复等完整生命周期管理，以及基于栈的场景导航机制。

通过场景系统，你可以轻松实现场景之间的平滑切换，管理场景栈（如主菜单 -> 游戏 -> 暂停菜单），并在场景转换时执行自定义逻辑。

**主要特性**：

- 完整的场景生命周期管理
- 基于栈的场景导航
- 场景转换管道和钩子
- 路由守卫（Route Guard）
- 场景工厂和行为模式
- 异步加载和卸载

## 核心概念

### 场景接口

`IScene` 定义了场景的完整生命周期：

```csharp
public interface IScene
{
    ValueTask OnLoadAsync(ISceneEnterParam? param);    // 加载资源
    ValueTask OnEnterAsync();                          // 进入场景
    ValueTask OnPauseAsync();                          // 暂停场景
    ValueTask OnResumeAsync();                         // 恢复场景
    ValueTask OnExitAsync();                           // 退出场景
    ValueTask OnUnloadAsync();                         // 卸载资源
}
```

### 场景路由

`ISceneRouter` 管理场景的导航和切换：

```csharp
public interface ISceneRouter : ISystem
{
    ISceneBehavior? Current { get; }              // 当前场景
    string? CurrentKey { get; }                   // 当前场景键
    IEnumerable<ISceneBehavior> Stack { get; }    // 场景栈
    bool IsTransitioning { get; }                 // 是否正在切换

    ValueTask ReplaceAsync(string sceneKey, ISceneEnterParam? param = null);
    ValueTask PushAsync(string sceneKey, ISceneEnterParam? param = null);
    ValueTask PopAsync();
    ValueTask ClearAsync();
}
```

### 场景行为

`ISceneBehavior` 封装了场景的具体实现和引擎集成：

```csharp
public interface ISceneBehavior
{
    string Key { get; }                    // 场景唯一标识
    IScene Scene { get; }                  // 场景实例
    ValueTask LoadAsync(ISceneEnterParam? param);
    ValueTask UnloadAsync();
}
```

## 基本用法

### 定义场景

实现 `IScene` 接口创建场景：

```csharp
using GFramework.Game.Abstractions.Scene;

public class MainMenuScene : IScene
{
    public async ValueTask OnLoadAsync(ISceneEnterParam? param)
    {
        // 加载场景资源
        Console.WriteLine("加载主菜单资源");
        await Task.Delay(100); // 模拟加载
    }

    public async ValueTask OnEnterAsync()
    {
        // 进入场景
        Console.WriteLine("进入主菜单");
        // 显示 UI、播放音乐等
        await Task.CompletedTask;
    }

    public async ValueTask OnPauseAsync()
    {
        // 暂停场景
        Console.WriteLine("暂停主菜单");
        await Task.CompletedTask;
    }

    public async ValueTask OnResumeAsync()
    {
        // 恢复场景
        Console.WriteLine("恢复主菜单");
        await Task.CompletedTask;
    }

    public async ValueTask OnExitAsync()
    {
        // 退出场景
        Console.WriteLine("退出主菜单");
        // 隐藏 UI、停止音乐等
        await Task.CompletedTask;
    }

    public async ValueTask OnUnloadAsync()
    {
        // 卸载场景资源
        Console.WriteLine("卸载主菜单资源");
        await Task.Delay(50); // 模拟卸载
    }
}
```

### 注册场景

在场景注册表中注册场景：

```csharp
using GFramework.Game.Abstractions.Scene;

public class GameSceneRegistry : IGameSceneRegistry
{
    private readonly Dictionary<string, Type> _scenes = new();

    public GameSceneRegistry()
    {
        // 注册场景
        Register("MainMenu", typeof(MainMenuScene));
        Register("Gameplay", typeof(GameplayScene));
        Register("Pause", typeof(PauseScene));
    }

    public void Register(string key, Type sceneType)
    {
        _scenes[key] = sceneType;
    }

    public Type? GetSceneType(string key)
    {
        return _scenes.TryGetValue(key, out var type) ? type : null;
    }
}
```

### 切换场景

使用场景路由进行导航：

```csharp
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Rule;

[ContextAware]
public partial class GameController : IController
{
    public async Task StartGame()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();

        // 替换当前场景（清空场景栈）
        await sceneRouter.ReplaceAsync("Gameplay");
    }

    public async Task ShowPauseMenu()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();

        // 压入新场景（保留当前场景）
        await sceneRouter.PushAsync("Pause");
    }

    public async Task ClosePauseMenu()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();

        // 弹出当前场景（恢复上一个场景）
        await sceneRouter.PopAsync();
    }
}
```

## 高级用法

### 场景参数传递

通过 `ISceneEnterParam` 传递数据：

```csharp
// 定义场景参数
public class GameplayEnterParam : ISceneEnterParam
{
    public int Level { get; set; }
    public string Difficulty { get; set; }
}

// 在场景中接收参数
public class GameplayScene : IScene
{
    private int _level;
    private string _difficulty;

    public async ValueTask OnLoadAsync(ISceneEnterParam? param)
    {
        if (param is GameplayEnterParam gameplayParam)
        {
            _level = gameplayParam.Level;
            _difficulty = gameplayParam.Difficulty;
            Console.WriteLine($"加载关卡 {_level}，难度: {_difficulty}");
        }

        await Task.CompletedTask;
    }

    // ... 其他生命周期方法
}

// 切换场景时传递参数
await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam
{
    Level = 1,
    Difficulty = "Normal"
});
```

### 路由守卫

使用路由守卫控制场景切换：

```csharp
using GFramework.Game.Abstractions.Scene;

public class SaveGameGuard : ISceneRouteGuard
{
    public async ValueTask<bool> CanLeaveAsync(
        ISceneBehavior from,
        string toKey,
        ISceneEnterParam? param)
    {
        // 离开游戏场景前检查是否需要保存
        if (from.Key == "Gameplay")
        {
            var needsSave = CheckIfNeedsSave();
            if (needsSave)
            {
                await SaveGameAsync();
            }
        }

        return true; // 允许离开
    }

    public async ValueTask<bool> CanEnterAsync(
        string toKey,
        ISceneEnterParam? param)
    {
        // 进入场景前的验证
        if (toKey == "Gameplay")
        {
            // 检查是否满足进入条件
            var canEnter = CheckGameplayRequirements();
            return canEnter;
        }

        return true;
    }

    private bool CheckIfNeedsSave() => true;
    private async Task SaveGameAsync() => await Task.Delay(100);
    private bool CheckGameplayRequirements() => true;
}

// 注册守卫
sceneRouter.AddGuard(new SaveGameGuard());
```

### 场景转换处理器

自定义场景转换逻辑：

```csharp
using GFramework.Game.Abstractions.Scene;

public class FadeTransitionHandler : ISceneTransitionHandler
{
    public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event)
    {
        Console.WriteLine($"准备加载场景: {@event.ToKey}");
        // 显示加载画面
        await ShowLoadingScreen();
    }

    public async ValueTask OnAfterLoadAsync(SceneTransitionEvent @event)
    {
        Console.WriteLine($"场景加载完成: {@event.ToKey}");
        await Task.CompletedTask;
    }

    public async ValueTask OnBeforeEnterAsync(SceneTransitionEvent @event)
    {
        Console.WriteLine($"准备进入场景: {@event.ToKey}");
        // 播放淡入动画
        await PlayFadeIn();
    }

    public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
    {
        Console.WriteLine($"已进入场景: {@event.ToKey}");
        // 隐藏加载画面
        await HideLoadingScreen();
    }

    public async ValueTask OnBeforeExitAsync(SceneTransitionEvent @event)
    {
        Console.WriteLine($"准备退出场景: {@event.FromKey}");
        // 播放淡出动画
        await PlayFadeOut();
    }

    public async ValueTask OnAfterExitAsync(SceneTransitionEvent @event)
    {
        Console.WriteLine($"已退出场景: {@event.FromKey}");
        await Task.CompletedTask;
    }

    private async Task ShowLoadingScreen() => await Task.Delay(100);
    private async Task HideLoadingScreen() => await Task.Delay(100);
    private async Task PlayFadeIn() => await Task.Delay(200);
    private async Task PlayFadeOut() => await Task.Delay(200);
}

// 注册转换处理器
sceneRouter.AddTransitionHandler(new FadeTransitionHandler());
```

### 场景栈管理

```csharp
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Rule;

[ContextAware]
public partial class SceneNavigationController : IController
{
    public async Task NavigateToSettings()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();

        // 检查场景是否已在栈中
        if (sceneRouter.Contains("Settings"))
        {
            Console.WriteLine("设置场景已打开");
            return;
        }

        // 压入设置场景
        await sceneRouter.PushAsync("Settings");
    }

    public void ShowSceneStack()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();

        Console.WriteLine("当前场景栈:");
        foreach (var scene in sceneRouter.Stack)
        {
            Console.WriteLine($"- {scene.Key}");
        }
    }

    public async Task ReturnToMainMenu()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();

        // 清空所有场景并加载主菜单
        await sceneRouter.ClearAsync();
        await sceneRouter.ReplaceAsync("MainMenu");
    }
}
```

### 场景加载进度

```csharp
public class GameplayScene : IScene
{
    public async ValueTask OnLoadAsync(ISceneEnterParam? param)
    {
        var resourceManager = GetResourceManager();

        // 加载多个资源并报告进度
        var resources = new[]
        {
            "textures/player.png",
            "textures/enemy.png",
            "audio/bgm.mp3",
            "models/level.obj"
        };

        for (int i = 0; i < resources.Length; i++)
        {
            await resourceManager.LoadAsync<object>(resources[i]);

            // 报告进度
            var progress = (i + 1) / (float)resources.Length;
            ReportProgress(progress);
        }
    }

    private void ReportProgress(float progress)
    {
        // 发送进度事件
        Console.WriteLine($"加载进度: {progress * 100:F0}%");
    }

    // ... 其他生命周期方法
}
```

### 场景预加载

```csharp
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Rule;

[ContextAware]
public partial class PreloadController : IController
{
    public async Task PreloadNextLevel()
    {
        var sceneFactory = this.GetUtility<ISceneFactory>();

        // 预加载下一关场景
        var scene = sceneFactory.Create("Level2");
        await scene.OnLoadAsync(null);

        Console.WriteLine("下一关预加载完成");
    }
}
```

## 最佳实践

1. **在 OnLoad 中加载资源，在 OnUnload 中释放**：保持资源管理清晰
   ```csharp
   public async ValueTask OnLoadAsync(ISceneEnterParam? param)
   {
       _texture = await LoadTextureAsync("player.png");
   }

   public async ValueTask OnUnloadAsync()
   {
       _texture?.Dispose();
       _texture = null;
   }
   ```

2. **使用 Push/Pop 管理临时场景**：如暂停菜单、设置界面
   ```csharp
   // 打开暂停菜单（保留游戏场景）
   await sceneRouter.PushAsync("Pause");

   // 关闭暂停菜单（恢复游戏场景）
   await sceneRouter.PopAsync();
   ```

3. **使用 Replace 切换主要场景**：如从菜单到游戏
   ```csharp
   // 开始游戏（清空场景栈）
   await sceneRouter.ReplaceAsync("Gameplay");
   ```

4. **在 OnPause/OnResume 中管理状态**：暂停和恢复游戏逻辑
   ```csharp
   public async ValueTask OnPauseAsync()
   {
       // 暂停游戏逻辑
       _gameTimer.Pause();
       _audioSystem.PauseBGM();
   }

   public async ValueTask OnResumeAsync()
   {
       // 恢复游戏逻辑
       _gameTimer.Resume();
       _audioSystem.ResumeBGM();
   }
   ```

5. **使用路由守卫处理业务逻辑**：如保存检查、权限验证
   ```csharp
   public async ValueTask<bool> CanLeaveAsync(...)
   {
       if (HasUnsavedChanges())
       {
           var confirmed = await ShowSaveDialog();
           if (confirmed)
           {
               await SaveAsync();
           }
           return confirmed;
       }
       return true;
   }
   ```

6. **避免在场景切换时阻塞**：使用异步操作
   ```csharp
   ✓ await sceneRouter.ReplaceAsync("Gameplay");
   ✗ sceneRouter.ReplaceAsync("Gameplay").Wait(); // 可能死锁
   ```

## 常见问题

### 问题：Replace、Push、Pop 有什么区别？

**解答**：

- **Replace**：清空场景栈，加载新场景（用于主要场景切换）
- **Push**：压入新场景，暂停当前场景（用于临时场景）
- **Pop**：弹出当前场景，恢复上一个场景（用于关闭临时场景）

```csharp
// 场景栈示例
await sceneRouter.ReplaceAsync("MainMenu");  // [MainMenu]
await sceneRouter.PushAsync("Settings");     // [MainMenu, Settings]
await sceneRouter.PushAsync("About");        // [MainMenu, Settings, About]
await sceneRouter.PopAsync();                // [MainMenu, Settings]
await sceneRouter.PopAsync();                // [MainMenu]
```

### 问题：如何在场景之间传递数据？

**解答**：
有几种方式：

1. **通过场景参数**：

```csharp
await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam
{
    Level = 5
});
```

2. **通过 Model**：

```csharp
var gameModel = this.GetModel<GameModel>();
gameModel.CurrentLevel = 5;
await sceneRouter.ReplaceAsync("Gameplay");
```

3. **通过事件**：

```csharp
this.SendEvent(new LevelSelectedEvent { Level = 5 });
await sceneRouter.ReplaceAsync("Gameplay");
```

### 问题：场景切换时如何显示加载画面？

**解答**：
使用场景转换处理器：

```csharp
public class LoadingScreenHandler : ISceneTransitionHandler
{
    public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event)
    {
        await ShowLoadingScreen();
    }

    public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
    {
        await HideLoadingScreen();
    }

    // ... 其他方法
}
```

### 问题：如何防止用户在场景切换时操作？

**解答**：
检查 `IsTransitioning` 状态：

```csharp
public async Task ChangeScene(string sceneKey)
{
    var sceneRouter = this.GetSystem<ISceneRouter>();

    if (sceneRouter.IsTransitioning)
    {
        Console.WriteLine("场景正在切换中，请稍候");
        return;
    }

    await sceneRouter.ReplaceAsync(sceneKey);
}
```

### 问题：场景切换失败怎么办？

**解答**：
使用 try-catch 捕获异常：

```csharp
try
{
    await sceneRouter.ReplaceAsync("Gameplay");
}
catch (Exception ex)
{
    Console.WriteLine($"场景切换失败: {ex.Message}");
    // 回退到安全场景
    await sceneRouter.ReplaceAsync("MainMenu");
}
```

### 问题：如何实现场景预加载？

**解答**：
在后台预先加载场景资源：

```csharp
// 在当前场景中预加载下一个场景
var factory = this.GetUtility<ISceneFactory>();
var nextScene = factory.Create("NextLevel");
await nextScene.OnLoadAsync(null);

// 稍后快速切换
await sceneRouter.ReplaceAsync("NextLevel");
```

## 相关文档

- [UI 系统](/zh-CN/game/ui) - UI 页面管理
- [资源管理系统](/zh-CN/core/resource) - 场景资源加载
- [状态机系统](/zh-CN/core/state-machine) - 场景状态管理
- [Godot 场景系统](/zh-CN/godot/scene) - Godot 引擎集成
- [存档系统实现教程](/zh-CN/tutorials/save-system) - 场景切换时保存数据
