Godot 集成教程
深入学习如何将 GFramework 与 Godot 引擎完美集成,创建高性能的游戏应用。
📋 目录
Godot 特定功能
1. 节点生命周期绑定
GFramework.Godot 提供了与 Godot 节点生命周期的无缝集成,确保框架初始化与 Godot 场景树同步。
csharp
using GFramework.Godot.architecture;
public class GodotGameArchitecture : AbstractArchitecture
{
protected override void Init()
{
// 注册核心组件
RegisterModel(new PlayerModel());
RegisterSystem(new PlayerControllerSystem());
RegisterUtility(new AudioUtility());
}
protected override void InstallModules()
{
// 安装 Godot 特定模块
InstallGodotModule(new AudioModule());
InstallGodotModule(new InputModule());
}
}2. Godot 模块系统
创建与 Godot 节点深度集成的模块:
csharp
[ContextAware]
[Log]
public partial class AudioModule : AbstractGodotModule
{
private AudioStreamPlayer _musicPlayer;
private AudioStreamPlayer _sfxPlayer;
// 模块节点本身
public override Node Node => this;
public override void Install(IArchitecture architecture)
{
// 注册音频系统
architecture.RegisterSystem(new AudioSystem());
architecture.RegisterUtility(new AudioUtility());
}
public override void OnAttach(Architecture architecture)
{
// 模块附加时的初始化
Logger.Info("Audio module attached");
// 创建音频播放器
CreateAudioPlayers();
}
public override void OnDetach(Architecture architecture)
{
// 模块分离时的清理
Logger.Info("Audio module detached");
CleanupAudioPlayers();
}
public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
switch (phase)
{
case ArchitecturePhase.Ready:
// 架构准备就绪,开始播放背景音乐
PlayBackgroundMusic();
break;
}
}
private void CreateAudioPlayers()
{
_musicPlayer = new AudioStreamPlayer();
AddChild(_musicPlayer);
_sfxPlayer = new AudioStreamPlayer();
AddChild(_sfxPlayer);
}
private void CleanupAudioPlayers()
{
_musicPlayer?.QueueFree();
_sfxPlayer?.QueueFree();
}
private void PlayBackgroundMusic()
{
var music = GD.Load<AudioStream>("res://assets/audio/background.ogg");
_musicPlayer.Stream = music;
_musicPlayer.Play();
}
}3. 节点池化系统
实现高效的 Godot 节点池化,避免频繁的创建和销毁:
csharp
using GFramework.Godot.pool;
public class BulletPoolSystem : AbstractNodePoolSystem<string, Bullet>
{
private Dictionary<string, PackedScene> _scenes = new();
public BulletPoolSystem()
{
// 预加载场景
_scenes["player"] = GD.Load<PackedScene>("res://assets/scenes/PlayerBullet.tscn");
_scenes["enemy"] = GD.Load<PackedScene>("res://assets/scenes/EnemyBullet.tscn");
}
protected override Bullet CreateItem(string key)
{
if (_scenes.TryGetValue(key, out var scene))
{
return scene.Instantiate<Bullet>();
}
throw new ArgumentException($"Unknown bullet type: {key}");
}
protected override void OnSpawn(Bullet item, string key)
{
// 重置子弹状态
item.Reset();
item.Position = Vector2.Zero;
item.Visible = true;
item.SetCollisionLayerValue(1, true);
item.SetCollisionMaskValue(1, true);
}
protected override void OnDespawn(Bullet item)
{
// 隐藏子弹
item.Visible = false;
item.SetCollisionLayerValue(1, false);
item.SetCollisionMaskValue(1, false);
// 移除父节点
item.GetParent()?.RemoveChild(item);
}
protected override bool CanDespawn(Bullet item)
{
// 只有不在使用中的子弹才能回收
return !item.IsActive && item.GetParent() != null;
}
}节点生命周期管理
1. 自动生命周期绑定
使用 GFramework 的扩展方法自动管理节点生命周期:
csharp
[ContextAware]
[Log]
public partial class PlayerController : CharacterBody2D, IController
{
private PlayerModel _playerModel;
public override void _Ready()
{
// 设置上下文
_playerModel = Context.GetModel<PlayerModel>();
// 注册事件监听,自动与节点生命周期绑定
this.RegisterEvent<PlayerInputEvent>(OnPlayerInput)
.UnRegisterWhenNodeExitTree(this);
// 监听属性变化,自动清理
_playerModel.Health.Register(OnHealthChanged)
.UnRegisterWhenNodeExitTree(this);
// 连接 Godot 信号,自动清理
this.CreateSignalBuilder(AnimationPlayer.SignalName.AnimationFinished)
.Connect(OnAnimationFinished)
.UnRegisterWhenNodeExitTree(this);
}
private void OnPlayerInput(PlayerInputEvent e)
{
// 处理玩家输入
ProcessInput(e);
}
private void OnHealthChanged(int newHealth)
{
// 更新 UI 显示
UpdateHealthDisplay(newHealth);
// 播放受伤动画
if (newHealth < _playerModel.PreviousHealth)
{
AnimationPlayer.Play("hurt");
}
}
private void OnAnimationFinished(StringName animName)
{
if (animName == "hurt")
{
AnimationPlayer.Play("idle");
}
}
}2. 延迟初始化模式
对于需要在特定时机初始化的组件:
csharp
[ContextAware]
[Log]
public partial class AdvancedController : Node, IController
{
private bool _initialized = false;
public override void _Ready()
{
// 不立即初始化,等待特定条件
this.WaitUntil(() => IsInitializationReady())
.Then(InitializeController);
}
private bool IsInitializationReady()
{
// 检查所有依赖是否准备就绪
return HasRequiredComponents() && Context.HasModel<GameModel>();
}
private void InitializeController()
{
if (_initialized) return;
Logger.Info("Initializing advanced controller");
// 执行初始化逻辑
SetupComponents();
RegisterEventListeners();
StartUpdateLoop();
_initialized = true;
}
private void SetupComponents()
{
// 设置组件
}
private void RegisterEventListeners()
{
// 注册事件监听器
this.RegisterEvent<GameStateChangeEvent>(OnGameStateChanged)
.UnRegisterWhenNodeExitTree(this);
}
private void StartUpdateLoop()
{
// 启动更新循环
SetProcess(true);
}
}3. 安全的节点操作
使用 GFramework 提供的安全扩展方法:
csharp
public partial class SafeNodeOperations : Node
{
public void SafeOperations()
{
// 安全获取子节点
var player = GetNodeX<Player>("Player");
var ui = FindChildX<UI>("UI");
// 安全添加子节点
var bullet = bulletScene.Instantiate<Bullet>();
AddChildX(bullet);
// 安全的异步操作
this.WaitUntilReady()
.Then(() => {
// 节点准备就绪后的操作
InitializeAfterReady();
});
// 安全的场景树遍历
this.ForEachChild<Node>(child => {
if (child is Sprite2D sprite)
{
ProcessSprite(sprite);
}
});
}
private void ProcessSprite(Sprite2D sprite)
{
// 处理精灵
}
private void InitializeAfterReady()
{
// 初始化逻辑
}
}信号系统集成
1. SignalBuilder 流畅 API
使用 GFramework 的 SignalBuilder 进行类型安全的信号连接:
csharp
[ContextAware]
[Log]
public partial class SignalController : Node, IController
{
private Button _button;
private Timer _timer;
private ProgressBar _progressBar;
public override void _Ready()
{
InitializeComponents();
SetupSignalConnections();
}
private void InitializeComponents()
{
_button = GetNode<Button>("Button");
_timer = GetNode<Timer>("Timer");
_progressBar = GetNode<ProgressBar>("ProgressBar");
}
private void SetupSignalConnections()
{
// 单个信号连接
this.CreateSignalBuilder(_button.SignalName.Pressed)
.Connect(OnButtonPressed)
.UnRegisterWhenNodeExitTree(this);
// 带参数的信号连接
this.CreateSignalBuilder(_timer.SignalName.Timeout)
.Connect(OnTimerTimeout)
.UnRegisterWhenNodeExitTree(this);
// 多信号连接
this.CreateSignalBuilder()
.AddSignal(_button.SignalName.Pressed, OnButtonPressed)
.AddSignal(_timer.SignalName.Timeout, OnTimerTimeout)
.AddSignal(_progressBar.SignalName.ValueChanged, OnProgressChanged)
.UnRegisterWhenNodeExitTree(this);
// 带标志的信号连接
this.CreateSignalBuilder(_timer.SignalName.Timeout)
.WithFlags(ConnectFlags.OneShot) // 单次触发
.CallImmediately() // 立即调用一次
.Connect(OnTimerTimeout)
.UnRegisterWhenNodeExitTree(this);
}
private void OnButtonPressed()
{
Logger.Info("Button pressed");
// 启动计时器
_timer.Start();
// 发送框架事件
Context.SendEvent(new ButtonClickEvent { ButtonId = "main_button" });
}
private void OnTimerTimeout()
{
Logger.Info("Timer timeout");
// 更新进度条
_progressBar.Value += 10;
// 发送框架事件
Context.SendEvent(new TimerTimeoutEvent());
}
private void OnProgressChanged(double value)
{
Logger.Debug($"Progress changed: {value}");
// 发送框架事件
Context.SendEvent(new ProgressChangeEvent { Value = value });
}
}2. 信号与框架事件桥接
实现 Godot 信号与 GFramework 事件系统的双向桥接:
csharp
[ContextAware]
[Log]
public partial class SignalEventBridge : Node, IController
{
private Button _uiButton;
private HealthBar _healthBar;
public override void _Ready()
{
InitializeComponents();
SetupSignalToEventBridge();
SetupEventToSignalBridge();
}
private void InitializeComponents()
{
_uiButton = GetNode<Button>("UI/Button");
_healthBar = GetNode<HealthBar>("UI/HealthBar");
}
private void SetupSignalToEventBridge()
{
// Godot 信号 -> 框架事件
this.CreateSignalBuilder(_uiButton.SignalName.Pressed)
.Connect(() => {
Context.SendEvent(new UIActionEvent {
Action = "button_click",
Source = "main_button"
});
})
.UnRegisterWhenNodeExitTree(this);
this.CreateSignalBuilder(_healthBar.SignalName.HealthDepleted)
.Connect(() => {
Context.SendEvent(new PlayerDeathEvent { Source = "health_system" });
})
.UnRegisterWhenNodeExitTree(this);
}
private void SetupEventToSignalBridge()
{
// 框架事件 -> Godot 信号
this.RegisterEvent<PlayerHealthChangeEvent>(OnPlayerHealthChange)
.UnRegisterWhenNodeExitTree(this);
this.RegisterEvent<GamePauseEvent>(OnGamePause)
.UnRegisterWhenNodeExitTree(this);
this.RegisterEvent<ScoreUpdateEvent>(OnScoreUpdate)
.UnRegisterWhenNodeExitTree(this);
}
private void OnPlayerHealthChange(PlayerHealthChangeEvent e)
{
// 更新 Godot UI 组件
_healthBar.SetValue(e.NewHealth, e.MaxHealth);
// 发送 Godot 信号
EmitSignal(SignalName.PlayerHealthChanged, e.NewHealth, e.MaxHealth);
}
private void OnGamePause(GamePauseEvent e)
{
// 更新 UI 状态
GetTree().Paused = true;
// 发送 Godot 信号
EmitSignal(SignalName.GameStateChanged, "paused");
}
private void OnScoreUpdate(ScoreUpdateEvent e)
{
// 更新分数显示
var scoreLabel = GetNode<Label>("UI/ScoreLabel");
scoreLabel.Text = $"Score: {e.Score}";
// 发送 Godot 信号
EmitSignal(SignalName.ScoreChanged, e.Score);
}
// 定义 Godot 信号
[Signal]
public delegate void PlayerHealthChangedEventHandler(int newHealth, int maxHealth);
[Signal]
public delegate void GameStateChangedEventHandler(string newState);
[Signal]
public delegate void ScoreChangedEventHandler(int score);
}资源管理优化
1. 智能资源加载
实现基于优先级的资源加载策略:
csharp
[ContextAware]
[Log]
public partial class SmartResourceLoader : Node, IController
{
private ResourceLoadUtility _resourceLoader;
private Dictionary<string, Resource> _resourceCache = new();
private Queue<ResourceLoadRequest> _loadQueue = new();
private bool _isLoading = false;
public override void _Ready()
{
_resourceLoader = new ResourceLoadUtility();
PreloadEssentialResources();
}
private void PreloadEssentialResources()
{
Logger.Info("Preloading essential resources");
var essentialResources = new[]
{
"res://assets/textures/player.png",
"res://assets/textures/enemy.png",
"res://assets/audio/shoot.wav",
"res://assets/scenes/player.tscn"
};
foreach (var path in essentialResources)
{
LoadResourceAsync(path, ResourcePriority.Essential);
}
}
public void LoadResourceAsync(string path, ResourcePriority priority = ResourcePriority.Normal)
{
if (_resourceCache.ContainsKey(path))
{
Logger.Debug($"Resource already loaded: {path}");
return;
}
var request = new ResourceLoadRequest
{
Path = path,
Priority = priority,
RequestTime = DateTime.UtcNow
};
_loadQueue.Enqueue(request);
ProcessLoadQueue();
}
private async void ProcessLoadQueue()
{
if (_isLoading || _loadQueue.Count == 0) return;
_isLoading = true;
while (_loadQueue.Count > 0)
{
var request = _loadQueue.Dequeue();
try
{
Logger.Debug($"Loading resource: {request.Path}");
// 使用 Godot 的异步资源加载
var resource = await LoadResourceAsync(request.Path);
if (resource != null)
{
_resourceCache[request.Path] = resource;
Logger.Info($"Resource loaded: {request.Path}");
// 发送资源加载完成事件
Context.SendEvent(new ResourceLoadedEvent {
Path = request.Path,
Resource = resource
});
}
}
catch (Exception ex)
{
Logger.Error($"Failed to load resource {request.Path}: {ex.Message}");
Context.SendEvent(new ResourceLoadFailedEvent {
Path = request.Path,
Error = ex.Message
});
}
}
_isLoading = false;
}
private async Task<Resource> LoadResourceAsync(string path)
{
// 使用 GD.LoadAsync 进行异步加载
var result = GD.LoadAsync(path);
// 等待加载完成
while (!result.IsCompleted)
{
await Task.Delay(10);
}
return result.Result;
}
public T GetResource<T>(string path) where T : Resource
{
if (_resourceCache.TryGetValue(path, out var resource))
{
return resource as T;
}
// 同步加载资源(作为后备)
return GD.Load<T>(path);
}
public void PreloadLevelResources(string levelName)
{
Logger.Info($"Preloading level resources: {levelName}");
var levelConfig = GD.Load<LevelConfig>($"res://assets/levels/{levelName}.json");
foreach (var assetPath in levelConfig.AssetPaths)
{
LoadResourceAsync(assetPath, ResourcePriority.Level);
}
}
public void UnloadUnusedResources()
{
Logger.Info("Unloading unused resources");
var unusedResources = new List<string>();
foreach (var kvp in _resourceCache)
{
if (kvp.Value.GetReferenceCount() <= 1) // 只有缓存引用
{
unusedResources.Add(kvp.Key);
}
}
foreach (var path in unusedResources)
{
_resourceCache.Remove(path);
Logger.Debug($"Unloaded resource: {path}");
}
}
}
public enum ResourcePriority
{
Essential, // 必需资源,立即加载
High, // 高优先级
Normal, // 普通优先级
Low, // 低优先级
Background // 后台加载
}
public class ResourceLoadRequest
{
public string Path { get; set; }
public ResourcePriority Priority { get; set; }
public DateTime RequestTime { get; set; }
}2. 资源预加载策略
实现基于场景的资源预加载:
csharp
[ContextAware]
[Log]
public partial class SceneResourcePreloader : Node, IController
{
private Dictionary<string, SceneResourceSet> _sceneResources = new();
public override void _Ready()
{
InitializeSceneResources();
}
private void InitializeSceneResources()
{
// 定义各场景需要的资源
_sceneResources["MainMenu"] = new SceneResourceSet
{
ScenePath = "res://assets/scenes/MainMenu.tscn",
RequiredAssets = new[]
{
"res://assets/textures/title.png",
"res://assets/audio/menu_music.ogg",
"res://assets/fonts/main_font.ttf"
}
};
_sceneResources["GameLevel"] = new SceneResourceSet
{
ScenePath = "res://assets/scenes/GameLevel.tscn",
RequiredAssets = new[]
{
"res://assets/textures/player.png",
"res://assets/textures/enemy.png",
"res://assets/textures/bullet.png",
"res://assets/audio/shoot.wav",
"res://assets/audio/explosion.wav",
"res://assets/audio/background_music.ogg"
}
};
}
public void PreloadSceneResources(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resourceSet))
{
Logger.Warning($"Unknown scene: {sceneName}");
return;
}
Logger.Info($"Preloading resources for scene: {sceneName}");
foreach (var assetPath in resourceSet.RequiredAssets)
{
Context.SendEvent(new ResourceLoadRequestEvent {
Path = assetPath,
Priority = ResourcePriority.High
});
}
}
public async Task<Control> LoadSceneAsync(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resourceSet))
{
throw new ArgumentException($"Unknown scene: {sceneName}");
}
Logger.Info($"Loading scene: {sceneName}");
// 预加载场景资源
PreloadSceneResources(sceneName);
// 等待资源加载完成
await WaitForResources(resourceSet.RequiredAssets);
// 加载场景
var scene = GD.Load<PackedScene>(resourceSet.ScenePath);
return scene.Instantiate<Control>();
}
private async Task WaitForResources(string[] assetPaths)
{
// 这里应该与资源加载系统集成
// 简化实现,等待固定时间
await Task.Delay(1000);
}
}
public class SceneResourceSet
{
public string ScenePath { get; set; }
public string[] RequiredAssets { get; set; }
}性能优化技巧
1. 节点性能优化
csharp
[ContextAware]
[Log]
public partial class PerformanceOptimizedController : Node, IController
{
private Timer _updateTimer;
private bool _isPerformanceCritical = false;
private float _updateInterval = 0.016f; // 60 FPS
public override void _Ready()
{
SetupPerformanceOptimization();
}
private void SetupPerformanceOptimization()
{
// 使用计时器控制更新频率
_updateTimer = new Timer();
_updateTimer.WaitTime = _updateInterval;
_updateTimer.Timeout += OnTimedUpdate;
AddChild(_updateTimer);
_updateTimer.Start();
// 监听性能事件
this.RegisterEvent<PerformanceModeChangeEvent>(OnPerformanceModeChange)
.UnRegisterWhenNodeExitTree(this);
}
private void OnPerformanceModeChange(PerformanceModeChangeEvent e)
{
_isPerformanceCritical = e.IsCritical;
// 根据性能模式调整更新频率
_updateInterval = _isPerformanceCritical ? 0.033f : 0.016f; // 30 FPS vs 60 FPS
_updateTimer.WaitTime = _updateInterval;
}
private void OnTimedUpdate()
{
if (!_isPerformanceCritical)
{
// 正常更新所有功能
UpdateFull();
}
else
{
// 性能关键时只更新核心功能
UpdateCriticalOnly();
}
}
private void UpdateFull()
{
// 完整更新逻辑
UpdatePlayer();
UpdateEnemies();
UpdateUI();
UpdateParticles();
UpdateAudio();
}
private void UpdateCriticalOnly()
{
// 只更新关键功能
UpdatePlayer();
UpdateBasicUI();
}
public override void _Process(double delta)
{
// 禁用默认的 _Process,使用计时器控制
// 这可以避免不必要的更新调用
}
}2. 内存管理优化
csharp
[ContextAware]
[Log]
public partial class MemoryOptimizedController : Node, IController
{
private ObjectPool<Effect> _effectPool;
private Dictionary<string, Texture2D> _textureAtlas;
private int _frameCount = 0;
public override void _Ready()
{
InitializeMemoryOptimization();
}
private void InitializeMemoryOptimization()
{
// 创建对象池
_effectPool = new ObjectPool<Effect>(
createFunc: () => new Effect(),
actionOnGet: effect => effect.Reset(),
actionOnRelease: effect => effect.Cleanup(),
collectionCheck: true
);
// 创建纹理图集
CreateTextureAtlas();
// 设置定期清理
var cleanupTimer = new Timer();
cleanupTimer.WaitTime = 60.0f; // 每分钟清理一次
cleanupTimer.Timeout += PerformMemoryCleanup;
AddChild(cleanupTimer);
cleanupTimer.Start();
}
private void CreateTextureAtlas()
{
_textureAtlas = new Dictionary<string, Texture2D>();
// 预加载常用纹理到图集
var textures = new[]
{
"player", "enemy", "bullet", "explosion", "powerup"
};
foreach (var textureName in textures)
{
var texture = GD.Load<Texture2D>($"res://assets/textures/{textureName}.png");
_textureAtlas[textureName] = texture;
}
}
public void SpawnEffect(Vector2 position, string effectType)
{
var effect = _effectPool.Get();
effect.Initialize(position, effectType, _textureAtlas[effectType]);
AddChild(effect);
// 自动回收效果
effect.StartLifetime(2.0f, () => {
RemoveChild(effect);
_effectPool.Release(effect);
});
}
private void PerformMemoryCleanup()
{
Logger.Info("Performing memory cleanup");
// 清理未使用的资源
ResourceLoader.UnloadUnused();
// 强制垃圾回收
GC.Collect();
GC.WaitForPendingFinalizers();
// 记录内存使用情况
var memoryUsage = GC.GetTotalMemory(false);
Logger.Debug($"Memory usage after cleanup: {memoryUsage / 1024 / 1024} MB");
}
public override void _Process(double delta)
{
_frameCount++;
// 每100帧检查一次内存使用
if (_frameCount % 100 == 0)
{
CheckMemoryUsage();
}
}
private void CheckMemoryUsage()
{
var memoryUsage = GC.GetTotalMemory(false);
var threshold = 100 * 1024 * 1024; // 100 MB
if (memoryUsage > threshold)
{
Logger.Warning($"High memory usage detected: {memoryUsage / 1024 / 1024} MB");
// 触发内存清理
Context.SendEvent(new HighMemoryUsageEvent {
CurrentUsage = memoryUsage,
Threshold = threshold
});
}
}
}
public class Effect : Node2D
{
private Sprite2D _sprite;
private float _lifetime;
private Action _onComplete;
public void Initialize(Vector2 position, string type, Texture2D texture)
{
Position = position;
_sprite = new Sprite2D();
_sprite.Texture = texture;
AddChild(_sprite);
}
public void StartLifetime(float duration, Action onComplete)
{
_lifetime = duration;
_onComplete = onComplete;
}
public override void _Process(double delta)
{
_lifetime -= (float)delta;
if (_lifetime <= 0)
{
_onComplete?.Invoke();
}
}
public void Reset()
{
Visible = true;
Modulate = Colors.White;
_lifetime = 0;
_onComplete = null;
}
public void Cleanup()
{
_sprite?.QueueFree();
}
}常见集成模式
1. 单例模式集成
csharp
[ContextAware]
[Log]
public partial class GameManager : Node, IController
{
private static GameManager _instance;
public static GameManager Instance
{
get
{
if (_instance == null)
{
throw new InvalidOperationException("GameManager not initialized");
}
return _instance;
}
}
public override void _Ready()
{
if (_instance != null)
{
QueueFree();
return;
}
_instance = this;
// 设置为不会被自动删除
ProcessMode = ProcessModeEnum.Always;
Logger.Info("GameManager initialized as singleton");
}
public override void _ExitTree()
{
if (_instance == this)
{
_instance = null;
}
}
}2. 状态机集成
csharp
[ContextAware]
[Log]
public partial class StateMachineController : Node, IController
{
private IGameState _currentState;
private Dictionary<Type, IGameState> _states = new();
public override void _Ready()
{
InitializeStates();
ChangeState<MenuState>();
}
private void InitializeStates()
{
_states[typeof(MenuState)] = new MenuState();
_states[typeof(PlayingState)] = new PlayingState();
_states[typeof(PausedState)] = new PausedState();
_states[typeof(GameOverState)] = new GameOverState();
}
public void ChangeState<T>() where T : IGameState
{
var newState = _states[typeof(T)];
if (newState == _currentState) return;
_currentState?.Exit(this);
_currentState = newState;
_currentState.Enter(this);
Logger.Info($"State changed to: {typeof(T).Name}");
}
public override void _Process(double delta)
{
_currentState?.Update(this, delta);
}
public override void _Input(InputEvent @event)
{
_currentState?.HandleInput(this, @event);
}
}
public interface IGameState
{
void Enter(StateMachineController controller);
void Update(StateMachineController controller, double delta);
void HandleInput(StateMachineController controller, InputEvent @event);
void Exit(StateMachineController controller);
}
public class MenuState : IGameState
{
public void Enter(StateMachineController controller)
{
// 显示主菜单
controller.GetTree().CallDeferred(SceneTree.MethodName.ChangeSceneToFile,
"res://assets/scenes/MainMenu.tscn");
}
public void Update(StateMachineController controller, double delta) { }
public void HandleInput(StateMachineController controller, InputEvent @event)
{
if (@event.IsActionPressed("start_game"))
{
controller.ChangeState<PlayingState>();
}
}
public void Exit(StateMachineController controller) { }
}
public class PlayingState : IGameState
{
public void Enter(StateMachineController controller)
{
controller.GetTree().Paused = false;
controller.Context.SendEvent(new GameStartEvent());
}
public void Update(StateMachineController controller, double delta) { }
public void HandleInput(StateMachineController controller, InputEvent @event)
{
if (@event.IsActionPressed("pause"))
{
controller.ChangeState<PausedState>();
}
}
public void Exit(StateMachineController controller)
{
controller.Context.SendEvent(new GamePauseEvent());
}
}调试与测试
1. Godot 集成测试
csharp
using NUnit.Framework;
using Godot;
using GFramework.Godot.Extensions;
[TestFixture]
public class GodotIntegrationTests
{
private SceneTree _sceneTree;
private TestScene _testScene;
[SetUp]
public void Setup()
{
// 创建测试场景树
_sceneTree = new SceneTree();
_sceneTree.Root = new Node();
// 创建测试场景
_testScene = new TestScene();
_sceneTree.Root.AddChild(_testScene);
}
[TearDown]
public void TearDown()
{
_testScene?.QueueFree();
_sceneTree?.Quit();
}
[Test]
public void Controller_WithGFrameworkIntegration_ShouldWorkCorrectly()
{
// Act
_testScene._Ready();
_sceneTree.ProcessFrame();
// Assert
Assert.That(_testScene.Context, Is.Not.Null);
Assert.That(_testScene.IsInitialized, Is.True);
}
[Test]
public void Signal_Builder_ShouldConnectCorrectly()
{
// Arrange
_testScene._Ready();
var button = _testScene.GetNode<Button>("TestButton");
// Act
button.EmitSignal(Button.SignalName.Pressed);
_sceneTree.ProcessFrame();
// Assert
Assert.That(_testScene.ButtonPressedCount, Is.EqualTo(1));
}
}
[ContextAware]
[Log]
public partial class TestScene : Node2D
{
public bool IsInitialized { get; private set; }
public int ButtonPressedCount { get; private set; }
public override void _Ready()
{
SetContext(new TestContext());
var button = new Button();
button.Name = "TestButton";
AddChild(button);
this.CreateSignalBuilder(button.SignalName.Pressed)
.Connect(() => ButtonPressedCount++)
.UnRegisterWhenNodeExitTree(this);
IsInitialized = true;
}
}
public class TestContext : IContext
{
// 简化的测试上下文实现
}2. 性能基准测试
csharp
[TestFixture]
public class GodotPerformanceTests
{
[Test]
public void NodeExtensions_Performance_Comparison()
{
var node = new Node();
var iterations = 10000;
// 测试扩展方法性能
var stopwatch1 = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
var child = node.GetNodeX<Node>("NonExistent");
}
stopwatch1.Stop();
// 测试原始方法性能
var stopwatch2 = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
try
{
var child = node.GetNode<Node>("NonExistent");
}
catch
{
// 忽略异常
}
}
stopwatch2.Stop();
GD.Print($"Extension method: {stopwatch1.ElapsedMilliseconds}ms");
GD.Print($"Original method: {stopwatch2.ElapsedMilliseconds}ms");
// 扩展方法应该更快或相等
Assert.That(stopwatch1.ElapsedMilliseconds, Is.LessThanOrEqualTo(stopwatch2.ElapsedMilliseconds));
}
}总结
通过本教程,你已经学会了:
- ✅ 深度集成 - GFramework 与 Godot 的完美结合
- ✅ 生命周期管理 - 自动化的节点生命周期控制
- ✅ 信号系统 - 类型安全的信号连接和桥接
- ✅ 性能优化 - 内存管理和运行时优化技巧
- ✅ 调试测试 - 完整的测试和调试方案
这些技术将帮助你创建高性能、可维护的 Godot 游戏。
教程版本: 1.0.0
更新日期: 2026-01-12