refactor(core): 移除过时的架构和音频管理组件

- 删除 AbstractArchitecture 类及其相关实现
- 移除 ArchitectureAnchorNode 锚点节点类
- 清理拖拽功能相关组件类
- 移除 NodeExtensions 扩展方法集
- 删除 UnRegisterExtension 扩展类
- 移除 AbstractAssetCatalogSystem 资源目录系统
- 删除 AbstractAudioManagerSystem 音频管理系统
This commit is contained in:
GwWuYou 2025-12-19 22:35:58 +08:00
parent f2159065fc
commit d7ef63f2c0
17 changed files with 0 additions and 2083 deletions

View File

@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>GeWuYou.GFramework.Core.Godot</PackageId>
<Product>GeWuYou.GFramework.Core.Godot</Product>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Godot.SourceGenerators" Version="4.5.1"/>
<PackageReference Include="GodotSharpEditor" Version="4.5.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
</ItemGroup>
<ItemGroup>
<Compile Remove="extensions\ControlExtensions.cs"/>
</ItemGroup>
</Project>

View File

@ -1,62 +0,0 @@
using GFramework.Core.architecture;
using Godot;
namespace GFramework.Core.Godot.architecture;
/// <summary>
/// 抽象架构类,为特定类型的架构提供基础实现框架
/// </summary>
/// <typeparam name="T">架构的具体类型必须继承自Architecture且能被实例化</typeparam>
public abstract class AbstractArchitecture<T> : Architecture<T> where T : Architecture<T>, new()
{
private const string ArchitectureName = "__GFrameworkArchitectureAnchor";
/// <summary>
/// 初始化架构,按顺序注册模型、系统和工具
/// </summary>
protected override void Init()
{
RegisterModels();
RegisterSystems();
RegisterUtilities();
AttachToGodotLifecycle();
}
/// <summary>
/// 将架构绑定到Godot生命周期中确保在场景树销毁时能够正确清理资源
/// 通过创建一个锚节点来监听场景树的销毁事件
/// </summary>
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);
}
/// <summary>
/// 注册工具抽象方法,由子类实现具体的工具注册逻辑
/// </summary>
protected abstract void RegisterUtilities();
/// <summary>
/// 注册系统抽象方法,由子类实现具体系统注册逻辑
/// </summary>
protected abstract void RegisterSystems();
/// <summary>
/// 注册模型抽象方法,由子类实现具体模型注册逻辑
/// </summary>
protected abstract void RegisterModels();
}

View File

@ -1,32 +0,0 @@
using Godot;
namespace GFramework.Core.Godot.architecture;
/// <summary>
/// 架构锚点节点类用于在Godot场景树中作为架构组件的根节点
/// 该类提供了退出时的回调绑定功能,可以在节点从场景树中移除时执行清理操作
/// </summary>
public partial class ArchitectureAnchorNode : Node
{
private Action? _onExit;
/// <summary>
/// 绑定节点退出时的回调动作
/// </summary>
/// <param name="onExit">当节点从场景树退出时要执行的动作</param>
public void Bind(Action onExit)
{
_onExit = onExit;
}
/// <summary>
/// 当节点从场景树中移除时调用此方法
/// 执行绑定的退出回调并清理引用
/// </summary>
public override void _ExitTree()
{
_onExit?.Invoke();
_onExit = null;
}
}

View File

@ -1,100 +0,0 @@
using GFramework.Core.architecture;
using GFramework.Core.controller;
using GFramework.Core.events;
using GFramework.Core.extensions;
using Godot;
namespace GFramework.Core.Godot.component;
/// <summary>
/// 抽象基类用于实现2D拖拽功能的组件。
/// 继承自Godot的Node类并实现了IController接口。
/// 提供了拖拽相关的信号定义以及基础属性配置。
/// </summary>
public abstract partial class AbstractDragDrop2DComponentBase : Node, IController
{
/// <summary>
/// 取消注册列表,用于管理需要在节点销毁时取消注册的对象
/// </summary>
protected readonly IUnRegisterList UnRegisterList = new UnRegisterList();
/// <summary>
/// 当拖拽被取消时触发的信号。
/// </summary>
/// <param name="startingPosition">拖拽起始位置。</param>
[Signal]
public delegate void DragCanceledEventHandler(Vector2 startingPosition);
/// <summary>
/// 当拖拽开始时触发的信号。
/// </summary>
[Signal]
public delegate void DragStartedEventHandler();
/// <summary>
/// 当拖拽结束并放置时触发的信号。
/// </summary>
/// <param name="startingPosition">拖拽起始位置。</param>
[Signal]
public delegate void DroppedEventHandler(Vector2 startingPosition);
/// <summary>
/// 是否启用拖拽功能。若为 false则忽略所有输入事件。
/// </summary>
public bool Enable { get; set; }
/// <summary>
/// 拖拽组的名称,用于区分不同的拖拽组。
/// </summary>
public string GroupName { get; set; } = "dragging";
/// <summary>
/// 获取或设置取消拖拽输入操作的名称
/// </summary>
public string CancelDragInputActionName { get; set; } = "cancel_drag";
/// <summary>
/// 获取或设置选择输入操作的名称
/// </summary>
public string SelectInputActionName { get; set; } = "select";
/// <summary>
/// 拖拽时元素的最大Z轴索引值。
/// </summary>
public int ZIndexMax { get; set; } = 99;
/// <summary>
/// 拖拽时元素的最小Z轴索引值。
/// </summary>
public int ZIndexMin { get; set; } = 0;
/// <summary>
/// 获取架构实例。
/// </summary>
/// <returns>返回实现IArchitecture接口的架构实例。</returns>
public abstract IArchitecture GetArchitecture();
/// <summary>
/// 表示是否正在拖拽操作的标志位。
/// </summary>
protected bool IsDragging;
/// <summary>
/// 表示拖拽操作中的偏移量,用于计算当前位置与起始位置的差值。
/// </summary>
protected Vector2 Offset = Vector2.Zero;
/// <summary>
/// 表示拖拽操作的起始位置坐标。
/// </summary>
protected Vector2 StartingPosition;
/// <summary>
/// 节点退出场景树时的回调方法。
/// 在节点从场景树移除前调用,用于清理资源。
/// </summary>
public override void _ExitTree()
{
UnRegisterList.UnRegisterAll();
}
}

View File

@ -1,127 +0,0 @@
using GFramework.Core.Godot.extensions;
using Godot;
namespace GFramework.Core.Godot.component;
/// <summary>
/// 抽象拖拽组件类,用于处理节点的拖放逻辑。
/// 实现了 IController 接口以支持架构通信,并通过信号通知拖拽事件的发生。
/// </summary>
public abstract partial class AbstractDragDropArea2DComponent : AbstractDragDrop2DComponentBase
{
/// <summary>
/// 目标区域,通常是可交互的游戏对象(如单位或物品)所在的碰撞区域。
/// </summary>
public required Area2D Target { get; set; }
/// <summary>
/// 节点准备就绪时的回调方法。
/// 在节点添加到场景树后调用,绑定目标区域的输入事件处理器。
/// </summary>
public override void _Ready()
{
Target = GetParent() as Area2D ?? throw new InvalidOperationException("Target must be an Area2D node.");
Target.InputEvent += OnTargetInputEvent;
}
/// <summary>
/// 处理输入事件的回调方法。
/// 根据当前拖拽状态和输入事件类型,执行相应的拖拽操作。
/// </summary>
/// <param name="event">输入事件对象</param>
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;
}
}
/// <summary>
/// 目标区域的输入事件处理器。
/// 当目标区域接收到输入事件时被调用,用于控制拖拽的开始和结束。
/// </summary>
/// <param name="viewport">事件发生的视口节点</param>
/// <param name="event">输入事件对象</param>
/// <param name="_">事件触点ID未使用</param>
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;
}
}
/// <summary>
/// 每帧更新逻辑,在拖拽过程中持续更新目标的位置。
/// </summary>
/// <param name="delta">与上一帧的时间间隔(秒)。</param>
public override void _Process(double delta)
{
if (IsDragging && Target.IsValidNode()) Target.GlobalPosition = Target.GetGlobalMousePosition() + Offset;
}
/// <summary>
/// 结束拖拽流程的基础方法。
/// 清除拖拽标志位并将目标从拖拽组中移除,恢复其层级顺序。
/// </summary>
private void EndDragging()
{
IsDragging = false;
Target.RemoveFromGroup(GroupName);
Target.ZIndex = ZIndexMin;
}
/// <summary>
/// 执行取消拖拽的操作。
/// 调用 EndDragging 并发出 DragCanceled 信号。
/// </summary>
private void CancelDragging()
{
EndDragging();
EmitSignalDragCanceled(StartingPosition);
}
/// <summary>
/// 开始拖拽操作。
/// 设置初始位置和偏移量,将目标加入拖拽组并提升显示层级,最后发出 DragStarted 信号。
/// </summary>
private void StartDragging()
{
IsDragging = true;
StartingPosition = Target.GlobalPosition;
Target.AddToGroup(GroupName);
Target.ZIndex = ZIndexMax;
Offset = Target.GlobalPosition - Target.GetGlobalMousePosition();
EmitSignalDragStarted();
}
/// <summary>
/// 完成一次拖拽操作。
/// 调用 EndDragging 方法并发出 Dropped 信号。
/// </summary>
private void Drop()
{
EndDragging();
EmitSignalDropped(StartingPosition);
}
}

View File

@ -1,243 +0,0 @@
using Godot;
namespace GFramework.Core.Godot.extensions;
/// <summary>
/// 节点扩展方法类提供对Godot节点的扩展功能
/// </summary>
public static class NodeExtensions
{
/// <summary>
/// 安全地将节点加入删除队列,在下一帧开始时释放节点资源
/// </summary>
/// <param name="node">要释放的节点实例</param>
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);
}
/// <summary>
/// 立即释放节点资源,不等待下一帧
/// </summary>
/// <param name="node">要立即释放的节点实例</param>
public static void FreeX(this Node? node)
{
// 检查节点是否为空
if (node is null) return;
// 检查节点实例是否有效
if (!GodotObject.IsInstanceValid(node)) return;
// 检查节点是否已经加入删除队列
if (node.IsQueuedForDeletion()) return;
// 立即释放节点资源
node.Free();
}
/// <summary>
/// 如果节点尚未进入场景树,则等待 ready 信号。
/// 如果已经在场景树中,则立刻返回。
/// </summary>
public static async Task WaitUntilReady(this Node node)
{
if (!node.IsInsideTree()) await node.ToSignal(node, Node.SignalName.Ready);
}
/// <summary>
/// 检查节点是否有效:
/// 1. 非 null
/// 2. Godot 实例仍然存在(未被释放)
/// 3. 已经加入 SceneTree
/// </summary>
public static bool IsValidNode(this Node? node)
{
return node is not null &&
GodotObject.IsInstanceValid(node) &&
node.IsInsideTree();
}
/// <summary>
/// 检查节点是否无效:
/// 1. 为 null或者
/// 2. Godot 实例已被释放,或者
/// 3. 尚未加入 SceneTree
/// 返回 true 表示该节点不可用。
/// </summary>
public static bool IsInvalidNode(this Node? node)
{
return node is null ||
!GodotObject.IsInstanceValid(node) ||
!node.IsInsideTree();
}
/// <summary>
/// 将当前节点的输入事件标记为已处理,防止事件继续向父节点传播。
/// </summary>
/// <param name="node">要处理输入事件的节点实例</param>
public static void SetInputAsHandled(this Node node)
{
// 获取节点的视口并标记输入事件为已处理
node.GetViewport().SetInputAsHandled();
}
/// <summary>
/// 设置节点所在场景树的暂停状态
/// </summary>
/// <param name="node">要操作的节点对象</param>
/// <param name="paused">暂停状态标识默认为true表示暂停false表示恢复运行</param>
public static void Paused(this Node node, bool paused = true)
{
var tree = node.GetTree();
tree.Paused = paused;
}
/// <summary>
/// 查找指定名称的子节点并将其转换为指定类型
/// </summary>
/// <typeparam name="T">要转换到的目标节点类型</typeparam>
/// <param name="node">要在其子节点中进行查找的父节点</param>
/// <param name="name">要查找的子节点名称</param>
/// <param name="recursive">是否递归查找所有层级的子节点默认为true</param>
/// <returns>找到的子节点转换为指定类型后的结果如果未找到或转换失败则返回null</returns>
public static T? FindChildX<T>(this Node node, string name, bool recursive = true)
where T : Node
{
var child = node.FindChild(name, recursive, false);
return child as T;
}
/// <summary>
/// 获取指定路径的节点,如果不存在则创建一个新的节点
/// </summary>
/// <typeparam name="T">节点类型必须继承自Node且具有无参构造函数</typeparam>
/// <param name="node">父节点</param>
/// <param name="path">节点路径</param>
/// <returns>找到的现有节点或新创建的节点</returns>
public static T GetOrCreateNode<T>(this Node node, string path)
where T : Node, new()
{
// 尝试获取现有节点
if (node.GetNodeOrNull<T>(path) is { } found)
return found;
// 创建新节点并添加到父节点
var created = new T();
node.AddChild(created);
created.Name = path;
return created;
}
/// <summary>
/// 异步添加子节点并等待其准备就绪
/// </summary>
/// <param name="parent">父节点</param>
/// <param name="child">要添加的子节点</param>
/// <returns>异步任务</returns>
public static async Task AddChildX(this Node parent, Node child)
{
parent.AddChild(child);
await child.WaitUntilReady();
}
/// <summary>
/// 获取父节点并将其转换为指定类型
/// </summary>
/// <typeparam name="T">要转换到的目标节点类型</typeparam>
/// <param name="node">当前节点</param>
/// <returns>父节点转换为指定类型后的结果如果转换失败则返回null</returns>
public static T? GetParentX<T>(this Node node) where T : Node
{
return node.GetParent() as T;
}
/// <summary>
/// 获取场景树的根节点的第一个子节点
/// </summary>
/// <param name="node">扩展方法的目标节点</param>
/// <returns>根节点的第一个子节点</returns>
public static Node GetRootNodeX(this Node node)
{
return node.GetTree().Root.GetChild(0);
}
/// <summary>
/// 遍历节点的所有子节点,并对指定类型的子节点执行特定操作
/// </summary>
/// <typeparam name="T">要筛选的节点类型</typeparam>
/// <param name="node">扩展方法的目标节点</param>
/// <param name="action">对符合条件的子节点执行的操作</param>
public static void ForEachChild<T>(this Node node, Action<T> action) where T : Node
{
foreach (var child in node.GetChildren())
if (child is T t)
action(t);
}
/// <summary>
/// 禁用节点所在场景树的输入处理功能
/// </summary>
/// <param name="node">扩展方法的目标节点</param>
public static void DisableInput(this Node node)
{
// 检查根节点是否为Viewport类型如果是则禁用GUI输入
if (node.GetTree().Root is Viewport vp)
vp.GuiDisableInput = true;
}
/// <summary>
/// 启用节点所在场景树的输入处理功能
/// </summary>
/// <param name="node">扩展方法的目标节点</param>
public static void EnableInput(this Node node)
{
// 检查根节点是否为Viewport类型如果是则启用GUI输入
if (node.GetTree().Root is Viewport vp)
vp.GuiDisableInput = false;
}
/// <summary>
/// 打印节点的路径信息到控制台
/// </summary>
/// <param name="node">扩展方法的目标节点</param>
public static void LogNodePath(this Node node)
{
GD.Print($"[NodePath] {node.GetPath()}");
}
/// <summary>
/// 以树形结构递归打印节点及其所有子节点的名称
/// </summary>
/// <param name="node">扩展方法的目标节点</param>
/// <param name="indent">缩进字符串,用于显示层级关系</param>
public static void PrintTreeX(this Node node, string indent = "")
{
GD.Print($"{indent}- {node.Name}");
// 递归打印所有子节点
foreach (var child in node.GetChildren())
child.PrintTreeX(indent + " ");
}
/// <summary>
/// 安全地延迟调用指定方法,确保节点有效后再执行
/// </summary>
/// <param name="node">扩展方法的目标节点</param>
/// <param name="method">要延迟调用的方法名</param>
public static void SafeCallDeferred(this Node? node, string method)
{
// 检查节点是否为空且实例是否有效,有效时才执行延迟调用
if (node != null && GodotObject.IsInstanceValid(node))
node.CallDeferred(method);
}
}

View File

@ -1,23 +0,0 @@
using GFramework.Core.events;
using Godot;
namespace GFramework.Core.Godot.extensions;
/// <summary>
/// 提供取消注册扩展方法的静态类
/// </summary>
public static class UnRegisterExtension
{
/// <summary>
/// 当节点退出场景树时自动取消注册监听器
/// </summary>
/// <param name="unRegister">需要在节点退出时被取消注册的监听器接口实例</param>
/// <param name="node">Godot节点对象当该节点退出场景树时触发取消注册操作</param>
/// <returns>返回传入的原始IUnRegister实例支持链式调用</returns>
public static IUnRegister UnRegisterWhenNodeExitTree(this IUnRegister unRegister, Node node)
{
// 监听节点的TreeExiting事件在节点即将退出场景树时执行取消注册操作
node.TreeExiting += unRegister.UnRegister;
return unRegister;
}
}

View File

@ -1,117 +0,0 @@
using GFramework.Core.system;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源目录系统抽象基类,用于管理和注册游戏中的场景和资源
/// </summary>
public abstract class AbstractAssetCatalogSystem : AbstractSystem, IAssetCatalogSystem
{
private readonly Dictionary<string, AssetCatalog.SceneId> _scenes = new();
private readonly Dictionary<string, AssetCatalog.ResourceId> _resources = new();
/// <summary>
/// 系统初始化时调用,用于注册所有资产
/// </summary>
protected override void OnInit()
{
RegisterAssets();
}
/// <summary>
/// 抽象方法,由子类实现具体的资产注册逻辑
/// </summary>
protected abstract void RegisterAssets();
#region Register or Module 使
/// <summary>
/// 注册场景资源
/// </summary>
/// <param name="key">场景的唯一标识键</param>
/// <param name="path">场景资源的路径</param>
/// <exception cref="InvalidOperationException">当场景键已存在时抛出异常</exception>
public void RegisterScene(string key, string path)
{
if (_scenes.ContainsKey(key))
throw new InvalidOperationException($"Scene key duplicated: {key}");
_scenes[key] = new AssetCatalog.SceneId(path);
}
/// <summary>
/// 注册场景资源
/// </summary>
/// <param name="mapping">包含键和场景标识符的映射对象</param>
/// <exception cref="InvalidOperationException">当场景键已存在时抛出异常</exception>
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}");
}
/// <summary>
/// 注册普通资源
/// </summary>
/// <param name="key">资源的唯一标识键</param>
/// <param name="path">资源的路径</param>
/// <exception cref="InvalidOperationException">当资源键已存在时抛出异常</exception>
public void RegisterResource(string key, string path)
{
if (_resources.ContainsKey(key))
throw new InvalidOperationException($"Resource key duplicated: {key}");
_resources[key] = new AssetCatalog.ResourceId(path);
}
/// <summary>
/// 注册普通资源
/// </summary>
/// <param name="mapping">包含键和资源标识符的映射对象</param>
/// <exception cref="InvalidOperationException">当资源键已存在时抛出异常</exception>
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
/// <summary>
/// 根据键获取场景ID
/// </summary>
/// <param name="key">场景的唯一标识键</param>
/// <returns>对应的场景ID</returns>
public AssetCatalog.SceneId GetScene(string key) => _scenes[key];
/// <summary>
/// 根据键获取资源ID
/// </summary>
/// <param name="key">资源的唯一标识键</param>
/// <returns>对应的资源ID</returns>
public AssetCatalog.ResourceId GetResource(string key) => _resources[key];
/// <summary>
/// 检查是否存在指定键的场景
/// </summary>
/// <param name="key">场景的唯一标识键</param>
/// <returns>如果存在返回true否则返回false</returns>
public bool HasScene(string key) => _scenes.ContainsKey(key);
/// <summary>
/// 检查是否存在指定键的资源
/// </summary>
/// <param name="key">资源的唯一标识键</param>
/// <returns>如果存在返回true否则返回false</returns>
public bool HasResource(string key) => _resources.ContainsKey(key);
#endregion
}

View File

@ -1,558 +0,0 @@
using GFramework.Core.extensions;
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 音频管理器抽象基类,提供音频播放的基础实现
/// </summary>
public abstract class AbstractAudioManagerSystem : AbstractSystem, IAudioManagerSystem
{
/// <summary>
/// 音频资源加载系统依赖
/// </summary>
protected IResourceLoadSystem? ResourceLoadSystem;
/// <summary>
/// 资源目录系统依赖
/// </summary>
protected IAssetCatalogSystem? AssetCatalogSystem;
/// <summary>
/// 背景音乐播放器
/// </summary>
protected AudioStreamPlayer? MusicPlayer;
/// <summary>
/// 音效播放器列表
/// </summary>
protected readonly List<AudioStreamPlayer> SoundPlayers = [];
/// <summary>
/// 可用音效播放器队列
/// </summary>
protected readonly Queue<AudioStreamPlayer> AvailableSoundPlayers = new();
/// <summary>
/// 3D音效播放器列表
/// </summary>
protected readonly List<AudioStreamPlayer3D> Sound3DPlayers = [];
/// <summary>
/// 可用3D音效播放器队列
/// </summary>
protected readonly Queue<AudioStreamPlayer3D> AvailableSound3DPlayers = new();
/// <summary>
/// 资源工厂系统依赖
/// </summary>
protected IResourceFactorySystem? ResourceFactorySystem;
/// <summary>
/// 背景音乐音量
/// </summary>
protected float MusicVolume = 1.0f;
/// <summary>
/// 音效音量
/// </summary>
protected float SoundVolume = 1.0f;
/// <summary>
/// 主音量
/// </summary>
protected float MasterVolume = 1.0f;
/// <summary>
/// 特效音量
/// </summary>
protected float SfxVolume = 1.0f;
/// <summary>
/// 语音音量
/// </summary>
protected float VoiceVolume = 1.0f;
/// <summary>
/// 环境音量
/// </summary>
protected float AmbientVolume = 1.0f;
/// <summary>
/// 音乐淡入淡出动画
/// </summary>
protected Tween? MusicFadeTween;
/// <summary>
/// 最大同时播放的音效数量
/// </summary>
protected const int MaxSoundPlayers = 10;
/// <summary>
/// 最大同时播放的3D音效数量
/// </summary>
protected const int MaxSound3DPlayers = 5;
/// <summary>
/// 所有者节点的抽象属性
/// </summary>
protected abstract Node Owner { get; }
/// <summary>
/// 系统初始化方法
/// </summary>
protected override void OnInit()
{
// 获取依赖的系统
ResourceLoadSystem = this.GetSystem<IResourceLoadSystem>();
AssetCatalogSystem = this.GetSystem<IAssetCatalogSystem>();
ResourceFactorySystem = this.GetSystem<IResourceFactorySystem>();
// 初始化背景音乐播放器
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);
}
}
/// <summary>
/// 当音效播放完成时的回调
/// </summary>
/// <param name="player">完成播放的音频播放器</param>
private void OnSoundFinished(AudioStreamPlayer player)
{
// 将播放器放回可用队列
AvailableSoundPlayers.Enqueue(player);
}
/// <summary>
/// 当3D音效播放完成时的回调
/// </summary>
/// <param name="player">完成播放的3D音频播放器</param>
private void OnSound3DFinished(AudioStreamPlayer3D player)
{
// 将播放器放回可用队列
AvailableSound3DPlayers.Enqueue(player);
}
/// <summary>
/// 播放背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="loop">是否循环播放</param>
public virtual void PlayMusic(string audioPath, float volume = 1.0f, bool loop = true)
{
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null || MusicPlayer == null) return;
// 停止当前正在进行的淡入淡出效果
MusicFadeTween?.Kill();
MusicPlayer.Stream = audioStream;
MusicPlayer.VolumeDb = LinearToDb(volume * MusicVolume * MasterVolume);
MusicPlayer.Play();
}
/// <summary>
/// 通过资源ID播放背景音乐
/// </summary>
/// <param name="musicId">音乐资源ID</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="loop">是否循环播放</param>
public virtual void PlayMusic(AssetCatalog.ResourceId musicId, float volume = 1.0f, bool loop = true)
{
PlayMusic(musicId.Path, volume, loop);
}
/// <summary>
/// 播放音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
public virtual void PlaySound(string audioPath, float volume = 1.0f, float pitch = 1.0f)
{
if (AvailableSoundPlayers.Count == 0) return;
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null) return;
var player = AvailableSoundPlayers.Dequeue();
player.Stream = audioStream;
player.VolumeDb = LinearToDb(volume * SoundVolume * MasterVolume);
player.Play();
}
/// <summary>
/// 播放特效音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
public virtual void PlaySfx(string audioPath, float volume = 1.0f, float pitch = 1.0f)
{
if (AvailableSoundPlayers.Count == 0) return;
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null) return;
var player = AvailableSoundPlayers.Dequeue();
player.Stream = audioStream;
player.VolumeDb = LinearToDb(volume * SfxVolume * MasterVolume);
player.Play();
}
/// <summary>
/// 播放语音
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
public virtual void PlayVoice(string audioPath, float volume = 1.0f, float pitch = 1.0f)
{
if (AvailableSoundPlayers.Count == 0) return;
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null) return;
var player = AvailableSoundPlayers.Dequeue();
player.Stream = audioStream;
player.VolumeDb = LinearToDb(volume * VoiceVolume * MasterVolume);
player.Play();
}
/// <summary>
/// 播放环境音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
public virtual void PlayAmbient(string audioPath, float volume = 1.0f, float pitch = 1.0f)
{
if (AvailableSoundPlayers.Count == 0) return;
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null) return;
var player = AvailableSoundPlayers.Dequeue();
player.Stream = audioStream;
player.VolumeDb = LinearToDb(volume * AmbientVolume * MasterVolume);
player.Play();
}
/// <summary>
/// 通过资源ID播放音效
/// </summary>
/// <param name="soundId">音效资源ID</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
public virtual void PlaySound(AssetCatalog.ResourceId soundId, float volume = 1.0f, float pitch = 1.0f)
{
PlaySound(soundId.Path, volume, pitch);
}
/// <summary>
/// 播放3D音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="position">3D空间中的位置</param>
/// <param name="volume">音量大小范围0-1</param>
public virtual void PlaySound3D(string audioPath, Vector3 position, float volume = 1.0f)
{
if (AvailableSound3DPlayers.Count == 0) return;
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(audioPath);
if (audioStream == null) return;
var player = AvailableSound3DPlayers.Dequeue();
player.Stream = audioStream;
player.VolumeDb = LinearToDb(volume * SoundVolume * MasterVolume);
player.Position = position;
player.Play();
}
/// <summary>
/// 停止背景音乐
/// </summary>
public virtual void StopMusic()
{
MusicFadeTween?.Kill();
MusicPlayer?.Stop();
}
/// <summary>
/// 暂停背景音乐
/// </summary>
public virtual void PauseMusic()
{
MusicFadeTween?.Kill();
// todo 需要记录音乐播放位置,以便恢复播放时从正确位置开始
}
/// <summary>
/// 恢复背景音乐播放
/// </summary>
public virtual void ResumeMusic()
{
MusicPlayer?.Play();
}
/// <summary>
/// 设置背景音乐音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetMusicVolume(float volume)
{
MusicVolume = volume;
if (MusicPlayer != null)
{
MusicPlayer.VolumeDb = LinearToDb(MusicVolume * MasterVolume);
}
}
/// <summary>
/// 获取背景音乐音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
public virtual float GetMusicVolume()
{
return MusicVolume;
}
/// <summary>
/// 设置音效音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetSoundVolume(float volume)
{
SoundVolume = volume;
}
/// <summary>
/// 获取音效音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
public virtual float GetSoundVolume()
{
return SoundVolume;
}
/// <summary>
/// 设置主音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetMasterVolume(float volume)
{
MasterVolume = volume;
// 更新音乐音量
if (MusicPlayer != null)
{
MusicPlayer.VolumeDb = LinearToDb(MusicVolume * MasterVolume);
}
}
/// <summary>
/// 获取主音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
public virtual float GetMasterVolume()
{
return MasterVolume;
}
/// <summary>
/// 设置SFX音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetSfxVolume(float volume)
{
SfxVolume = volume;
}
/// <summary>
/// 获取SFX音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
public virtual float GetSfxVolume()
{
return SfxVolume;
}
/// <summary>
/// 设置语音音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetVoiceVolume(float volume)
{
VoiceVolume = volume;
}
/// <summary>
/// 获取语音音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
public virtual float GetVoiceVolume()
{
return VoiceVolume;
}
/// <summary>
/// 设置环境音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
public virtual void SetAmbientVolume(float volume)
{
AmbientVolume = volume;
}
/// <summary>
/// 获取环境音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
public virtual float GetAmbientVolume()
{
return AmbientVolume;
}
/// <summary>
/// 检查背景音乐是否正在播放
/// </summary>
/// <returns>正在播放返回true否则返回false</returns>
public virtual bool IsMusicPlaying()
{
return MusicPlayer?.Playing ?? false;
}
/// <summary>
/// 淡入背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="duration">淡入持续时间(秒)</param>
/// <param name="volume">目标音量</param>
public virtual void FadeInMusic(string audioPath, float duration, float volume = 1.0f)
{
var audioStream = ResourceLoadSystem?.LoadResource<AudioStream>(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);
}
/// <summary>
/// 淡出背景音乐
/// </summary>
/// <param name="duration">淡出持续时间(秒)</param>
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()));
}
/// <summary>
/// 设置低通滤波器强度
/// </summary>
/// <param name="amount">滤波器强度范围0-1</param>
public virtual void SetLowPassFilter(float amount)
{
// TODO: 实现低通滤波器效果
// 可以通过AudioEffectLowPassFilter实现
}
/// <summary>
/// 设置音频混响效果
/// </summary>
/// <param name="roomSize">房间大小</param>
/// <param name="damping">阻尼</param>
/// <param name="wetLevel">湿声级别</param>
public virtual void SetReverb(float roomSize, float damping, float wetLevel)
{
// TODO: 实现音频混响效果
// 可以通过AudioEffectReverb实现
}
/// <summary>
/// 将线性音量值转换为分贝值
/// </summary>
/// <param name="linear">线性音量值0-1</param>
/// <returns>分贝值</returns>
protected static float LinearToDb(float linear)
{
return linear > 0 ? 20 * Mathf.Log(linear) : -100;
}
/// <summary>
/// 将分贝值转换为线性音量值
/// </summary>
/// <param name="db">分贝值</param>
/// <returns>线性音量值0-1</returns>
protected static float DbToLinear(float db)
{
return db > -100 ? Mathf.Exp(db / 20) : 0;
}
/// <summary>
/// 系统销毁时清理资源
/// </summary>
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();
}
}

View File

@ -1,109 +0,0 @@
using GFramework.Core.events;
using GFramework.Core.extensions;
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源工厂系统抽象基类,用于统一管理各类资源的创建与预加载逻辑。
/// 提供注册场景和资源的方法,并通过依赖的资源加载系统和资产目录系统完成实际资源的获取与构造。
/// </summary>
public abstract class AbstractResourceFactorySystem : AbstractSystem, IResourceFactorySystem
{
private ResourceFactory.Registry? _registry;
private IResourceLoadSystem? _resourceLoadSystem;
private IAssetCatalogSystem? _assetCatalogSystem;
/// <summary>
/// 系统初始化方法,在系统启动时执行一次。
/// 初始化资源注册表,并获取依赖的资源加载系统和资产目录系统。
/// 最后执行所有已注册资源的预加载操作。
/// </summary>
protected override void OnInit()
{
_registry = new ResourceFactory.Registry();
_resourceLoadSystem = this.GetSystem<IResourceLoadSystem>();
_assetCatalogSystem = this.GetSystem<IAssetCatalogSystem>();
// 监听架构初始化事件
this.RegisterEvent<ArchitectureEvents.ArchitectureInitializedEvent>(_ =>
{
// 注册资源
RegisterResources();
// 预加载所有资源
_registry.PreloadAll();
});
}
/// <summary>
/// 注册系统所需的各种资源类型。由子类实现具体注册逻辑。
/// </summary>
protected abstract void RegisterResources();
/// <summary>
/// 根据指定的键获取资源工厂函数。
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
/// <param name="key">资源键</param>
/// <returns>返回创建指定类型资源的工厂函数</returns>
public Func<T> GetFactory<T>(string key)=>_registry!.ResolveFactory<T>(key);
/// <summary>
/// 根据资产目录映射信息获取资源工厂函数。
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
/// <param name="mapping">资产目录映射信息</param>
/// <returns>返回创建指定类型资源的工厂函数</returns>
public Func<T> GetFactory<T>(AssetCatalog.AssetCatalogMapping mapping) => _registry!.ResolveFactory<T>(mapping.Key);
#region Register Helpers
/// <summary>
/// 注册场景资源工厂。
/// 根据场景键名获取场景ID并将场景加载工厂注册到注册表中。
/// </summary>
/// <typeparam name="T">场景节点类型必须继承自Node</typeparam>
/// <param name="sceneKey">场景在资产目录中的键名</param>
/// <param name="preload">是否需要预加载该场景资源</param>
protected void RegisterScene<T>(
string sceneKey,
bool preload = false)
where T : Node
{
var id = _assetCatalogSystem!.GetScene(sceneKey);
_registry!.Register(
sceneKey,
_resourceLoadSystem!.GetOrRegisterSceneFactory<T>(id),
preload
);
}
/// <summary>
/// 注册普通资源工厂。
/// 根据资源键名获取资源ID并将资源加载工厂注册到注册表中。
/// </summary>
/// <typeparam name="T">资源类型必须继承自Resource</typeparam>
/// <param name="resourceKey">资源在资产目录中的键名</param>
/// <param name="duplicate">是否需要复制资源实例</param>
/// <param name="preload">是否需要预加载该资源</param>
protected void RegisterResource<T>(
string resourceKey,
bool duplicate = false,
bool preload = false)
where T : Resource
{
var id = _assetCatalogSystem!.GetResource(resourceKey);
_registry!.Register(
resourceKey,
_resourceLoadSystem!.GetOrRegisterResourceFactory<T>(id, duplicate),
preload
);
}
#endregion
}

View File

@ -1,39 +0,0 @@

namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源目录类,用于定义和管理游戏中的场景和资源标识符
/// </summary>
public static class AssetCatalog
{
/// <summary>
/// 资源标识符接口,定义了资源路径的访问接口
/// </summary>
public interface IAssetId
{
/// <summary>
/// 获取资源的路径
/// </summary>
string Path { get; }
}
/// <summary>
/// 资源目录映射结构体,用于存储资源目录的键值对映射关系
/// </summary>
/// <param name="Key">资源目录的键</param>
/// <param name="Id">资源标识符</param>
public readonly record struct AssetCatalogMapping(string Key, IAssetId Id);
/// <summary>
/// 场景标识符结构体,用于唯一标识一个场景资源
/// </summary>
/// <param name="Path">场景资源的路径</param>
public readonly record struct SceneId(string Path) : IAssetId;
/// <summary>
/// 资源标识符结构体,用于唯一标识一个游戏资源
/// </summary>
/// <param name="Path">游戏资源的路径</param>
public readonly record struct ResourceId(string Path) : IAssetId;
}

View File

@ -1,37 +0,0 @@
using GFramework.Core.system;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源目录系统接口,用于管理场景和资源的获取与查询
/// </summary>
public interface IAssetCatalogSystem : ISystem
{
/// <summary>
/// 根据键名获取场景标识符
/// </summary>
/// <param name="key">场景的唯一键名</param>
/// <returns>返回对应的场景ID</returns>
AssetCatalog.SceneId GetScene(string key);
/// <summary>
/// 根据键名获取资源标识符
/// </summary>
/// <param name="key">资源的唯一键名</param>
/// <returns>返回对应的资源ID</returns>
AssetCatalog.ResourceId GetResource(string key);
/// <summary>
/// 检查是否存在指定键名的场景
/// </summary>
/// <param name="key">要检查的场景键名</param>
/// <returns>如果存在返回true否则返回false</returns>
bool HasScene(string key);
/// <summary>
/// 检查是否存在指定键名的资源
/// </summary>
/// <param name="key">要检查的资源键名</param>
/// <returns>如果存在返回true否则返回false</returns>
bool HasResource(string key);
}

View File

@ -1,179 +0,0 @@
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 音频管理器系统接口,用于统一管理背景音乐和音效的播放
/// </summary>
public interface IAudioManagerSystem : ISystem
{
/// <summary>
/// 播放背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="loop">是否循环播放</param>
void PlayMusic(string audioPath, float volume = 1.0f, bool loop = true);
/// <summary>
/// 播放音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
void PlaySound(string audioPath, float volume = 1.0f, float pitch = 1.0f);
/// <summary>
/// 播放特效音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
void PlaySfx(string audioPath, float volume = 1.0f, float pitch = 1.0f);
/// <summary>
/// 播放语音
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
void PlayVoice(string audioPath, float volume = 1.0f, float pitch = 1.0f);
/// <summary>
/// 播放环境音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="volume">音量大小范围0-1</param>
/// <param name="pitch">音调调整</param>
void PlayAmbient(string audioPath, float volume = 1.0f, float pitch = 1.0f);
/// <summary>
/// 停止背景音乐
/// </summary>
void StopMusic();
/// <summary>
/// 暂停背景音乐
/// </summary>
void PauseMusic();
/// <summary>
/// 恢复背景音乐播放
/// </summary>
void ResumeMusic();
/// <summary>
/// 设置背景音乐音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetMusicVolume(float volume);
/// <summary>
/// 获取背景音乐音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
float GetMusicVolume();
/// <summary>
/// 设置音效音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetSoundVolume(float volume);
/// <summary>
/// 获取音效音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
float GetSoundVolume();
/// <summary>
/// 设置特效音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetSfxVolume(float volume);
/// <summary>
/// 获取特效音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
float GetSfxVolume();
/// <summary>
/// 设置语音音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetVoiceVolume(float volume);
/// <summary>
/// 获取语音音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
float GetVoiceVolume();
/// <summary>
/// 设置环境音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetAmbientVolume(float volume);
/// <summary>
/// 获取环境音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
float GetAmbientVolume();
/// <summary>
/// 设置主音量
/// </summary>
/// <param name="volume">音量大小范围0-1</param>
void SetMasterVolume(float volume);
/// <summary>
/// 获取主音量
/// </summary>
/// <returns>音量大小范围0-1</returns>
float GetMasterVolume();
/// <summary>
/// 检查背景音乐是否正在播放
/// </summary>
/// <returns>正在播放返回true否则返回false</returns>
bool IsMusicPlaying();
/// <summary>
/// 淡入背景音乐
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="duration">淡入持续时间(秒)</param>
/// <param name="volume">目标音量</param>
void FadeInMusic(string audioPath, float duration, float volume = 1.0f);
/// <summary>
/// 淡出背景音乐
/// </summary>
/// <param name="duration">淡出持续时间(秒)</param>
void FadeOutMusic(float duration);
/// <summary>
/// 播放3D音效
/// </summary>
/// <param name="audioPath">音频文件路径</param>
/// <param name="position">3D空间中的位置</param>
/// <param name="volume">音量大小范围0-1</param>
void PlaySound3D(string audioPath, Vector3 position, float volume = 1.0f);
/// <summary>
/// 设置低通滤波器强度
/// </summary>
/// <param name="amount">滤波器强度范围0-1</param>
void SetLowPassFilter(float amount);
/// <summary>
/// 设置音频混响效果
/// </summary>
/// <param name="roomSize">房间大小</param>
/// <param name="damping">阻尼</param>
/// <param name="wetLevel">湿声级别</param>
void SetReverb(float roomSize, float damping, float wetLevel);
}

View File

@ -1,26 +0,0 @@
using GFramework.Core.system;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源工厂系统接口,用于获取指定类型的资源创建函数
/// </summary>
public interface IResourceFactorySystem : ISystem
{
/// <summary>
/// 根据指定键名获取指定类型T的资源创建函数
/// </summary>
/// <typeparam name="T">要获取创建函数的资源类型</typeparam>
/// <param name="key">用于标识资源的键名</param>
/// <returns>返回一个创建T类型实例的函数委托</returns>
Func<T> GetFactory<T>(string key);
/// <summary>
/// 根据资产目录映射获取指定类型T的资源创建函数
/// </summary>
/// <typeparam name="T">要获取创建函数的资源类型</typeparam>
/// <param name="mapping">资产目录映射信息</param>
/// <returns>返回一个创建T类型实例的函数委托</returns>
Func<T> GetFactory<T>(AssetCatalog.AssetCatalogMapping mapping);
}

View File

@ -1,68 +0,0 @@
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源加载系统接口,提供资源和场景的加载、实例化、预加载等功能
/// </summary>
public interface IResourceLoadSystem : ISystem
{
/// <summary>
/// 加载指定路径的资源
/// </summary>
/// <typeparam name="T">资源类型必须继承自Resource</typeparam>
/// <param name="path">资源路径</param>
/// <returns>加载的资源实例</returns>
public T? LoadResource<T>(string path) where T : Resource;
/// <summary>
/// 获取场景加载器,用于延迟加载场景
/// </summary>
/// <param name="path">场景路径</param>
/// <returns>场景的延迟加载包装器</returns>
public Lazy<PackedScene> GetSceneLoader(string path);
/// <summary>
/// 创建指定路径场景的实例
/// </summary>
/// <typeparam name="T">节点类型必须继承自Node</typeparam>
/// <param name="path">场景路径</param>
/// <returns>场景实例化的节点对象</returns>
public T? CreateInstance<T>(string path) where T : Node;
/// <summary>
/// 获取或注册场景工厂函数
/// </summary>
/// <typeparam name="T">节点类型必须继承自Node</typeparam>
/// <param name="id">场景资源标识符</param>
/// <returns>创建场景实例的工厂函数</returns>
public Func<T> GetOrRegisterSceneFactory<T>(AssetCatalog.SceneId id) where T : Node;
/// <summary>
/// 获取或注册资源工厂函数
/// </summary>
/// <typeparam name="T">资源类型必须继承自Node</typeparam>
/// <param name="id">资源标识符</param>
/// <param name="duplicate">是否创建副本默认为false</param>
/// <returns>创建资源实例的工厂函数</returns>
public Func<T> GetOrRegisterResourceFactory<T>(AssetCatalog.ResourceId id, bool duplicate = false)
where T : Resource;
/// <summary>
/// 预加载指定路径的多个资源
/// </summary>
/// <param name="paths">需要预加载的资源路径集合</param>
public void Preload(IEnumerable<string> paths);
/// <summary>
/// 卸载指定路径的资源
/// </summary>
/// <param name="path">需要卸载的资源路径</param>
public void Unload(string path);
/// <summary>
/// 清除所有已加载的资源
/// </summary>
public void ClearAll();
}

View File

@ -1,130 +0,0 @@
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源工厂类,用于注册和解析各种资源的创建工厂
/// </summary>
public static class ResourceFactory
{
/// <summary>
/// 可预加载条目接口,定义了是否需要预加载以及执行工厂的方法
/// </summary>
private interface IPreloadableEntry
{
/// <summary>
/// 获取一个值,表示该资源是否需要预加载
/// </summary>
bool Preload { get; }
/// <summary>
/// 执行与该条目关联的工厂方法
/// </summary>
void ExecuteFactory();
/// <summary>
/// 获取资源类型
/// </summary>
Type ResourceType { get; }
/// <summary>
/// 获取资源键值
/// </summary>
string Key { get; }
}
/// <summary>
/// 表示一个具体的资源工厂条目,实现 IPreloadableEntry 接口
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
private sealed class Entry<T>(string key, Func<T> factory, bool preload) : IPreloadableEntry
{
/// <summary>
/// 获取用于创建资源的工厂函数
/// </summary>
public Func<T> Factory { get; } = factory;
/// <summary>
/// 获取一个值,表示该资源是否需要预加载
/// </summary>
public bool Preload { get; } = preload;
/// <summary>
/// 执行工厂函数以创建资源实例
/// </summary>
public void ExecuteFactory() => Factory();
/// <summary>
/// 获取资源的类型
/// </summary>
public Type ResourceType => typeof(T);
/// <summary>
/// 获取资源的键值
/// </summary>
public string Key { get; } = key;
}
/// <summary>
/// 工厂注册表,管理所有已注册的资源工厂
/// </summary>
internal sealed class Registry
{
/// <summary>
/// 存储所有已注册的工厂函数,键为资源类型,值为对应的工厂条目对象
/// </summary>
private readonly Dictionary<(Type type, string key), IPreloadableEntry> _factories = new();
/// <summary>
/// 注册指定类型的资源工厂
/// </summary>
/// <typeparam name="T">要注册的资源类型</typeparam>
/// <param name="key">键</param>
/// <param name="factory">创建该类型资源的工厂函数</param>
/// <param name="preload">是否需要预加载该资源默认为false</param>
public void Register<T>(string key, Func<T> 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<T>(key, factory, preload);
}
/// <summary>
/// 解析并获取指定类型的工厂函数
/// </summary>
/// <typeparam name="T">要获取工厂函数的资源类型</typeparam>
/// <param name="key">资源键</param>
/// <returns>指定类型的工厂函数</returns>
/// <exception cref="InvalidOperationException">当指定类型的工厂未注册时抛出异常</exception>
public Func<T> ResolveFactory<T>(string key)
{
var dictKey = (typeof(T), key);
if (_factories.TryGetValue(dictKey, out var entry)
&& entry is Entry<T> typed)
{
return typed.Factory;
}
throw new InvalidOperationException(
$"Factory not registered: {typeof(T).Name} with key '{key}'");
}
/// <summary>
/// 预加载所有标记为需要预加载的资源
/// </summary>
public void PreloadAll()
{
// 遍历所有已注册的工厂
foreach (var entry in _factories.Values.Where(entry => entry.Preload))
{
// 执行其工厂方法进行预加载
entry.ExecuteFactory();
}
}
}
}

View File

@ -1,209 +0,0 @@
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源加载系统用于统一管理和缓存Godot资源如场景、纹理等的加载与实例化。
/// 提供基础加载、场景实例化、资源工厂注册以及缓存管理功能。
/// </summary>
public class ResourceLoadSystem : AbstractSystem, IResourceLoadSystem
{
/// <summary>
/// 已加载的资源缓存字典键为资源路径值为已加载的Resource对象。
/// </summary>
private readonly Dictionary<string, Resource> _loadedResources = new();
/// <summary>
/// 场景懒加载器缓存键为场景路径值为延迟加载的PackedScene对象。
/// </summary>
private readonly Dictionary<string, Lazy<PackedScene>> _sceneLoaders = new();
/// <summary>
/// 场景实例化工厂委托缓存键为场景路径值为创建该场景实例的Func委托。
/// </summary>
private readonly Dictionary<string, Delegate> _sceneFactories = new();
/// <summary>
/// 资源获取/复制工厂委托缓存键为资源路径值为获取或复制资源的Func委托。
/// </summary>
private readonly Dictionary<string, Delegate> _resourceFactories = new();
/// <summary>
/// 初始化方法,在系统初始化时打印日志信息。
/// </summary>
protected override void OnInit()
{
}
#region
/// <summary>
/// 加载指定类型的资源并进行缓存。如果资源已经加载过则直接从缓存中返回。
/// </summary>
/// <typeparam name="T">要加载的资源类型必须继承自Resource。</typeparam>
/// <param name="path">资源在项目中的相对路径。</param>
/// <returns>成功加载的资源对象若路径无效或加载失败则返回null。</returns>
public T? LoadResource<T>(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<T>(path);
if (res == null)
{
GD.PrintErr($"[ResourceLoadSystem] Load failed: {path}");
return null;
}
_loadedResources[path] = res;
return res;
}
/// <summary>
/// 获取一个场景的懒加载器用于按需加载PackedScene资源。
/// 若对应路径尚未注册加载器则会自动创建一个新的Lazy实例。
/// </summary>
/// <param name="path">场景文件的相对路径。</param>
/// <returns>表示该场景懒加载逻辑的Lazy&lt;PackedScene&gt;对象。</returns>
public Lazy<PackedScene> GetSceneLoader(string path)
{
if (_sceneLoaders.TryGetValue(path, out var loader))
return loader;
loader = new Lazy<PackedScene>(() =>
{
var scene = LoadResource<PackedScene>(path);
return scene ?? throw new InvalidOperationException($"Failed to load scene: {path}");
});
_sceneLoaders[path] = loader;
return loader;
}
#endregion
#region
/// <summary>
/// 根据给定路径加载场景,并创建其节点实例。
/// </summary>
/// <typeparam name="T">期望返回的节点类型必须是Node的子类。</typeparam>
/// <param name="path">场景文件的相对路径。</param>
/// <returns>新创建的场景根节点实例如果加载失败则返回null。</returns>
public T? CreateInstance<T>(string path) where T : Node
{
var scene = GetSceneLoader(path).Value;
return scene.Instantiate<T>();
}
/// <summary>
/// 注册或获取一个用于创建特定场景实例的工厂函数。
/// 如果已存在相同路径的工厂函数,则尝试转换后复用。
/// </summary>
/// <typeparam name="T">目标场景根节点的类型。</typeparam>
/// <param name="id">场景文件的id。</param>
/// <returns>用于创建该场景实例的Func委托。</returns>
/// <exception cref="InvalidCastException">当已有工厂不是Func&lt;T&gt;类型时抛出。</exception>
/// <exception cref="InvalidOperationException">当无法加载场景或实例化失败时抛出。</exception>
public Func<T> GetOrRegisterSceneFactory<T>(AssetCatalog.SceneId id) where T : Node
{
var path = id.Path;
if (_sceneFactories.TryGetValue(path, out var d))
return d as Func<T> ??
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<T>()
?? throw new InvalidOperationException($"Instantiate failed: {path}");
};
_sceneFactories[path] = factory;
return factory;
}
#endregion
#region
/// <summary>
/// 注册或获取一个用于加载或复制资源的工厂函数。
/// 可选择是否每次调用都返回副本Duplicate适用于需要独立状态的资源。
/// </summary>
/// <typeparam name="T">资源的具体类型。</typeparam>
/// <param name="id">资源文件的id。</param>
/// <param name="duplicate">是否每次都返回资源的一个副本默认为false。</param>
/// <returns>用于加载或复制资源的Func委托。</returns>
/// <exception cref="InvalidCastException">当已有工厂不是Func&lt;T&gt;类型时抛出。</exception>
/// <exception cref="InvalidOperationException">当资源加载失败时抛出。</exception>
public Func<T> GetOrRegisterResourceFactory<T>(AssetCatalog.ResourceId id, bool duplicate = false)
where T : Resource
{
var path = id.Path;
if (_resourceFactories.TryGetValue(path, out var d))
return d as Func<T> ??
throw new InvalidCastException($"Factory for path '{path}' is not of type Func<{typeof(T)}>");
var factory = () =>
{
var res = LoadResource<T>(path)
?? throw new InvalidOperationException($"Load failed: {path}");
if (!duplicate) return res;
return res.Duplicate() as T ?? res;
};
_resourceFactories[path] = factory;
return factory;
}
#endregion
#region
/// <summary>
/// 预加载一组资源和场景到内存中以提升后续访问速度。
/// </summary>
/// <param name="paths">待预加载的资源路径集合。</param>
public void Preload(IEnumerable<string> paths)
{
foreach (var path in paths)
{
GetSceneLoader(path);
LoadResource<Resource>(path);
}
}
/// <summary>
/// 清除指定路径的所有相关缓存数据,包括资源、场景加载器及各类工厂。
/// </summary>
/// <param name="path">要卸载的资源路径。</param>
public void Unload(string path)
{
_loadedResources.Remove(path);
_sceneLoaders.Remove(path);
_sceneFactories.Remove(path);
_resourceFactories.Remove(path);
}
/// <summary>
/// 清空所有当前系统的资源缓存、加载器和工厂列表。
/// </summary>
public void ClearAll()
{
_loadedResources.Clear();
_sceneLoaders.Clear();
_sceneFactories.Clear();
_resourceFactories.Clear();
}
#endregion
}