diff --git a/GFramework.Game/GFramework.Game.csproj b/GFramework.Game/GFramework.Game.csproj new file mode 100644 index 0000000..6bbf9c1 --- /dev/null +++ b/GFramework.Game/GFramework.Game.csproj @@ -0,0 +1,14 @@ + + + + GeWuYou.GFramework.Game + net8.0;net9.0;net10.0 + enable + enable + + + + + + + diff --git a/GFramework.Godot/GFramework.Godot.csproj b/GFramework.Godot/GFramework.Godot.csproj new file mode 100644 index 0000000..5c76886 --- /dev/null +++ b/GFramework.Godot/GFramework.Godot.csproj @@ -0,0 +1,25 @@ + + + + GeWuYou.GFramework.Godot + GeWuYou.GFramework.Godot + enable + enable + net8.0;net9.0;net10.0 + + + + + + + + + + + + + + + + + diff --git a/GFramework.Godot/architecture/AbstractArchitecture.cs b/GFramework.Godot/architecture/AbstractArchitecture.cs new file mode 100644 index 0000000..80bfc21 --- /dev/null +++ b/GFramework.Godot/architecture/AbstractArchitecture.cs @@ -0,0 +1,62 @@ +using GFramework.Core.architecture; +using Godot; + +namespace GFramework.Godot.architecture; + +/// +/// 抽象架构类,为特定类型的架构提供基础实现框架 +/// +/// 架构的具体类型,必须继承自Architecture且能被实例化 +public abstract class AbstractArchitecture : Architecture where T : Architecture, new() +{ + private const string ArchitectureName = "__GFrameworkArchitectureAnchor"; + /// + /// 初始化架构,按顺序注册模型、系统和工具 + /// + protected override void Init() + { + RegisterModels(); + RegisterSystems(); + RegisterUtilities(); + AttachToGodotLifecycle(); + } + + /// + /// 将架构绑定到Godot生命周期中,确保在场景树销毁时能够正确清理资源 + /// 通过创建一个锚节点来监听场景树的销毁事件 + /// + private void AttachToGodotLifecycle() + { + if (Engine.GetMainLoop() is not SceneTree tree) + return; + + // 防止重复挂载(热重载 / 多次 Init) + if (tree.Root.GetNodeOrNull(ArchitectureName) != null) + return; + + var anchor = new ArchitectureAnchorNode + { + Name = ArchitectureName + }; + + anchor.Bind(Destroy); + + tree.Root.CallDeferred(Node.MethodName.AddChild, anchor); + } + + /// + /// 注册工具抽象方法,由子类实现具体的工具注册逻辑 + /// + protected abstract void RegisterUtilities(); + + /// + /// 注册系统抽象方法,由子类实现具体系统注册逻辑 + /// + protected abstract void RegisterSystems(); + + /// + /// 注册模型抽象方法,由子类实现具体模型注册逻辑 + /// + protected abstract void RegisterModels(); +} + diff --git a/GFramework.Godot/architecture/ArchitectureAnchorNode.cs b/GFramework.Godot/architecture/ArchitectureAnchorNode.cs new file mode 100644 index 0000000..0a9037d --- /dev/null +++ b/GFramework.Godot/architecture/ArchitectureAnchorNode.cs @@ -0,0 +1,32 @@ +using Godot; + +namespace GFramework.Godot.architecture; + +/// +/// 架构锚点节点类,用于在Godot场景树中作为架构组件的根节点 +/// 该类提供了退出时的回调绑定功能,可以在节点从场景树中移除时执行清理操作 +/// +public partial class ArchitectureAnchorNode : Node +{ + private Action? _onExit; + + /// + /// 绑定节点退出时的回调动作 + /// + /// 当节点从场景树退出时要执行的动作 + public void Bind(Action onExit) + { + _onExit = onExit; + } + + /// + /// 当节点从场景树中移除时调用此方法 + /// 执行绑定的退出回调并清理引用 + /// + public override void _ExitTree() + { + _onExit?.Invoke(); + _onExit = null; + } +} + diff --git a/GFramework.Godot/component/AbstractDragDrop2DComponentBase.cs b/GFramework.Godot/component/AbstractDragDrop2DComponentBase.cs new file mode 100644 index 0000000..d867eb7 --- /dev/null +++ b/GFramework.Godot/component/AbstractDragDrop2DComponentBase.cs @@ -0,0 +1,100 @@ +using GFramework.Core.architecture; +using GFramework.Core.controller; +using GFramework.Core.events; +using GFramework.Core.extensions; +using Godot; + +namespace GFramework.Godot.component; + +/// +/// 抽象基类,用于实现2D拖拽功能的组件。 +/// 继承自Godot的Node类并实现了IController接口。 +/// 提供了拖拽相关的信号定义以及基础属性配置。 +/// +public abstract partial class AbstractDragDrop2DComponentBase : Node, IController +{ + /// + /// 取消注册列表,用于管理需要在节点销毁时取消注册的对象 + /// + protected readonly IUnRegisterList UnRegisterList = new UnRegisterList(); + + /// + /// 当拖拽被取消时触发的信号。 + /// + /// 拖拽起始位置。 + [Signal] + public delegate void DragCanceledEventHandler(Vector2 startingPosition); + + /// + /// 当拖拽开始时触发的信号。 + /// + [Signal] + public delegate void DragStartedEventHandler(); + + /// + /// 当拖拽结束并放置时触发的信号。 + /// + /// 拖拽起始位置。 + [Signal] + public delegate void DroppedEventHandler(Vector2 startingPosition); + + /// + /// 是否启用拖拽功能。若为 false,则忽略所有输入事件。 + /// + public bool Enable { get; set; } + + /// + /// 拖拽组的名称,用于区分不同的拖拽组。 + /// + public string GroupName { get; set; } = "dragging"; + + /// + /// 获取或设置取消拖拽输入操作的名称 + /// + public string CancelDragInputActionName { get; set; } = "cancel_drag"; + + /// + /// 获取或设置选择输入操作的名称 + /// + public string SelectInputActionName { get; set; } = "select"; + + /// + /// 拖拽时元素的最大Z轴索引值。 + /// + public int ZIndexMax { get; set; } = 99; + + /// + /// 拖拽时元素的最小Z轴索引值。 + /// + public int ZIndexMin { get; set; } = 0; + + /// + /// 获取架构实例。 + /// + /// 返回实现IArchitecture接口的架构实例。 + public abstract IArchitecture GetArchitecture(); + + /// + /// 表示是否正在拖拽操作的标志位。 + /// + protected bool IsDragging; + + /// + /// 表示拖拽操作中的偏移量,用于计算当前位置与起始位置的差值。 + /// + protected Vector2 Offset = Vector2.Zero; + + /// + /// 表示拖拽操作的起始位置坐标。 + /// + protected Vector2 StartingPosition; + + /// + /// 节点退出场景树时的回调方法。 + /// 在节点从场景树移除前调用,用于清理资源。 + /// + public override void _ExitTree() + { + UnRegisterList.UnRegisterAll(); + } +} \ No newline at end of file diff --git a/GFramework.Godot/component/AbstractDragDropArea2DComponent.cs b/GFramework.Godot/component/AbstractDragDropArea2DComponent.cs new file mode 100644 index 0000000..3de75da --- /dev/null +++ b/GFramework.Godot/component/AbstractDragDropArea2DComponent.cs @@ -0,0 +1,127 @@ +using GFramework.Godot.extensions; +using Godot; + +namespace GFramework.Godot.component; + +/// +/// 抽象拖拽组件类,用于处理节点的拖放逻辑。 +/// 实现了 IController 接口以支持架构通信,并通过信号通知拖拽事件的发生。 +/// +public abstract partial class AbstractDragDropArea2DComponent : AbstractDragDrop2DComponentBase +{ + /// + /// 目标区域,通常是可交互的游戏对象(如单位或物品)所在的碰撞区域。 + /// + public required Area2D Target { get; set; } + + /// + /// 节点准备就绪时的回调方法。 + /// 在节点添加到场景树后调用,绑定目标区域的输入事件处理器。 + /// + public override void _Ready() + { + Target = GetParent() as Area2D ?? throw new InvalidOperationException("Target must be an Area2D node."); + Target.InputEvent += OnTargetInputEvent; + } + + /// + /// 处理输入事件的回调方法。 + /// 根据当前拖拽状态和输入事件类型,执行相应的拖拽操作。 + /// + /// 输入事件对象 + public override void _Input(InputEvent @event) + { + switch (IsDragging) + { + // 处理取消拖拽操作:当正在拖拽且按下取消拖拽按键时,执行取消拖拽逻辑 + case true when Target.IsValidNode() && @event.IsActionPressed(CancelDragInputActionName): + CancelDragging(); + // 设置输入为处理,防止输入穿透 + this.SetInputAsHandled(); + break; + case true when @event.IsActionReleased(SelectInputActionName): + Drop(); + break; + } + } + + /// + /// 目标区域的输入事件处理器。 + /// 当目标区域接收到输入事件时被调用,用于控制拖拽的开始和结束。 + /// + /// 事件发生的视口节点 + /// 输入事件对象 + /// 事件触点ID(未使用) + private void OnTargetInputEvent(Node viewport, InputEvent @event, long _) + { + if (!Enable) return; + + // 获取当前正在拖拽的对象 + var draggingObj = GetTree().GetFirstNodeInGroup(GroupName); + switch (IsDragging) + { + // 处理开始拖拽操作:当未在拖拽状态且按下选择按键时,开始拖拽 + case false when + // 如果当前没有拖拽操作且已有其他对象正在拖拽,则直接返回 + draggingObj is not null: + return; + case false when @event.IsActionPressed(SelectInputActionName): + StartDragging(); + break; + } + } + + /// + /// 每帧更新逻辑,在拖拽过程中持续更新目标的位置。 + /// + /// 与上一帧的时间间隔(秒)。 + public override void _Process(double delta) + { + if (IsDragging && Target.IsValidNode()) Target.GlobalPosition = Target.GetGlobalMousePosition() + Offset; + } + + /// + /// 结束拖拽流程的基础方法。 + /// 清除拖拽标志位并将目标从拖拽组中移除,恢复其层级顺序。 + /// + private void EndDragging() + { + IsDragging = false; + Target.RemoveFromGroup(GroupName); + Target.ZIndex = ZIndexMin; + } + + /// + /// 执行取消拖拽的操作。 + /// 调用 EndDragging 并发出 DragCanceled 信号。 + /// + private void CancelDragging() + { + EndDragging(); + EmitSignalDragCanceled(StartingPosition); + } + + /// + /// 开始拖拽操作。 + /// 设置初始位置和偏移量,将目标加入拖拽组并提升显示层级,最后发出 DragStarted 信号。 + /// + private void StartDragging() + { + IsDragging = true; + StartingPosition = Target.GlobalPosition; + Target.AddToGroup(GroupName); + Target.ZIndex = ZIndexMax; + Offset = Target.GlobalPosition - Target.GetGlobalMousePosition(); + EmitSignalDragStarted(); + } + + /// + /// 完成一次拖拽操作。 + /// 调用 EndDragging 方法并发出 Dropped 信号。 + /// + private void Drop() + { + EndDragging(); + EmitSignalDropped(StartingPosition); + } +} \ No newline at end of file diff --git a/GFramework.Godot/extensions/NodeExtensions.cs b/GFramework.Godot/extensions/NodeExtensions.cs new file mode 100644 index 0000000..c316b5d --- /dev/null +++ b/GFramework.Godot/extensions/NodeExtensions.cs @@ -0,0 +1,243 @@ +using Godot; + +namespace GFramework.Godot.extensions; + +/// +/// 节点扩展方法类,提供对Godot节点的扩展功能 +/// +public static class NodeExtensions +{ + /// + /// 安全地将节点加入删除队列,在下一帧开始时释放节点资源 + /// + /// 要释放的节点实例 + public static void QueueFreeX(this Node? node) + { + // 检查节点是否为空 + if (node is null) return; + + // 检查节点实例是否有效 + if (!GodotObject.IsInstanceValid(node)) return; + + // 检查节点是否已经加入删除队列 + if (node.IsQueuedForDeletion()) return; + + // 延迟调用QueueFree方法,避免在当前帧中直接删除节点 + node.CallDeferred(Node.MethodName.QueueFree); + } + + /// + /// 立即释放节点资源,不等待下一帧 + /// + /// 要立即释放的节点实例 + public static void FreeX(this Node? node) + { + // 检查节点是否为空 + if (node is null) return; + + // 检查节点实例是否有效 + if (!GodotObject.IsInstanceValid(node)) return; + + // 检查节点是否已经加入删除队列 + if (node.IsQueuedForDeletion()) return; + + // 立即释放节点资源 + node.Free(); + } + + /// + /// 如果节点尚未进入场景树,则等待 ready 信号。 + /// 如果已经在场景树中,则立刻返回。 + /// + public static async Task WaitUntilReady(this Node node) + { + if (!node.IsInsideTree()) await node.ToSignal(node, Node.SignalName.Ready); + } + + /// + /// 检查节点是否有效: + /// 1. 非 null + /// 2. Godot 实例仍然存在(未被释放) + /// 3. 已经加入 SceneTree + /// + public static bool IsValidNode(this Node? node) + { + return node is not null && + GodotObject.IsInstanceValid(node) && + node.IsInsideTree(); + } + + /// + /// 检查节点是否无效: + /// 1. 为 null,或者 + /// 2. Godot 实例已被释放,或者 + /// 3. 尚未加入 SceneTree + /// 返回 true 表示该节点不可用。 + /// + public static bool IsInvalidNode(this Node? node) + { + return node is null || + !GodotObject.IsInstanceValid(node) || + !node.IsInsideTree(); + } + + /// + /// 将当前节点的输入事件标记为已处理,防止事件继续向父节点传播。 + /// + /// 要处理输入事件的节点实例 + public static void SetInputAsHandled(this Node node) + { + // 获取节点的视口并标记输入事件为已处理 + node.GetViewport().SetInputAsHandled(); + } + + /// + /// 设置节点所在场景树的暂停状态 + /// + /// 要操作的节点对象 + /// 暂停状态标识,默认为true表示暂停,false表示恢复运行 + public static void Paused(this Node node, bool paused = true) + { + var tree = node.GetTree(); + tree.Paused = paused; + } + + /// + /// 查找指定名称的子节点并将其转换为指定类型 + /// + /// 要转换到的目标节点类型 + /// 要在其子节点中进行查找的父节点 + /// 要查找的子节点名称 + /// 是否递归查找所有层级的子节点,默认为true + /// 找到的子节点转换为指定类型后的结果,如果未找到或转换失败则返回null + public static T? FindChildX(this Node node, string name, bool recursive = true) + where T : Node + { + var child = node.FindChild(name, recursive, false); + return child as T; + } + + /// + /// 获取指定路径的节点,如果不存在则创建一个新的节点 + /// + /// 节点类型,必须继承自Node且具有无参构造函数 + /// 父节点 + /// 节点路径 + /// 找到的现有节点或新创建的节点 + public static T GetOrCreateNode(this Node node, string path) + where T : Node, new() + { + // 尝试获取现有节点 + if (node.GetNodeOrNull(path) is { } found) + return found; + + // 创建新节点并添加到父节点 + var created = new T(); + node.AddChild(created); + created.Name = path; + return created; + } + + /// + /// 异步添加子节点并等待其准备就绪 + /// + /// 父节点 + /// 要添加的子节点 + /// 异步任务 + public static async Task AddChildX(this Node parent, Node child) + { + parent.AddChild(child); + await child.WaitUntilReady(); + } + + /// + /// 获取父节点并将其转换为指定类型 + /// + /// 要转换到的目标节点类型 + /// 当前节点 + /// 父节点转换为指定类型后的结果,如果转换失败则返回null + public static T? GetParentX(this Node node) where T : Node + { + return node.GetParent() as T; + } + + /// + /// 获取场景树的根节点的第一个子节点 + /// + /// 扩展方法的目标节点 + /// 根节点的第一个子节点 + public static Node GetRootNodeX(this Node node) + { + return node.GetTree().Root.GetChild(0); + } + + /// + /// 遍历节点的所有子节点,并对指定类型的子节点执行特定操作 + /// + /// 要筛选的节点类型 + /// 扩展方法的目标节点 + /// 对符合条件的子节点执行的操作 + public static void ForEachChild(this Node node, Action action) where T : Node + { + foreach (var child in node.GetChildren()) + if (child is T t) + action(t); + } + + /// + /// 禁用节点所在场景树的输入处理功能 + /// + /// 扩展方法的目标节点 + public static void DisableInput(this Node node) + { + // 检查根节点是否为Viewport类型,如果是则禁用GUI输入 + if (node.GetTree().Root is Viewport vp) + vp.GuiDisableInput = true; + } + + /// + /// 启用节点所在场景树的输入处理功能 + /// + /// 扩展方法的目标节点 + public static void EnableInput(this Node node) + { + // 检查根节点是否为Viewport类型,如果是则启用GUI输入 + if (node.GetTree().Root is Viewport vp) + vp.GuiDisableInput = false; + } + + /// + /// 打印节点的路径信息到控制台 + /// + /// 扩展方法的目标节点 + public static void LogNodePath(this Node node) + { + GD.Print($"[NodePath] {node.GetPath()}"); + } + + /// + /// 以树形结构递归打印节点及其所有子节点的名称 + /// + /// 扩展方法的目标节点 + /// 缩进字符串,用于显示层级关系 + public static void PrintTreeX(this Node node, string indent = "") + { + GD.Print($"{indent}- {node.Name}"); + + // 递归打印所有子节点 + foreach (var child in node.GetChildren()) + child.PrintTreeX(indent + " "); + } + + /// + /// 安全地延迟调用指定方法,确保节点有效后再执行 + /// + /// 扩展方法的目标节点 + /// 要延迟调用的方法名 + public static void SafeCallDeferred(this Node? node, string method) + { + // 检查节点是否为空且实例是否有效,有效时才执行延迟调用 + if (node != null && GodotObject.IsInstanceValid(node)) + node.CallDeferred(method); + } +} \ No newline at end of file diff --git a/GFramework.Godot/extensions/UnRegisterExtension.cs b/GFramework.Godot/extensions/UnRegisterExtension.cs new file mode 100644 index 0000000..2c72256 --- /dev/null +++ b/GFramework.Godot/extensions/UnRegisterExtension.cs @@ -0,0 +1,23 @@ +using GFramework.Core.events; +using Godot; + +namespace GFramework.Godot.extensions; + +/// +/// 提供取消注册扩展方法的静态类 +/// +public static class UnRegisterExtension +{ + /// + /// 当节点退出场景树时自动取消注册监听器 + /// + /// 需要在节点退出时被取消注册的监听器接口实例 + /// Godot节点对象,当该节点退出场景树时触发取消注册操作 + /// 返回传入的原始IUnRegister实例,支持链式调用 + public static IUnRegister UnRegisterWhenNodeExitTree(this IUnRegister unRegister, Node node) + { + // 监听节点的TreeExiting事件,在节点即将退出场景树时执行取消注册操作 + node.TreeExiting += unRegister.UnRegister; + return unRegister; + } +} \ No newline at end of file diff --git a/GFramework.Godot/system/AbstractAssetCatalogSystem.cs b/GFramework.Godot/system/AbstractAssetCatalogSystem.cs new file mode 100644 index 0000000..fb14996 --- /dev/null +++ b/GFramework.Godot/system/AbstractAssetCatalogSystem.cs @@ -0,0 +1,117 @@ +using GFramework.Core.system; + +namespace GFramework.Godot.system; + +/// +/// 资源目录系统抽象基类,用于管理和注册游戏中的场景和资源 +/// +public abstract class AbstractAssetCatalogSystem : AbstractSystem, IAssetCatalogSystem +{ + private readonly Dictionary _scenes = new(); + private readonly Dictionary _resources = new(); + + /// + /// 系统初始化时调用,用于注册所有资产 + /// + protected override void OnInit() + { + RegisterAssets(); + } + + /// + /// 抽象方法,由子类实现具体的资产注册逻辑 + /// + protected abstract void RegisterAssets(); + + #region Register(内部 or Module 使用) + + /// + /// 注册场景资源 + /// + /// 场景的唯一标识键 + /// 场景资源的路径 + /// 当场景键已存在时抛出异常 + public void RegisterScene(string key, string path) + { + if (_scenes.ContainsKey(key)) + throw new InvalidOperationException($"Scene key duplicated: {key}"); + + _scenes[key] = new AssetCatalog.SceneId(path); + } + + /// + /// 注册场景资源 + /// + /// 包含键和场景标识符的映射对象 + /// 当场景键已存在时抛出异常 + public void RegisterScene(AssetCatalog.AssetCatalogMapping mapping) + { + if (mapping.Id is not AssetCatalog.SceneId sceneId) + throw new InvalidOperationException("Mapping ID is not a SceneId"); + + if (!_scenes.TryAdd(mapping.Key, sceneId)) + throw new InvalidOperationException($"Scene key duplicated: {mapping.Key}"); + } + + /// + /// 注册普通资源 + /// + /// 资源的唯一标识键 + /// 资源的路径 + /// 当资源键已存在时抛出异常 + public void RegisterResource(string key, string path) + { + if (_resources.ContainsKey(key)) + throw new InvalidOperationException($"Resource key duplicated: {key}"); + + _resources[key] = new AssetCatalog.ResourceId(path); + } + + /// + /// 注册普通资源 + /// + /// 包含键和资源标识符的映射对象 + /// 当资源键已存在时抛出异常 + public void RegisterResource(AssetCatalog.AssetCatalogMapping mapping) + { + if (mapping.Id is not AssetCatalog.ResourceId resourceId) + throw new InvalidOperationException("Mapping ID is not a ResourceId"); + + if (!_resources.TryAdd(mapping.Key, resourceId)) + throw new InvalidOperationException($"Resource key duplicated: {mapping.Key}"); + } + + #endregion + + #region Query(对外) + + /// + /// 根据键获取场景ID + /// + /// 场景的唯一标识键 + /// 对应的场景ID + public AssetCatalog.SceneId GetScene(string key) => _scenes[key]; + + /// + /// 根据键获取资源ID + /// + /// 资源的唯一标识键 + /// 对应的资源ID + public AssetCatalog.ResourceId GetResource(string key) => _resources[key]; + + /// + /// 检查是否存在指定键的场景 + /// + /// 场景的唯一标识键 + /// 如果存在返回true,否则返回false + public bool HasScene(string key) => _scenes.ContainsKey(key); + + /// + /// 检查是否存在指定键的资源 + /// + /// 资源的唯一标识键 + /// 如果存在返回true,否则返回false + public bool HasResource(string key) => _resources.ContainsKey(key); + + #endregion +} diff --git a/GFramework.Godot/system/AbstractAudioManagerSystem.cs b/GFramework.Godot/system/AbstractAudioManagerSystem.cs new file mode 100644 index 0000000..29816e3 --- /dev/null +++ b/GFramework.Godot/system/AbstractAudioManagerSystem.cs @@ -0,0 +1,558 @@ +using GFramework.Core.extensions; +using GFramework.Core.system; +using Godot; + +namespace GFramework.Godot.system; + +/// +/// 音频管理器抽象基类,提供音频播放的基础实现 +/// +public abstract class AbstractAudioManagerSystem : AbstractSystem, IAudioManagerSystem +{ + /// + /// 音频资源加载系统依赖 + /// + protected IResourceLoadSystem? ResourceLoadSystem; + + /// + /// 资源目录系统依赖 + /// + protected IAssetCatalogSystem? AssetCatalogSystem; + + /// + /// 背景音乐播放器 + /// + protected AudioStreamPlayer? MusicPlayer; + + /// + /// 音效播放器列表 + /// + protected readonly List SoundPlayers = []; + + /// + /// 可用音效播放器队列 + /// + protected readonly Queue AvailableSoundPlayers = new(); + + /// + /// 3D音效播放器列表 + /// + protected readonly List Sound3DPlayers = []; + + /// + /// 可用3D音效播放器队列 + /// + protected readonly Queue AvailableSound3DPlayers = new(); + + /// + /// 资源工厂系统依赖 + /// + protected IResourceFactorySystem? ResourceFactorySystem; + + /// + /// 背景音乐音量 + /// + protected float MusicVolume = 1.0f; + + /// + /// 音效音量 + /// + protected float SoundVolume = 1.0f; + + /// + /// 主音量 + /// + protected float MasterVolume = 1.0f; + + /// + /// 特效音量 + /// + protected float SfxVolume = 1.0f; + + /// + /// 语音音量 + /// + protected float VoiceVolume = 1.0f; + + /// + /// 环境音量 + /// + protected float AmbientVolume = 1.0f; + + /// + /// 音乐淡入淡出动画 + /// + protected Tween? MusicFadeTween; + + /// + /// 最大同时播放的音效数量 + /// + protected const int MaxSoundPlayers = 10; + + /// + /// 最大同时播放的3D音效数量 + /// + protected const int MaxSound3DPlayers = 5; + + /// + /// 所有者节点的抽象属性 + /// + protected abstract Node Owner { get; } + + /// + /// 系统初始化方法 + /// + protected override void OnInit() + { + // 获取依赖的系统 + ResourceLoadSystem = this.GetSystem(); + AssetCatalogSystem = this.GetSystem(); + ResourceFactorySystem = this.GetSystem(); + + // 初始化背景音乐播放器 + MusicPlayer = new AudioStreamPlayer(); + Owner.AddChild(MusicPlayer); + + // 预创建音效播放器池 + for (var i = 0; i < MaxSoundPlayers; i++) + { + var soundPlayer = new AudioStreamPlayer(); + Owner.AddChild(soundPlayer); + soundPlayer.Finished += () => OnSoundFinished(soundPlayer); + SoundPlayers.Add(soundPlayer); + AvailableSoundPlayers.Enqueue(soundPlayer); + } + + // 预创建3D音效播放器池 + for (var i = 0; i < MaxSound3DPlayers; i++) + { + var sound3DPlayer = new AudioStreamPlayer3D(); + Owner.AddChild(sound3DPlayer); + sound3DPlayer.Finished += () => OnSound3DFinished(sound3DPlayer); + Sound3DPlayers.Add(sound3DPlayer); + AvailableSound3DPlayers.Enqueue(sound3DPlayer); + } + } + + /// + /// 当音效播放完成时的回调 + /// + /// 完成播放的音频播放器 + private void OnSoundFinished(AudioStreamPlayer player) + { + // 将播放器放回可用队列 + AvailableSoundPlayers.Enqueue(player); + } + + /// + /// 当3D音效播放完成时的回调 + /// + /// 完成播放的3D音频播放器 + private void OnSound3DFinished(AudioStreamPlayer3D player) + { + // 将播放器放回可用队列 + AvailableSound3DPlayers.Enqueue(player); + } + + /// + /// 播放背景音乐 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 是否循环播放 + public virtual void PlayMusic(string audioPath, float volume = 1.0f, bool loop = true) + { + var audioStream = ResourceLoadSystem?.LoadResource(audioPath); + if (audioStream == null || MusicPlayer == null) return; + + // 停止当前正在进行的淡入淡出效果 + MusicFadeTween?.Kill(); + + MusicPlayer.Stream = audioStream; + MusicPlayer.VolumeDb = LinearToDb(volume * MusicVolume * MasterVolume); + MusicPlayer.Play(); + } + + /// + /// 通过资源ID播放背景音乐 + /// + /// 音乐资源ID + /// 音量大小,范围0-1 + /// 是否循环播放 + public virtual void PlayMusic(AssetCatalog.ResourceId musicId, float volume = 1.0f, bool loop = true) + { + PlayMusic(musicId.Path, volume, loop); + } + + /// + /// 播放音效 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + public virtual void PlaySound(string audioPath, float volume = 1.0f, float pitch = 1.0f) + { + if (AvailableSoundPlayers.Count == 0) return; + + var audioStream = ResourceLoadSystem?.LoadResource(audioPath); + if (audioStream == null) return; + + var player = AvailableSoundPlayers.Dequeue(); + player.Stream = audioStream; + player.VolumeDb = LinearToDb(volume * SoundVolume * MasterVolume); + player.Play(); + } + + /// + /// 播放特效音效 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + public virtual void PlaySfx(string audioPath, float volume = 1.0f, float pitch = 1.0f) + { + if (AvailableSoundPlayers.Count == 0) return; + + var audioStream = ResourceLoadSystem?.LoadResource(audioPath); + if (audioStream == null) return; + + var player = AvailableSoundPlayers.Dequeue(); + player.Stream = audioStream; + player.VolumeDb = LinearToDb(volume * SfxVolume * MasterVolume); + player.Play(); + } + + /// + /// 播放语音 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + public virtual void PlayVoice(string audioPath, float volume = 1.0f, float pitch = 1.0f) + { + if (AvailableSoundPlayers.Count == 0) return; + + var audioStream = ResourceLoadSystem?.LoadResource(audioPath); + if (audioStream == null) return; + + var player = AvailableSoundPlayers.Dequeue(); + player.Stream = audioStream; + player.VolumeDb = LinearToDb(volume * VoiceVolume * MasterVolume); + player.Play(); + } + + /// + /// 播放环境音效 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + public virtual void PlayAmbient(string audioPath, float volume = 1.0f, float pitch = 1.0f) + { + if (AvailableSoundPlayers.Count == 0) return; + + var audioStream = ResourceLoadSystem?.LoadResource(audioPath); + if (audioStream == null) return; + + var player = AvailableSoundPlayers.Dequeue(); + player.Stream = audioStream; + player.VolumeDb = LinearToDb(volume * AmbientVolume * MasterVolume); + player.Play(); + } + + /// + /// 通过资源ID播放音效 + /// + /// 音效资源ID + /// 音量大小,范围0-1 + /// 音调调整 + public virtual void PlaySound(AssetCatalog.ResourceId soundId, float volume = 1.0f, float pitch = 1.0f) + { + PlaySound(soundId.Path, volume, pitch); + } + + /// + /// 播放3D音效 + /// + /// 音频文件路径 + /// 3D空间中的位置 + /// 音量大小,范围0-1 + public virtual void PlaySound3D(string audioPath, Vector3 position, float volume = 1.0f) + { + if (AvailableSound3DPlayers.Count == 0) return; + + var audioStream = ResourceLoadSystem?.LoadResource(audioPath); + if (audioStream == null) return; + + var player = AvailableSound3DPlayers.Dequeue(); + player.Stream = audioStream; + player.VolumeDb = LinearToDb(volume * SoundVolume * MasterVolume); + player.Position = position; + player.Play(); + } + + /// + /// 停止背景音乐 + /// + public virtual void StopMusic() + { + MusicFadeTween?.Kill(); + MusicPlayer?.Stop(); + } + + /// + /// 暂停背景音乐 + /// + public virtual void PauseMusic() + { + MusicFadeTween?.Kill(); + // todo 需要记录音乐播放位置,以便恢复播放时从正确位置开始 + } + + /// + /// 恢复背景音乐播放 + /// + public virtual void ResumeMusic() + { + MusicPlayer?.Play(); + } + + /// + /// 设置背景音乐音量 + /// + /// 音量大小,范围0-1 + public virtual void SetMusicVolume(float volume) + { + MusicVolume = volume; + if (MusicPlayer != null) + { + MusicPlayer.VolumeDb = LinearToDb(MusicVolume * MasterVolume); + } + } + + /// + /// 获取背景音乐音量 + /// + /// 音量大小,范围0-1 + public virtual float GetMusicVolume() + { + return MusicVolume; + } + + /// + /// 设置音效音量 + /// + /// 音量大小,范围0-1 + public virtual void SetSoundVolume(float volume) + { + SoundVolume = volume; + } + + /// + /// 获取音效音量 + /// + /// 音量大小,范围0-1 + public virtual float GetSoundVolume() + { + return SoundVolume; + } + + /// + /// 设置主音量 + /// + /// 音量大小,范围0-1 + public virtual void SetMasterVolume(float volume) + { + MasterVolume = volume; + + // 更新音乐音量 + if (MusicPlayer != null) + { + MusicPlayer.VolumeDb = LinearToDb(MusicVolume * MasterVolume); + } + } + + /// + /// 获取主音量 + /// + /// 音量大小,范围0-1 + public virtual float GetMasterVolume() + { + return MasterVolume; + } + + /// + /// 设置SFX音量 + /// + /// 音量大小,范围0-1 + public virtual void SetSfxVolume(float volume) + { + SfxVolume = volume; + } + + /// + /// 获取SFX音量 + /// + /// 音量大小,范围0-1 + public virtual float GetSfxVolume() + { + return SfxVolume; + } + + /// + /// 设置语音音量 + /// + /// 音量大小,范围0-1 + public virtual void SetVoiceVolume(float volume) + { + VoiceVolume = volume; + } + + /// + /// 获取语音音量 + /// + /// 音量大小,范围0-1 + public virtual float GetVoiceVolume() + { + return VoiceVolume; + } + + /// + /// 设置环境音量 + /// + /// 音量大小,范围0-1 + public virtual void SetAmbientVolume(float volume) + { + AmbientVolume = volume; + } + + /// + /// 获取环境音量 + /// + /// 音量大小,范围0-1 + public virtual float GetAmbientVolume() + { + return AmbientVolume; + } + + /// + /// 检查背景音乐是否正在播放 + /// + /// 正在播放返回true,否则返回false + public virtual bool IsMusicPlaying() + { + return MusicPlayer?.Playing ?? false; + } + + /// + /// 淡入背景音乐 + /// + /// 音频文件路径 + /// 淡入持续时间(秒) + /// 目标音量 + public virtual void FadeInMusic(string audioPath, float duration, float volume = 1.0f) + { + var audioStream = ResourceLoadSystem?.LoadResource(audioPath); + if (audioStream == null || MusicPlayer == null) return; + + // 停止当前正在进行的淡入淡出效果 + MusicFadeTween?.Kill(); + + MusicPlayer.Stream = audioStream; + MusicPlayer.VolumeDb = LinearToDb(0.0f); // 初始音量为0 + MusicPlayer.Play(); + + // 创建淡入动画 + MusicFadeTween = Owner.CreateTween(); + MusicFadeTween.TweenProperty(MusicPlayer, "volume_db", LinearToDb(volume * MusicVolume * MasterVolume), + duration); + } + + /// + /// 淡出背景音乐 + /// + /// 淡出持续时间(秒) + public virtual void FadeOutMusic(float duration) + { + if (MusicPlayer == null) return; + + // 停止当前正在进行的淡入淡出效果 + MusicFadeTween?.Kill(); + + // 创建淡出动画 + MusicFadeTween = Owner.CreateTween(); + MusicFadeTween.TweenProperty(MusicPlayer, "volume_db", LinearToDb(0.0f), duration); + MusicFadeTween.TweenCallback(Callable.From(() => MusicPlayer.Stop())); + } + + /// + /// 设置低通滤波器强度 + /// + /// 滤波器强度,范围0-1 + public virtual void SetLowPassFilter(float amount) + { + // TODO: 实现低通滤波器效果 + // 可以通过AudioEffectLowPassFilter实现 + } + + /// + /// 设置音频混响效果 + /// + /// 房间大小 + /// 阻尼 + /// 湿声级别 + public virtual void SetReverb(float roomSize, float damping, float wetLevel) + { + // TODO: 实现音频混响效果 + // 可以通过AudioEffectReverb实现 + } + + /// + /// 将线性音量值转换为分贝值 + /// + /// 线性音量值(0-1) + /// 分贝值 + protected static float LinearToDb(float linear) + { + return linear > 0 ? 20 * Mathf.Log(linear) : -100; + } + + /// + /// 将分贝值转换为线性音量值 + /// + /// 分贝值 + /// 线性音量值(0-1) + protected static float DbToLinear(float db) + { + return db > -100 ? Mathf.Exp(db / 20) : 0; + } + + /// + /// 系统销毁时清理资源 + /// + protected override void OnDestroy() + { + // 停止并清理淡入淡出动画 + MusicFadeTween?.Kill(); + + // 清理音乐播放器 + MusicPlayer?.QueueFree(); + + // 清理音效播放器池 + foreach (var player in SoundPlayers) + { + player.QueueFree(); + } + + // 清理3D音效播放器池 + foreach (var player in Sound3DPlayers) + { + player.QueueFree(); + } + + SoundPlayers.Clear(); + AvailableSoundPlayers.Clear(); + Sound3DPlayers.Clear(); + AvailableSound3DPlayers.Clear(); + } +} \ No newline at end of file diff --git a/GFramework.Godot/system/AbstractInputSystem.cs b/GFramework.Godot/system/AbstractInputSystem.cs new file mode 100644 index 0000000..37026bd --- /dev/null +++ b/GFramework.Godot/system/AbstractInputSystem.cs @@ -0,0 +1,113 @@ +namespace GFramework.Godot.system; + +/// +/// 抽象输入系统基类,实现了IInputSystem接口的基本功能 +/// +public abstract class AbstractInputSystem : AbstractAssetCatalogSystem, IInputSystem +{ + // 存储动作名称与按键绑定的映射关系 + private readonly Dictionary _actionBindings = new(); + + // 存储动作名称与当前状态的映射关系 + private readonly Dictionary _actionStates = new(); + + // 存储动作名称与上一帧状态的映射关系 + private readonly Dictionary _previousActionStates = new(); + + /// + public virtual void SetBinding(string actionName, string keyCode) + { + _actionBindings[actionName] = keyCode; + } + + /// + public virtual string GetBinding(string actionName) + { + return _actionBindings.TryGetValue(actionName, out var binding) ? binding : string.Empty; + } + + /// + public virtual bool IsActionPressed(string actionName) + { + return _actionStates.TryGetValue(actionName, out var state) && state; + } + + /// + public virtual bool IsActionJustPressed(string actionName) + { + var current = _actionStates.TryGetValue(actionName, out var currentState) && currentState; + var previous = _previousActionStates.TryGetValue(actionName, out var previousState) && previousState; + return current && !previous; + } + + /// + public virtual bool IsActionJustReleased(string actionName) + { + var current = _actionStates.TryGetValue(actionName, out var currentState) && currentState; + var previous = _previousActionStates.TryGetValue(actionName, out var previousState) && previousState; + return !current && previous; + } + + /// + public virtual void AddAction(string actionName, string defaultKeyCode) + { + if (!_actionBindings.ContainsKey(actionName)) + { + _actionBindings[actionName] = defaultKeyCode; + _actionStates[actionName] = false; + _previousActionStates[actionName] = false; + } + } + + /// + public virtual void RemoveAction(string actionName) + { + _actionBindings.Remove(actionName); + _actionStates.Remove(actionName); + _previousActionStates.Remove(actionName); + } + + /// + public abstract void SaveConfiguration(); + + /// + public abstract void LoadConfiguration(); + + /// + public abstract void Update(double delta); + + /// + /// 更新输入状态,在每一帧调用 + /// + protected virtual void UpdateInputStates() + { + // 保存当前状态作为下一帧的前一状态 + foreach (var kvp in _actionStates) + { + _previousActionStates[kvp.Key] = kvp.Value; + } + + // 更新当前状态 + foreach (var kvp in _actionBindings) + { + var actionName = kvp.Key; + var keyCode = kvp.Value; + + // 这里需要根据具体平台和引擎实现具体的按键检测逻辑 + // 当前只是一个占位实现 + _actionStates[actionName] = CheckKeyPressed(keyCode); + } + } + + /// + /// 检查特定按键是否被按下 + /// + /// 按键码 + /// 按键是否被按下 + protected virtual bool CheckKeyPressed(string keyCode) + { + // 这里需要根据Godot引擎的具体实现来检查按键状态 + // 目前只是示例实现 + return false; + } +} \ No newline at end of file diff --git a/GFramework.Godot/system/AbstractResourceFactorySystem.cs b/GFramework.Godot/system/AbstractResourceFactorySystem.cs new file mode 100644 index 0000000..5df0c2a --- /dev/null +++ b/GFramework.Godot/system/AbstractResourceFactorySystem.cs @@ -0,0 +1,109 @@ +using GFramework.Core.events; +using GFramework.Core.extensions; +using GFramework.Core.system; +using Godot; + +namespace GFramework.Godot.system; + + +/// +/// 资源工厂系统抽象基类,用于统一管理各类资源的创建与预加载逻辑。 +/// 提供注册场景和资源的方法,并通过依赖的资源加载系统和资产目录系统完成实际资源的获取与构造。 +/// +public abstract class AbstractResourceFactorySystem : AbstractSystem, IResourceFactorySystem +{ + private ResourceFactory.Registry? _registry; + private IResourceLoadSystem? _resourceLoadSystem; + private IAssetCatalogSystem? _assetCatalogSystem; + + /// + /// 系统初始化方法,在系统启动时执行一次。 + /// 初始化资源注册表,并获取依赖的资源加载系统和资产目录系统。 + /// 最后执行所有已注册资源的预加载操作。 + /// + protected override void OnInit() + { + _registry = new ResourceFactory.Registry(); + _resourceLoadSystem = this.GetSystem(); + _assetCatalogSystem = this.GetSystem(); + // 监听架构初始化事件 + this.RegisterEvent(_ => + { + // 注册资源 + RegisterResources(); + // 预加载所有资源 + _registry.PreloadAll(); + }); + } + /// + /// 注册系统所需的各种资源类型。由子类实现具体注册逻辑。 + /// + protected abstract void RegisterResources(); + + + /// + /// 根据指定的键获取资源工厂函数。 + /// + /// 资源类型 + /// 资源键 + /// 返回创建指定类型资源的工厂函数 + public Func GetFactory(string key)=>_registry!.ResolveFactory(key); + + /// + /// 根据资产目录映射信息获取资源工厂函数。 + /// + /// 资源类型 + /// 资产目录映射信息 + /// 返回创建指定类型资源的工厂函数 + public Func GetFactory(AssetCatalog.AssetCatalogMapping mapping) => _registry!.ResolveFactory(mapping.Key); + + + + #region Register Helpers(声明式) + + /// + /// 注册场景资源工厂。 + /// 根据场景键名获取场景ID,并将场景加载工厂注册到注册表中。 + /// + /// 场景节点类型,必须继承自Node + /// 场景在资产目录中的键名 + /// 是否需要预加载该场景资源 + protected void RegisterScene( + string sceneKey, + bool preload = false) + where T : Node + { + var id = _assetCatalogSystem!.GetScene(sceneKey); + + _registry!.Register( + sceneKey, + _resourceLoadSystem!.GetOrRegisterSceneFactory(id), + preload + ); + } + + /// + /// 注册普通资源工厂。 + /// 根据资源键名获取资源ID,并将资源加载工厂注册到注册表中。 + /// + /// 资源类型,必须继承自Resource + /// 资源在资产目录中的键名 + /// 是否需要复制资源实例 + /// 是否需要预加载该资源 + protected void RegisterResource( + string resourceKey, + bool duplicate = false, + bool preload = false) + where T : Resource + { + var id = _assetCatalogSystem!.GetResource(resourceKey); + + _registry!.Register( + resourceKey, + _resourceLoadSystem!.GetOrRegisterResourceFactory(id, duplicate), + preload + ); + } + + #endregion +} diff --git a/GFramework.Godot/system/AssetCatalog.cs b/GFramework.Godot/system/AssetCatalog.cs new file mode 100644 index 0000000..15edf32 --- /dev/null +++ b/GFramework.Godot/system/AssetCatalog.cs @@ -0,0 +1,39 @@ + +namespace GFramework.Godot.system; + +/// +/// 资源目录类,用于定义和管理游戏中的场景和资源标识符 +/// +public static class AssetCatalog +{ + /// + /// 资源标识符接口,定义了资源路径的访问接口 + /// + public interface IAssetId + { + /// + /// 获取资源的路径 + /// + string Path { get; } + } + + /// + /// 资源目录映射结构体,用于存储资源目录的键值对映射关系 + /// + /// 资源目录的键 + /// 资源标识符 + public readonly record struct AssetCatalogMapping(string Key, IAssetId Id); + + /// + /// 场景标识符结构体,用于唯一标识一个场景资源 + /// + /// 场景资源的路径 + public readonly record struct SceneId(string Path) : IAssetId; + + /// + /// 资源标识符结构体,用于唯一标识一个游戏资源 + /// + /// 游戏资源的路径 + public readonly record struct ResourceId(string Path) : IAssetId; + +} diff --git a/GFramework.Godot/system/GodotInputSystem.cs b/GFramework.Godot/system/GodotInputSystem.cs new file mode 100644 index 0000000..4e2cfb6 --- /dev/null +++ b/GFramework.Godot/system/GodotInputSystem.cs @@ -0,0 +1,195 @@ +// using Godot; +// +// namespace GFramework.Godot.system; +// +// /// +// /// Godot引擎专用的输入系统实现 +// /// +// public class GodotInputSystem : AbstractInputSystem +// { +// private InputMap _inputMap; +// private string _configPath; +// +// public override void Init() +// { +// base.Init(); +// +// _inputMap = new InputMap(); +// _configPath = "user://input_config.json"; +// +// // 添加一些默认的输入动作 +// AddDefaultActions(); +// +// // 尝试加载用户配置 +// LoadConfiguration(); +// } +// +// public override void Destroy() +// { +// // 保存配置 +// SaveConfiguration(); +// base.Destroy(); +// } +// +// /// +// /// 添加默认输入动作 +// /// +// private void AddDefaultActions() +// { +// _inputMap.AddAction(new InputAction("move_left", InputActionType.Axis, "Left")); +// _inputMap.AddAction(new InputAction("move_right", InputActionType.Axis, "Right")); +// _inputMap.AddAction(new InputAction("move_up", InputActionType.Axis, "Up")); +// _inputMap.AddAction(new InputAction("move_down", InputActionType.Axis, "Down")); +// _inputMap.AddAction(new InputAction("jump", InputActionType.Button, "Space")); +// _inputMap.AddAction(new InputAction("attack", InputActionType.Button, "LeftMouse")); +// _inputMap.AddAction(new InputAction("interact", InputActionType.Button, "E")); +// } +// +// /// +// public override void SaveConfiguration() +// { +// try +// { +// var configData = new Dictionary(); +// foreach (var action in _inputMap.GetAllActions()) +// { +// configData[action.Name] = action.CurrentBindings; +// } +// +// var json = Json.Stringify(configData); +// File.WriteAllText(ProjectSettings.GlobalizePath(_configPath), json); +// } +// catch (Exception ex) +// { +// GD.PrintErr($"Failed to save input configuration: {ex.Message}"); +// } +// } +// +// /// +// public override void LoadConfiguration() +// { +// try +// { +// if (!File.Exists(ProjectSettings.GlobalizePath(_configPath))) +// { +// // 配置文件不存在,使用默认配置 +// return; +// } +// +// var json = File.ReadAllText(ProjectSettings.GlobalizePath(_configPath)); +// var parsed = Json.ParseString(json); +// if (parsed is not Core.Godot.Collections.Dictionary dict) +// { +// GD.PrintErr("Invalid input configuration file"); +// return; +// } +// +// foreach (var key in dict.Keys) +// { +// var action = _inputMap.GetAction(key.AsString()); +// if (action != null && dict[key] is Core.Godot.Collections.Array array) +// { +// var bindings = new string[array.Count]; +// for (int i = 0; i < array.Count; i++) +// { +// bindings[i] = array[i].AsString(); +// } +// action.SetBindings(bindings); +// } +// } +// } +// catch (Exception ex) +// { +// GD.PrintErr($"Failed to load input configuration: {ex.Message}"); +// } +// } +// +// /// +// protected override bool CheckKeyPressed(string keyCode) +// { +// // 根据Godot的输入系统检查按键状态 +// return Input.IsPhysicalKeyPressed(GodotKeyMapper.GetKeyFromString(keyCode)) || +// Input.IsMouseButtonPressed(GodotKeyMapper.GetMouseButtonFromString(keyCode)); +// } +// +// /// +// public override void Update(double delta) +// { +// UpdateInputStates(); +// } +// +// protected override void RegisterAssets() +// { +// throw new NotImplementedException(); +// } +// } +// +// /// +// /// Godot按键映射辅助类 +// /// +// public static class GodotKeyMapper +// { +// private static readonly Dictionary KeyMap = new() +// { +// { "Left", Key.Left }, +// { "Right", Key.Right }, +// { "Up", Key.Up }, +// { "Down", Key.Down }, +// { "Space", Key.Space }, +// { "Enter", Key.Enter }, +// { "Escape", Key.Escape }, +// { "A", Key.A }, +// { "B", Key.B }, +// { "C", Key.C }, +// { "D", Key.D }, +// { "E", Key.E }, +// { "F", Key.F }, +// { "G", Key.G }, +// { "H", Key.H }, +// { "I", Key.I }, +// { "J", Key.J }, +// { "K", Key.K }, +// { "L", Key.L }, +// { "M", Key.M }, +// { "N", Key.N }, +// { "O", Key.O }, +// { "P", Key.P }, +// { "Q", Key.Q }, +// { "R", Key.R }, +// { "S", Key.S }, +// { "T", Key.T }, +// { "U", Key.U }, +// { "V", Key.V }, +// { "W", Key.W }, +// { "X", Key.X }, +// { "Y", Key.Y }, +// { "Z", Key.Z }, +// { "0", Key.Key0 }, +// { "1", Key.Key1 }, +// { "2", Key.Key2 }, +// { "3", Key.Key3 }, +// { "4", Key.Key4 }, +// { "5", Key.Key5 }, +// { "6", Key.Key6 }, +// { "7", Key.Key7 }, +// { "8", Key.Key8 }, +// { "9", Key.Key9 } +// }; +// +// private static readonly Dictionary MouseButtonMap = new() +// { +// { "LeftMouse", MouseButton.Left }, +// { "RightMouse", MouseButton.Right }, +// { "MiddleMouse", MouseButton.Middle } +// }; +// +// public static Key GetKeyFromString(string keyString) +// { +// return KeyMap.GetValueOrDefault(keyString, Key.None); +// } +// +// public static MouseButton GetMouseButtonFromString(string mouseButtonString) +// { +// return MouseButtonMap.GetValueOrDefault(mouseButtonString, MouseButton.None); +// } +// } \ No newline at end of file diff --git a/GFramework.Godot/system/IAssetCatalogSystem.cs b/GFramework.Godot/system/IAssetCatalogSystem.cs new file mode 100644 index 0000000..fe66618 --- /dev/null +++ b/GFramework.Godot/system/IAssetCatalogSystem.cs @@ -0,0 +1,37 @@ +using GFramework.Core.system; + +namespace GFramework.Godot.system; + +/// +/// 资源目录系统接口,用于管理场景和资源的获取与查询 +/// +public interface IAssetCatalogSystem : ISystem +{ + /// + /// 根据键名获取场景标识符 + /// + /// 场景的唯一键名 + /// 返回对应的场景ID + AssetCatalog.SceneId GetScene(string key); + + /// + /// 根据键名获取资源标识符 + /// + /// 资源的唯一键名 + /// 返回对应的资源ID + AssetCatalog.ResourceId GetResource(string key); + + /// + /// 检查是否存在指定键名的场景 + /// + /// 要检查的场景键名 + /// 如果存在返回true,否则返回false + bool HasScene(string key); + + /// + /// 检查是否存在指定键名的资源 + /// + /// 要检查的资源键名 + /// 如果存在返回true,否则返回false + bool HasResource(string key); +} diff --git a/GFramework.Godot/system/IAudioManagerSystem.cs b/GFramework.Godot/system/IAudioManagerSystem.cs new file mode 100644 index 0000000..e035bc2 --- /dev/null +++ b/GFramework.Godot/system/IAudioManagerSystem.cs @@ -0,0 +1,179 @@ +using GFramework.Core.system; +using Godot; + +namespace GFramework.Godot.system; + +/// +/// 音频管理器系统接口,用于统一管理背景音乐和音效的播放 +/// +public interface IAudioManagerSystem : ISystem +{ + /// + /// 播放背景音乐 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 是否循环播放 + void PlayMusic(string audioPath, float volume = 1.0f, bool loop = true); + + /// + /// 播放音效 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + void PlaySound(string audioPath, float volume = 1.0f, float pitch = 1.0f); + + /// + /// 播放特效音效 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + void PlaySfx(string audioPath, float volume = 1.0f, float pitch = 1.0f); + + /// + /// 播放语音 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + void PlayVoice(string audioPath, float volume = 1.0f, float pitch = 1.0f); + + /// + /// 播放环境音效 + /// + /// 音频文件路径 + /// 音量大小,范围0-1 + /// 音调调整 + void PlayAmbient(string audioPath, float volume = 1.0f, float pitch = 1.0f); + + /// + /// 停止背景音乐 + /// + void StopMusic(); + + /// + /// 暂停背景音乐 + /// + void PauseMusic(); + + /// + /// 恢复背景音乐播放 + /// + void ResumeMusic(); + + /// + /// 设置背景音乐音量 + /// + /// 音量大小,范围0-1 + void SetMusicVolume(float volume); + + /// + /// 获取背景音乐音量 + /// + /// 音量大小,范围0-1 + float GetMusicVolume(); + + /// + /// 设置音效音量 + /// + /// 音量大小,范围0-1 + void SetSoundVolume(float volume); + + /// + /// 获取音效音量 + /// + /// 音量大小,范围0-1 + float GetSoundVolume(); + + /// + /// 设置特效音量 + /// + /// 音量大小,范围0-1 + void SetSfxVolume(float volume); + + /// + /// 获取特效音量 + /// + /// 音量大小,范围0-1 + float GetSfxVolume(); + + /// + /// 设置语音音量 + /// + /// 音量大小,范围0-1 + void SetVoiceVolume(float volume); + + /// + /// 获取语音音量 + /// + /// 音量大小,范围0-1 + float GetVoiceVolume(); + + /// + /// 设置环境音量 + /// + /// 音量大小,范围0-1 + void SetAmbientVolume(float volume); + + /// + /// 获取环境音量 + /// + /// 音量大小,范围0-1 + float GetAmbientVolume(); + + /// + /// 设置主音量 + /// + /// 音量大小,范围0-1 + void SetMasterVolume(float volume); + + /// + /// 获取主音量 + /// + /// 音量大小,范围0-1 + float GetMasterVolume(); + + /// + /// 检查背景音乐是否正在播放 + /// + /// 正在播放返回true,否则返回false + bool IsMusicPlaying(); + + /// + /// 淡入背景音乐 + /// + /// 音频文件路径 + /// 淡入持续时间(秒) + /// 目标音量 + void FadeInMusic(string audioPath, float duration, float volume = 1.0f); + + /// + /// 淡出背景音乐 + /// + /// 淡出持续时间(秒) + void FadeOutMusic(float duration); + + /// + /// 播放3D音效 + /// + /// 音频文件路径 + /// 3D空间中的位置 + /// 音量大小,范围0-1 + void PlaySound3D(string audioPath, Vector3 position, float volume = 1.0f); + + /// + /// 设置低通滤波器强度 + /// + /// 滤波器强度,范围0-1 + void SetLowPassFilter(float amount); + + /// + /// 设置音频混响效果 + /// + /// 房间大小 + /// 阻尼 + /// 湿声级别 + void SetReverb(float roomSize, float damping, float wetLevel); +} \ No newline at end of file diff --git a/GFramework.Godot/system/IInputSystem.cs b/GFramework.Godot/system/IInputSystem.cs new file mode 100644 index 0000000..4e44727 --- /dev/null +++ b/GFramework.Godot/system/IInputSystem.cs @@ -0,0 +1,73 @@ +using GFramework.Core.system; + +namespace GFramework.Godot.system; + +/// +/// 输入系统接口,用于统一管理游戏中的输入操作和键位绑定 +/// +public interface IInputSystem : ISystem +{ + /// + /// 设置指定动作的按键绑定 + /// + /// 动作名称 + /// 按键码 + void SetBinding(string actionName, string keyCode); + + /// + /// 获取指定动作的按键绑定 + /// + /// 动作名称 + /// 绑定的按键码 + string GetBinding(string actionName); + + /// + /// 检查指定动作是否正在被执行 + /// + /// 动作名称 + /// 如果动作正在执行则返回true,否则返回false + bool IsActionPressed(string actionName); + + /// + /// 检查指定动作是否刚刚开始执行 + /// + /// 动作名称 + /// 如果动作刚刚开始执行则返回true,否则返回false + bool IsActionJustPressed(string actionName); + + /// + /// 检查指定动作是否刚刚停止执行 + /// + /// 动作名称 + /// 如果动作刚刚停止执行则返回true,否则返回false + bool IsActionJustReleased(string actionName); + + /// + /// 添加输入动作 + /// + /// 动作名称 + /// 默认按键绑定 + void AddAction(string actionName, string defaultKeyCode); + + /// + /// 移除输入动作 + /// + /// 动作名称 + void RemoveAction(string actionName); + + /// + /// 保存输入配置到文件 + /// + void SaveConfiguration(); + + /// + /// 从文件加载输入配置 + /// + void LoadConfiguration(); + + /// + /// 更新输入系统状态,应在每帧调用 + /// + /// 帧间隔时间 + void Update(double delta); +} \ No newline at end of file diff --git a/GFramework.Godot/system/IResourceFactorySystem.cs b/GFramework.Godot/system/IResourceFactorySystem.cs new file mode 100644 index 0000000..793dbc9 --- /dev/null +++ b/GFramework.Godot/system/IResourceFactorySystem.cs @@ -0,0 +1,26 @@ +using GFramework.Core.system; + +namespace GFramework.Godot.system; + +/// +/// 资源工厂系统接口,用于获取指定类型的资源创建函数 +/// +public interface IResourceFactorySystem : ISystem +{ + /// + /// 根据指定键名获取指定类型T的资源创建函数 + /// + /// 要获取创建函数的资源类型 + /// 用于标识资源的键名 + /// 返回一个创建T类型实例的函数委托 + Func GetFactory(string key); + + /// + /// 根据资产目录映射获取指定类型T的资源创建函数 + /// + /// 要获取创建函数的资源类型 + /// 资产目录映射信息 + /// 返回一个创建T类型实例的函数委托 + Func GetFactory(AssetCatalog.AssetCatalogMapping mapping); +} + diff --git a/GFramework.Godot/system/IResourceLoadSystem.cs b/GFramework.Godot/system/IResourceLoadSystem.cs new file mode 100644 index 0000000..fa94e89 --- /dev/null +++ b/GFramework.Godot/system/IResourceLoadSystem.cs @@ -0,0 +1,68 @@ +using GFramework.Core.system; +using Godot; + +namespace GFramework.Godot.system; + +/// +/// 资源加载系统接口,提供资源和场景的加载、实例化、预加载等功能 +/// +public interface IResourceLoadSystem : ISystem +{ + /// + /// 加载指定路径的资源 + /// + /// 资源类型,必须继承自Resource + /// 资源路径 + /// 加载的资源实例 + public T? LoadResource(string path) where T : Resource; + + /// + /// 获取场景加载器,用于延迟加载场景 + /// + /// 场景路径 + /// 场景的延迟加载包装器 + public Lazy GetSceneLoader(string path); + /// + /// 创建指定路径场景的实例 + /// + /// 节点类型,必须继承自Node + /// 场景路径 + /// 场景实例化的节点对象 + public T? CreateInstance(string path) where T : Node; + + + /// + /// 获取或注册场景工厂函数 + /// + /// 节点类型,必须继承自Node + /// 场景资源标识符 + /// 创建场景实例的工厂函数 + public Func GetOrRegisterSceneFactory(AssetCatalog.SceneId id) where T : Node; + + /// + /// 获取或注册资源工厂函数 + /// + /// 资源类型,必须继承自Node + /// 资源标识符 + /// 是否创建副本,默认为false + /// 创建资源实例的工厂函数 + public Func GetOrRegisterResourceFactory(AssetCatalog.ResourceId id, bool duplicate = false) + where T : Resource; + + /// + /// 预加载指定路径的多个资源 + /// + /// 需要预加载的资源路径集合 + public void Preload(IEnumerable paths); + + /// + /// 卸载指定路径的资源 + /// + /// 需要卸载的资源路径 + public void Unload(string path); + + /// + /// 清除所有已加载的资源 + /// + public void ClearAll(); +} diff --git a/GFramework.Godot/system/InputAction.cs b/GFramework.Godot/system/InputAction.cs new file mode 100644 index 0000000..f1cf934 --- /dev/null +++ b/GFramework.Godot/system/InputAction.cs @@ -0,0 +1,79 @@ +namespace GFramework.Godot.system; + +/// +/// 输入动作类,表示游戏中的一种输入行为 +/// +public class InputAction +{ + /// + /// 动作名称 + /// + public string Name { get; } + + /// + /// 动作类型 + /// + public InputActionType ActionType { get; } + + /// + /// 默认按键绑定 + /// + public string[] DefaultBindings { get; } + + /// + /// 当前按键绑定 + /// + public string[] CurrentBindings { get; private set; } + + /// + /// 构造函数 + /// + /// 动作名称 + /// 动作类型 + /// 默认按键绑定 + public InputAction(string name, InputActionType actionType, params string[] defaultBindings) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + ActionType = actionType; + DefaultBindings = defaultBindings ?? throw new ArgumentNullException(nameof(defaultBindings)); + CurrentBindings = (string[])defaultBindings.Clone(); + } + + /// + /// 重置为默认按键绑定 + /// + public void ResetToDefault() + { + CurrentBindings = (string[])DefaultBindings.Clone(); + } + + /// + /// 设置新的按键绑定 + /// + /// 新的按键绑定 + public void SetBindings(params string[] bindings) + { + CurrentBindings = bindings ?? throw new ArgumentNullException(nameof(bindings)); + } +} + +/// +/// 输入动作类型枚举 +/// +public enum InputActionType +{ + /// + /// 按钮类型,如跳跃、射击等 + /// + Button, + + /// + /// 轴类型,如移动、视角控制等 + /// + Axis, + + /// + /// 向量类型,如二维移动控制 + /// + Vector +} \ No newline at end of file diff --git a/GFramework.Godot/system/InputMap.cs b/GFramework.Godot/system/InputMap.cs new file mode 100644 index 0000000..416656f --- /dev/null +++ b/GFramework.Godot/system/InputMap.cs @@ -0,0 +1,67 @@ +namespace GFramework.Godot.system; + +/// +/// 输入映射类,管理所有的输入动作及其绑定 +/// +public class InputMap +{ + private readonly Dictionary _actions = new(); + + /// + /// 添加输入动作 + /// + /// 输入动作 + public void AddAction(InputAction action) + { + _actions[action.Name] = action; + } + + /// + /// 移除输入动作 + /// + /// 动作名称 + public void RemoveAction(string actionName) + { + _actions.Remove(actionName); + } + + /// + /// 获取输入动作 + /// + /// 动作名称 + /// 输入动作,如果不存在则返回null + public InputAction GetAction(string actionName) + { + return _actions.GetValueOrDefault(actionName); + } + + /// + /// 获取所有输入动作 + /// + /// 输入动作列表 + public IEnumerable GetAllActions() + { + return _actions.Values; + } + + /// + /// 检查是否存在指定名称的动作 + /// + /// 动作名称 + /// 存在返回true,否则返回false + public bool ContainsAction(string actionName) + { + return _actions.ContainsKey(actionName); + } + + /// + /// 根据按键查找绑定的动作 + /// + /// 按键码 + /// 绑定到该按键的所有动作 + public IEnumerable FindActionsByBinding(string keyCode) + { + return _actions.Values.Where(action => + action.CurrentBindings.Contains(keyCode)); + } +} \ No newline at end of file diff --git a/GFramework.Godot/system/ResourceFactory.cs b/GFramework.Godot/system/ResourceFactory.cs new file mode 100644 index 0000000..8137b2c --- /dev/null +++ b/GFramework.Godot/system/ResourceFactory.cs @@ -0,0 +1,130 @@ +namespace GFramework.Godot.system; + +/// +/// 资源工厂类,用于注册和解析各种资源的创建工厂 +/// +public static class ResourceFactory +{ + /// + /// 可预加载条目接口,定义了是否需要预加载以及执行工厂的方法 + /// + private interface IPreloadableEntry + { + /// + /// 获取一个值,表示该资源是否需要预加载 + /// + bool Preload { get; } + + /// + /// 执行与该条目关联的工厂方法 + /// + void ExecuteFactory(); + + /// + /// 获取资源类型 + /// + Type ResourceType { get; } + + /// + /// 获取资源键值 + /// + string Key { get; } + } + + + /// + /// 表示一个具体的资源工厂条目,实现 IPreloadableEntry 接口 + /// + /// 资源类型 + private sealed class Entry(string key, Func factory, bool preload) : IPreloadableEntry + { + /// + /// 获取用于创建资源的工厂函数 + /// + public Func Factory { get; } = factory; + + /// + /// 获取一个值,表示该资源是否需要预加载 + /// + public bool Preload { get; } = preload; + + /// + /// 执行工厂函数以创建资源实例 + /// + public void ExecuteFactory() => Factory(); + + /// + /// 获取资源的类型 + /// + public Type ResourceType => typeof(T); + + /// + /// 获取资源的键值 + /// + public string Key { get; } = key; + } + + + /// + /// 工厂注册表,管理所有已注册的资源工厂 + /// + internal sealed class Registry + { + /// + /// 存储所有已注册的工厂函数,键为资源类型,值为对应的工厂条目对象 + /// + private readonly Dictionary<(Type type, string key), IPreloadableEntry> _factories = new(); + + /// + /// 注册指定类型的资源工厂 + /// + /// 要注册的资源类型 + /// 键 + /// 创建该类型资源的工厂函数 + /// 是否需要预加载该资源,默认为false + public void Register(string key, Func factory, bool preload = false) + { + if (string.IsNullOrWhiteSpace(key)) + throw new ArgumentException("Resource key cannot be null or empty.", nameof(key)); + + var dictKey = (typeof(T), key); + + _factories[dictKey] = new Entry(key, factory, preload); + } + + /// + /// 解析并获取指定类型的工厂函数 + /// + /// 要获取工厂函数的资源类型 + /// 资源键 + /// 指定类型的工厂函数 + /// 当指定类型的工厂未注册时抛出异常 + public Func ResolveFactory(string key) + { + var dictKey = (typeof(T), key); + + if (_factories.TryGetValue(dictKey, out var entry) + && entry is Entry typed) + { + return typed.Factory; + } + + throw new InvalidOperationException( + $"Factory not registered: {typeof(T).Name} with key '{key}'"); + } + + /// + /// 预加载所有标记为需要预加载的资源 + /// + public void PreloadAll() + { + // 遍历所有已注册的工厂 + foreach (var entry in _factories.Values.Where(entry => entry.Preload)) + { + // 执行其工厂方法进行预加载 + entry.ExecuteFactory(); + } + } + } + +} diff --git a/GFramework.Godot/system/ResourceLoadSystem.cs b/GFramework.Godot/system/ResourceLoadSystem.cs new file mode 100644 index 0000000..728858e --- /dev/null +++ b/GFramework.Godot/system/ResourceLoadSystem.cs @@ -0,0 +1,209 @@ +using GFramework.Core.system; +using Godot; + +namespace GFramework.Godot.system; + +/// +/// 资源加载系统,用于统一管理和缓存Godot资源(如场景、纹理等)的加载与实例化。 +/// 提供基础加载、场景实例化、资源工厂注册以及缓存管理功能。 +/// +public class ResourceLoadSystem : AbstractSystem, IResourceLoadSystem +{ + /// + /// 已加载的资源缓存字典,键为资源路径,值为已加载的Resource对象。 + /// + private readonly Dictionary _loadedResources = new(); + + /// + /// 场景懒加载器缓存,键为场景路径,值为延迟加载的PackedScene对象。 + /// + private readonly Dictionary> _sceneLoaders = new(); + + /// + /// 场景实例化工厂委托缓存,键为场景路径,值为创建该场景实例的Func委托。 + /// + private readonly Dictionary _sceneFactories = new(); + + /// + /// 资源获取/复制工厂委托缓存,键为资源路径,值为获取或复制资源的Func委托。 + /// + private readonly Dictionary _resourceFactories = new(); + + /// + /// 初始化方法,在系统初始化时打印日志信息。 + /// + protected override void OnInit() + { + } + + #region 基础加载 + + /// + /// 加载指定类型的资源并进行缓存。如果资源已经加载过则直接从缓存中返回。 + /// + /// 要加载的资源类型,必须继承自Resource。 + /// 资源在项目中的相对路径。 + /// 成功加载的资源对象;若路径无效或加载失败则返回null。 + public T? LoadResource(string path) where T : Resource + { + if (string.IsNullOrEmpty(path)) + return null; + + if (_loadedResources.TryGetValue(path, out var cached)) + return cached as T; + + var res = GD.Load(path); + if (res == null) + { + GD.PrintErr($"[ResourceLoadSystem] Load failed: {path}"); + return null; + } + + _loadedResources[path] = res; + return res; + } + + /// + /// 获取一个场景的懒加载器,用于按需加载PackedScene资源。 + /// 若对应路径尚未注册加载器,则会自动创建一个新的Lazy实例。 + /// + /// 场景文件的相对路径。 + /// 表示该场景懒加载逻辑的Lazy<PackedScene>对象。 + public Lazy GetSceneLoader(string path) + { + if (_sceneLoaders.TryGetValue(path, out var loader)) + return loader; + + loader = new Lazy(() => + { + var scene = LoadResource(path); + return scene ?? throw new InvalidOperationException($"Failed to load scene: {path}"); + }); + + _sceneLoaders[path] = loader; + return loader; + } + + #endregion + + #region 场景实例化 + + /// + /// 根据给定路径加载场景,并创建其节点实例。 + /// + /// 期望返回的节点类型,必须是Node的子类。 + /// 场景文件的相对路径。 + /// 新创建的场景根节点实例;如果加载失败则返回null。 + public T? CreateInstance(string path) where T : Node + { + var scene = GetSceneLoader(path).Value; + return scene.Instantiate(); + } + + /// + /// 注册或获取一个用于创建特定场景实例的工厂函数。 + /// 如果已存在相同路径的工厂函数,则尝试转换后复用。 + /// + /// 目标场景根节点的类型。 + /// 场景文件的id。 + /// 用于创建该场景实例的Func委托。 + /// 当已有工厂不是Func<T>类型时抛出。 + /// 当无法加载场景或实例化失败时抛出。 + public Func GetOrRegisterSceneFactory(AssetCatalog.SceneId id) where T : Node + { + var path = id.Path; + if (_sceneFactories.TryGetValue(path, out var d)) + return d as Func ?? + throw new InvalidCastException($"Factory for path '{path}' is not of type Func<{typeof(T)}>"); + + var factory = () => + { + var scene = GetSceneLoader(path).Value + ?? throw new InvalidOperationException($"Scene not loaded: {path}"); + + return scene.Instantiate() + ?? throw new InvalidOperationException($"Instantiate failed: {path}"); + }; + + _sceneFactories[path] = factory; + return factory; + } + + #endregion + + #region 资源工厂 + + /// + /// 注册或获取一个用于加载或复制资源的工厂函数。 + /// 可选择是否每次调用都返回副本(Duplicate),适用于需要独立状态的资源。 + /// + /// 资源的具体类型。 + /// 资源文件的id。 + /// 是否每次都返回资源的一个副本,默认为false。 + /// 用于加载或复制资源的Func委托。 + /// 当已有工厂不是Func<T>类型时抛出。 + /// 当资源加载失败时抛出。 + public Func GetOrRegisterResourceFactory(AssetCatalog.ResourceId id, bool duplicate = false) + where T : Resource + { + var path = id.Path; + if (_resourceFactories.TryGetValue(path, out var d)) + return d as Func ?? + throw new InvalidCastException($"Factory for path '{path}' is not of type Func<{typeof(T)}>"); + + var factory = () => + { + var res = LoadResource(path) + ?? throw new InvalidOperationException($"Load failed: {path}"); + + if (!duplicate) return res; + + return res.Duplicate() as T ?? res; + }; + + _resourceFactories[path] = factory; + return factory; + } + + #endregion + + #region 缓存管理 + + /// + /// 预加载一组资源和场景到内存中以提升后续访问速度。 + /// + /// 待预加载的资源路径集合。 + public void Preload(IEnumerable paths) + { + foreach (var path in paths) + { + GetSceneLoader(path); + LoadResource(path); + } + } + + /// + /// 清除指定路径的所有相关缓存数据,包括资源、场景加载器及各类工厂。 + /// + /// 要卸载的资源路径。 + public void Unload(string path) + { + _loadedResources.Remove(path); + _sceneLoaders.Remove(path); + _sceneFactories.Remove(path); + _resourceFactories.Remove(path); + } + + /// + /// 清空所有当前系统的资源缓存、加载器和工厂列表。 + /// + public void ClearAll() + { + _loadedResources.Clear(); + _sceneLoaders.Clear(); + _sceneFactories.Clear(); + _resourceFactories.Clear(); + } + + #endregion +} diff --git a/GFramework.csproj b/GFramework.csproj index 835a649..17c72b4 100644 --- a/GFramework.csproj +++ b/GFramework.csproj @@ -1,8 +1,6 @@ - enable - enable GeWuYou.GFramework gewuyou GeWuYou.GFramework @@ -14,10 +12,7 @@ game;framework true true - true - portable false - GFramework README.md net8.0;net9.0;net10.0 false @@ -25,38 +20,30 @@ - - - - - - - - - - - - - - - - - GFramework.Godot\godot\extensions\NodeExtensions.cs - - - GFramework.Godot\godot\extensions\UnRegisterExtension.cs - - - - + + + + + + - - + - - - + + + + + + + + + + + + + + + + diff --git a/GFramework.sln b/GFramework.sln index 5fb5554..5af8702 100644 --- a/GFramework.sln +++ b/GFramework.sln @@ -8,7 +8,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Generator.Attrib EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core", "GFramework.Core\GFramework.Core.csproj", "{A6D5854D-79EA-487A-9ED9-396E6A1F8031}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core.Godot", "GFramework.Core.Godot\GFramework.Core.Godot.csproj", "{FC56D81A-3A3B-4B49-B318-363DFA0D8206}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot", "GFramework.Godot\GFramework.Godot.csproj", "{FC56D81A-3A3B-4B49-B318-363DFA0D8206}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game", "GFramework.Game\GFramework.Game.csproj", "{0B00816B-E8B2-4562-8C11-0C06CE761638}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -36,5 +38,9 @@ Global {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|Any CPU.Build.0 = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal