GeWuYou b3838ce8c7 docs(godot): 添加 Godot 架构集成和场景系统文档
- 新增 Godot 架构集成文档,介绍 AbstractArchitecture 和 ArchitectureAnchor
- 添加 Godot 场景系统文档,涵盖 SceneBehavior 和场景生命周期管理
- 包含数据与存档系统文档,介绍 IDataRepository 和 ISaveRepository 接口
- 提供完整的代码示例和最佳实践指南
- 覆盖多架构支持、热重载和场景参数传递等高级功能
- 包含常见问题解答和相关文档链接
2026-03-07 13:02:19 +08:00

13 KiB
Raw Blame History

title, description
title description
Godot 场景系统 Godot 场景系统提供了 GFramework 场景管理与 Godot 场景树的完整集成。

Godot 场景系统

概述

Godot 场景系统是 GFramework.Godot 中连接框架场景管理与 Godot 场景树的核心组件。它提供了场景行为封装、场景工厂、场景注册表等功能,让你可以在 Godot 项目中使用 GFramework 的场景管理系统。

通过 Godot 场景系统,你可以使用 GFramework 的场景路由、生命周期管理等功能,同时保持与 Godot 场景系统的完美兼容。

主要特性

  • 场景行为封装SceneBehavior
  • 场景工厂和注册表
  • 与 Godot PackedScene 集成
  • 多种场景行为类型Node2D、Node3D、Control
  • 场景生命周期管理
  • 场景根节点管理

核心概念

场景行为

SceneBehaviorBase<T> 封装了 Godot 节点的场景行为:

public abstract class SceneBehaviorBase<T> : ISceneBehavior
    where T : Node
{
    protected readonly T Owner;
    public string Key { get; }
    public IScene Scene { get; }
}

场景工厂

GodotSceneFactory 负责创建场景实例:

public class GodotSceneFactory : ISceneFactory
{
    public ISceneBehavior Create(string sceneKey);
}

场景注册表

IGodotSceneRegistry 管理场景资源:

public interface IGodotSceneRegistry
{
    void Register(string key, PackedScene scene);
    PackedScene Get(string key);
}

基本用法

创建场景脚本

using Godot;
using GFramework.Game.Abstractions.scene;

public partial class MainMenuScene : Control, IScene
{
    public async ValueTask OnLoadAsync(ISceneEnterParam? param)
    {
        GD.Print("加载主菜单资源");
        await Task.CompletedTask;
    }

    public async ValueTask OnEnterAsync()
    {
        GD.Print("进入主菜单");
        Show();
        await Task.CompletedTask;
    }

    public async ValueTask OnPauseAsync()
    {
        GD.Print("暂停主菜单");
        await Task.CompletedTask;
    }

    public async ValueTask OnResumeAsync()
    {
        GD.Print("恢复主菜单");
        await Task.CompletedTask;
    }

    public async ValueTask OnExitAsync()
    {
        GD.Print("退出主菜单");
        Hide();
        await Task.CompletedTask;
    }

    public async ValueTask OnUnloadAsync()
    {
        GD.Print("卸载主菜单资源");
        await Task.CompletedTask;
    }
}

注册场景

using GFramework.Godot.scene;
using Godot;

public class GameSceneRegistry : GodotSceneRegistry
{
    publieneRegistry()
    {
        // 注册场景资源
        Register("MainMenu", GD.Load<PackedScene>("res://scenes/MainMenu.tscn"));
        Register("Gameplay", GD.Load<PackedScene>("res://scenes/Gameplay.tscn"));
        Register("Pause", GD.Load<PackedScene>("res://scenes/Pause.tscn"));
    }
}

设置场景系统

using GFramework.Godot.architecture;
using GFramework.Godot.scene;

public class GameArchitecture : AbstractArchitecture
{
    protected override void InstallModules()
    {
        // 注册场景注册表
        var sceneRegistry = new GameSceneRegistry();
        RegisterUtility<IGodotSceneRegistry>(sceneRegistry);

        // 注册场景工厂
        var sceneFactory = new GodotSceneFactory();
        RegisterUtility<ISceneFactory>(sceneFactory);

        // 注册场景路由
        var sceneRouter = new GodotSceneRouter();
        RegisterSystem<ISceneRouter>(sceneRouter);
    }
}

使用场景路由

using Godot;
using GFramework.Godot.extensions;

public partial class GameController : Node
{
    public override void _Ready()
    {
        // 切换到主菜单
        SwitchToMainMenu();
    }

    private async void SwitchToMainMenu()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();
        await sceneRouter.ReplaceAsync("MainMenu");
    }

    private async void StartGame()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();
        await sceneRouter.ReplaceAsync("Gameplay");
    }

    private async void ShowPause()
    {
        var sceneRouter = this.GetSystem<ISceneRouter>();
        await sceneRouter.PushAsync("Pause");
    }
}

高级用法

使用场景行为提供者

using Godot;
using GFramework.Game.Abstractions.scene;
using GFramework.Godot.scene;

public partial class GameplayScene : Node2D, ISceneBehaviorProvider
{
    private GameplaySceneBehavior _behavior;

    public override void _Ready()
    {
        _behavior = new GameplaySceneBehavior(this, "Gameplay");
    }

    public ISceneBehavior GetScene()
    {
        return _behavior;
    }
}

// 自定义场景行为
public class GameplaySceneBehavior : Node2DSceneBehavior
{
    public GameplaySceneBehavior(Node2D owner, string key) : base(owner, key)
    {
    }

    protected override async ValueTask OnLoadInternalAsync(ISceneEnterParam? param)
    {
        GD.Print("加载游戏场景");
        // 加载游戏资源
        await Task.CompletedTask;
    }

    protected override async ValueTask OnEnterInternalAsync()
    {
        GD.Print("进入游戏场景");
        Owner.Show();
        await Task.CompletedTask;
    }
}

不同类型的场景行为

// Node2D 场景
public class Node2DSceneBehavior : SceneBehaviorBase<Node2D>
{
    public Node2DSceneBehavior(Node2D owner, string key) : base(owner, key)
    {
    }
}

// Node3D 场景
public class Node3DSceneBehavior : SceneBehaviorBase<Node3D>
{
    public Node3DSceneBehavior(Node3D owner, string key) : base(owner, key)
    {
    }
}

// Control 场景UI
public class ControlSceneBehavior : SceneBehaviorBase<Control>
{
    public ControlSceneBehavior(Control owner, string key) : base(owner, key)
    {
    }
}

场景根节点管理

using Godot;
using GFramework.Godot.scene;

public partial class SceneRoot : Node, ISceneRoot
{
    private Node _currentSceneNode;

    public void AttachScene(Node sceneNode)
    {
        // 移除旧场景
        if (_currentSceneNode != null)
        {
            RemoveChild(_currentSceneNode);
            _currentSceneNode.QueueFree();
        }

        // 添加新场景
        _currentSceneNode = sceneNode;
        AddChild(_currentSceneNode);
    }

    public void DetachScene(Node sceneNode)
    {
        if (_currentSceneNode == sceneNode)
        {
            RemoveChild(_currentSceneNode);
            _currentSceneNode = null;
        }
    }
}

场景参数传递

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

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

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

        await Task.CompletedTask;
    }

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

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

场景预加载

public partial class LoadingScene : Control
{
    public override async void _Ready()
    {
        // 预加载下一个场景
        await PreloadNextScene();

        // 切换到预加载的场景
        var sceneRouter = this.GetSystem<ISceneRouter>();
        await sceneRouter.ReplaceAsync("Gameplay");
    }

    private async Task PreloadNextScene()
    {
        var sceneFactory = this.GetUtility<ISceneFactory>();
        var sceneBehavior = sceneFactory.Create("Gameplay");

        // 预加载场景资源
        await sceneBehavior.LoadAsync(null);

        GD.Print("场景预加载完成");
    }
}

场景转换动画

using Godot;
using GFramework.Game.Abstractions.scene;

public class FadeTransitionHandler : ISceneTransitionHandler
{
    private ColorRect _fadeRect;

    public FadeTransitionHandler(ColorRect fadeRect)
    {
        _fadeRect = fadeRect;
    }

    public async ValueTask OnBeforeExitAsync(SceneTransitionEvent @event)
    {
        // 淡出动画
        var tween = _fadeRect.CreateTween();
        tween.TweenProperty(_fadeRect, "modulate:a", 1.0f, 0.3f);
        await tween.ToSignal(tween, Tween.SignalName.Finished);
    }

    public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
    {
        // 淡入动画
        var tween = _fadeRect.CreateTween();
        tween.TweenProperty(_fadeRect, "modulate:a", 0.0f, 0.3f);
        await tween.ToSignal(tween, Tween.SignalName.Finished);
    }

    // ... 其他方法
}

场景间通信

// 通过事件通信
public partial class GameplayScene : Node2D, IScene
{
    public async ValueTask OnEnterAsync()
    {
        // 发送场景进入事件
        this.SendEvent(new GameplaySceneEnteredEvent());
        await Task.CompletedTask;
    }
}

// 在其他地方监听
public partial class HUD : Control
{
    public override void _Ready()
    {
        this.RegisterEvent<GameplaySceneEnteredEvent>(OnGameplayEntered);
    }

    private void OnGameplayEntered(GameplaySceneEnteredEvent evt)
    {
        GD.Print("游戏场景已进入,显示 HUD");
        Show();
    }
}

最佳实践

  1. 场景脚本实现 IScene 接口:获得完整的生命周期管理

     public partial class MyScene : Node2D, IScene { }
     public partial class MyScene : Node2D { } // 无生命周期管理
    
  2. 使用场景注册表管理场景资源:集中管理所有场景

    public class GameSceneRegistry : GodotSceneRegistry
    {
        public GameSceneRegistry()
        {
            Register("MainMenu", GD.Load<PackedScene>("res://scenes/MainMenu.tscn"));
            Register("Gameplay", GD.Load<PackedScene>("res://scenes/Gameplay.tscn"));
        }
    }
    
  3. 在 OnLoadAsync 中加载资源:避免场景切换卡顿

    public async ValueTask OnLoadAsync(ISceneEnterParam? param)
    {
        // 异步加载资源
        await LoadTexturesAsync();
        await LoadAudioAsync();
    }
    
  4. 使用场景根节点管理场景树:保持场景树结构清晰

    // 创建场景根节点
    var sceneRoot = new Node { Name = "SceneRoot" };
    AddChild(sceneRoot);
    
    // 绑定到场景路由
    sceneRouter.BindRoot(sceneRoot);
    
  5. 正确清理场景资源:在 OnUnloadAsync 中释放资源

    public async ValueTask OnUnloadAsync()
    {
        // 释放资源
        _texture?.Dispose();
        _audioStream?.Dispose();
        await Task.CompletedTask;
    }
    
  6. 使用场景参数传递数据:避免使用全局变量

     await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam { Level = 1 });
     GlobalData.CurrentLevel = 1; // 避免全局状态
    

常见问题

问题:如何在 Godot 场景中使用 GFramework

解答 场景脚本实现 IScene 接口:

public partial class MyScene : Node2D, IScene
{
    public async ValueTask OnLoadAsync(ISceneEnterParam? param) { }
    public async ValueTask OnEnterAsync() { }
    // ... 实现其他方法
}

问题:场景切换时节点如何管理?

解答 使用场景根节点管理:

// 场景路由会自动管理节点的添加和移除
await sceneRouter.ReplaceAsync("NewScene");
// 旧场景节点会被移除,新场景节点会被添加

问题:如何实现场景预加载?

解答 使用场景工厂提前创建场景:

var sceneFactory = this.GetUtility<ISceneFactory>();
var sceneBehavior = sceneFactory.Create("NextScene");
await sceneBehavior.LoadAsync(null);

问题:场景生命周期方法的调用顺序是什么?

解答

  • 进入场景:OnLoadAsync -> OnEnterAsync -> OnShow
  • 暂停场景:OnPause -> OnHide
  • 恢复场景:OnShow -> OnResume
  • 退出场景:OnHide -> OnExitAsync -> OnUnloadAsync

问题:如何在场景中访问架构组件?

解答 使用扩展方法:

public partial class MyScene : Node2D, IScene
{
    public async ValueTask OnEnterAsync()
    {
        var playerModel = this.GetModel<PlayerModel>();
        var gameSystem = this.GetSystem<GameSystem>();
        await Task.CompletedTask;
    }
}

问题:场景切换时如何显示加载界面?

解答 使用场景转换处理器:

public class LoadingScreenHandler : ISceneTransitionHandler
{
    public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event)
    {
        // 显示加载界面
        ShowLoadingScreen();
        await Task.CompletedTask;
    }

    public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
    {
        // 隐藏加载界面
        HideLoadingScreen();
        await Task.CompletedTask;
    }
}

相关文档