feat(GFramework.Godot): 引入 Godot 输入模块与架构锚点重构

新增 GodotInputModule 和相关输入事件类型,实现 Godot 输入系统与游戏框架的桥接。
重构架构锚点类名及其引用,统一使用 GFrameworkConstants 中定义的框架名称常量。
添加 AbstractGodotModule 基类以规范模块行为,并完善输入事件记录类定义。
This commit is contained in:
GwWuYou 2025-12-21 16:13:16 +08:00
parent bb403bd454
commit 12e257d16f
11 changed files with 253 additions and 7 deletions

View File

@ -0,0 +1,12 @@
namespace GFramework.Core.constants;
/// <summary>
/// GFramework框架常量定义类
/// </summary>
public static class GFrameworkConstants
{
/// <summary>
/// 框架名称常量
/// </summary>
public const string FrameworkName = "GFramework";
}

View File

@ -0,0 +1,33 @@
using System.Numerics;
namespace GFramework.Game.input;
public static class InputEvents
{
/// <summary>
/// 按键输入事件
/// </summary>
/// <param name="Action">按键操作名称</param>
/// <param name="Pressed">按键是否被按下true表示按下false表示释放</param>
/// <param name="Echo">是否为回显事件,用于处理按键重复触发</param>
public sealed record KeyInputEvent(
string Action,
bool Pressed,
bool Echo
) : IGameInputEvent;
/// <summary>
/// 指针/鼠标输入事件
/// </summary>
/// <typeparam name="TVector2">二维向量类型</typeparam>
/// <param name="Position">指针当前位置坐标</param>
/// <param name="Delta">指针位置变化量</param>
/// <param name="Button">鼠标按键编号0表示左键1表示右键2表示中键</param>
/// <param name="Pressed">按键是否被按下true表示按下false表示释放</param>
public sealed record PointerInputEvent<TVector2>(
TVector2 Position,
TVector2 Delta,
int Button,
bool Pressed
) : IGameInputEvent where TVector2 : struct;
}

View File

@ -1,4 +1,5 @@
using GFramework.Core.architecture;
using GFramework.Core.constants;
using GFramework.Godot.extensions;
using Godot;
@ -16,7 +17,7 @@ public abstract class AbstractArchitecture<T> : Architecture<T> where T : Archit
/// 架构锚点节点的唯一标识名称
/// 用于在Godot场景树中创建和查找架构锚点节点
/// </summary>
private const string ArchitectureName = "__GFramework__Architecture__Anchor";
private const string ArchitectureAnchorName = $"__${GFrameworkConstants.FrameworkName}__ArchitectureAnchor__";
/// <summary>
/// 存储所有已安装的Godot架构扩展组件列表
@ -28,7 +29,7 @@ public abstract class AbstractArchitecture<T> : Architecture<T> where T : Archit
/// 架构锚点节点引用
/// 用于将架构绑定到Godot生命周期并作为扩展节点的父节点
/// </summary>
private ArchitectureAnchorNode? _anchor;
private ArchitectureAnchor? _anchor;
/// <summary>
/// 获取架构根节点。如果尚未初始化或已被销毁,则抛出异常。
@ -69,12 +70,12 @@ public abstract class AbstractArchitecture<T> : Architecture<T> where T : Archit
return;
// 防止重复挂载(热重载 / 多次 Init
if (tree.Root.GetNodeOrNull(ArchitectureName) != null)
if (tree.Root.GetNodeOrNull(ArchitectureAnchorName) != null)
return;
_anchor = new ArchitectureAnchorNode
_anchor = new ArchitectureAnchor
{
Name = ArchitectureName
Name = ArchitectureAnchorName
};
_anchor.Bind(Destroy);

View File

@ -0,0 +1,58 @@
using GFramework.Core.architecture;
using Godot;
namespace GFramework.Godot.architecture;
/// <summary>
/// 抽象的Godot模块基类用于定义Godot框架中的模块行为
/// </summary>
/// <typeparam name="T">架构类型必须继承自Architecture并且有无参构造函数</typeparam>
public abstract class AbstractGodotModule<T>: IGodotModule<T> where T : Architecture<T>, new()
{
/// <summary>
/// 当架构阶段发生变化时调用此方法
/// </summary>
/// <param name="phase">当前的架构阶段</param>
/// <param name="arch">架构实例</param>
public virtual void OnPhase(ArchitecturePhase phase, IArchitecture arch)
{
}
/// <summary>
/// 当架构阶段发生变化时调用此方法
/// </summary>
/// <param name="phase">当前的架构阶段</param>
public virtual void OnArchitecturePhase(ArchitecturePhase phase)
{
}
/// <summary>
/// 安装模块到指定架构中
/// </summary>
/// <param name="architecture">要安装到的架构实例</param>
public abstract void Install(IArchitecture architecture);
/// <summary>
/// 获取模块关联的Godot节点
/// </summary>
public abstract Node Node { get; }
/// <summary>
/// 当模块被附加到架构时调用此方法
/// </summary>
/// <param name="architecture">被附加到的架构实例</param>
public virtual void OnAttach(Architecture<T> architecture)
{
}
/// <summary>
/// 当模块从架构中分离时调用此方法
/// </summary>
public virtual void OnDetach()
{
}
}

View File

@ -6,7 +6,7 @@ namespace GFramework.Godot.architecture;
/// 架构锚点节点类用于在Godot场景树中作为架构组件的根节点
/// 该类提供了退出时的回调绑定功能,可以在节点从场景树中移除时执行清理操作
/// </summary>
public partial class ArchitectureAnchorNode : Node
public partial class ArchitectureAnchor : Node
{
private Action? _onExit;
/// <summary>
@ -18,7 +18,7 @@ public partial class ArchitectureAnchorNode : Node
if (_onExit != null)
{
GD.PushWarning(
$"{nameof(ArchitectureAnchorNode)} already bound. Rebinding will override previous callback.");
$"{nameof(ArchitectureAnchor)} already bound. Rebinding will override previous callback.");
}
_onExit = onExit;
}

View File

@ -0,0 +1,79 @@
using GFramework.Game.input;
using Godot;
namespace GFramework.Godot.input;
/// <summary>
/// Godot输入桥接类用于将Godot的输入事件转换为游戏框架的输入事件
/// </summary>
public partial class GodotInputBridge : Node
{
private InputSystem _inputSystem = null!;
/// <summary>
/// 绑定输入系统
/// </summary>
/// <param name="inputSystem">要绑定的输入系统实例</param>
public void Bind(InputSystem inputSystem)
{
_inputSystem = inputSystem;
}
/// <summary>
/// 处理输入事件的回调方法
/// </summary>
/// <param name="event">Godot输入事件</param>
public override void _Input(InputEvent @event)
{
var gameEvent = Translate(@event);
if (gameEvent == null)
{
return;
}
_inputSystem.Handle(gameEvent);
GetViewport().SetInputAsHandled();
}
/// <summary>
/// 将Godot输入事件翻译为游戏框架输入事件
/// </summary>
/// <param name="evt">Godot输入事件</param>
/// <returns>翻译后的游戏输入事件如果无法翻译则返回null</returns>
private static IGameInputEvent? Translate(InputEvent evt)
{
// 处理动作输入事件
if (evt is InputEventAction action)
{
return new InputEvents.KeyInputEvent(
action.Action,
action.Pressed,
false
);
}
// 鼠标按钮
if (evt is InputEventMouseButton mb)
{
return new InputEvents.PointerInputEvent<Vector2>(
mb.Position,
Vector2.Zero,
(int)mb.ButtonIndex,
mb.Pressed
);
}
// 鼠标移动
if (evt is InputEventMouseMotion mm)
{
return new InputEvents.PointerInputEvent<Vector2>(
mm.Position,
mm.Relative,
0,
false
);
}
return null;
}
}

View File

@ -0,0 +1,61 @@
using GFramework.Core.architecture;
using GFramework.Core.constants;
using GFramework.Game.input;
using GFramework.Godot.architecture;
using GFramework.Godot.extensions;
using Godot;
namespace GFramework.Godot.input;
/// <summary>
/// Godot输入模块类用于管理Godot游戏引擎中的输入系统
/// </summary>
/// <typeparam name="T">架构类型必须继承自Architecture且具有无参构造函数</typeparam>
public sealed class GodotInputModule<T> : AbstractGodotModule<T>
where T : Architecture<T>, new()
{
private GodotInputBridge? _node;
/// <summary>
/// 架构锚点节点的唯一标识名称
/// 用于在Godot场景树中创建和查找架构锚点节点
/// </summary>
private const string GodotInputBridgeName = $"__${GFrameworkConstants.FrameworkName}__GodotInputBridge__";
/// <summary>
/// 获取模块对应的节点对象
/// </summary>
/// <exception cref="InvalidOperationException">当节点尚未创建时抛出异常</exception>
public override Node Node => _node
?? throw new InvalidOperationException("Node not created yet");
private InputSystem _inputSystem = null!;
/// <summary>
/// 当模块被附加到架构时调用此方法
/// </summary>
/// <param name="architecture">要附加到的架构实例</param>
public override void OnAttach(Architecture<T> architecture)
{
// 创建Godot输入桥接节点并绑定输入系统
_node = new GodotInputBridge { Name = GodotInputBridgeName};
_node.Bind(_inputSystem);
}
/// <summary>
/// 当模块从架构中分离时调用此方法
/// </summary>
public override void OnDetach()
{
// 释放节点资源并清理引用
Node.QueueFreeX();
_node = null;
}
/// <summary>
/// 安装模块时调用此方法,用于获取所需的系统组件
/// </summary>
/// <param name="architecture">当前架构实例</param>
public override void Install(IArchitecture architecture)
{
// 从架构中获取输入系统实例
_inputSystem = architecture.GetSystem<InputSystem>()!;
}
}

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInputEventAction_002Ecs_002Fl_003AD_0021_003FTool_003FDevelopment_0020Tools_003FJetBrains_003F_002EJetBrains_003F_002ERider_003Fconfig_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1c378f459c054fecaf4484a0fa6d44c055a800_003F18_003F33b52a1c_003FInputEventAction_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>