diff --git a/GFramework.Core.Godot/architecture/AbstractArchitecture.cs b/GFramework.Core.Godot/architecture/AbstractArchitecture.cs new file mode 100644 index 0000000..49c3b5f --- /dev/null +++ b/GFramework.Core.Godot/architecture/AbstractArchitecture.cs @@ -0,0 +1,35 @@ +using GFramework.Core.architecture; + +namespace GFramework.Core.Godot.architecture; + +/// +/// 抽象架构类,为特定类型的架构提供基础实现框架 +/// +/// 架构的具体类型,必须继承自Architecture且能被实例化 +public abstract class AbstractArchitecture : Architecture where T : Architecture, new() +{ + /// + /// 初始化架构,按顺序注册模型、系统和工具 + /// + protected override void Init() + { + RegisterModels(); + RegisterSystems(); + RegisterUtilities(); + } + + /// + /// 注册工具抽象方法,由子类实现具体的工具注册逻辑 + /// + protected abstract void RegisterUtilities(); + + /// + /// 注册系统抽象方法,由子类实现具体系统注册逻辑 + /// + protected abstract void RegisterSystems(); + + /// + /// 注册模型抽象方法,由子类实现具体模型注册逻辑 + /// + protected abstract void RegisterModels(); +} diff --git a/GFramework.Core.Godot/system/AbstractAssetCatalogSystem.cs b/GFramework.Core.Godot/system/AbstractAssetCatalogSystem.cs new file mode 100644 index 0000000..bc91f9a --- /dev/null +++ b/GFramework.Core.Godot/system/AbstractAssetCatalogSystem.cs @@ -0,0 +1,89 @@ +using GFramework.Core.system; + +namespace GFramework.Core.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 RegisterResource(string key, string path) + { + if (_resources.ContainsKey(key)) + throw new InvalidOperationException($"Resource key duplicated: {key}"); + + _resources[key] = new AssetCatalog.ResourceId(path); + } + + #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.Core.Godot/system/AbstractResourceFactorySystem.cs b/GFramework.Core.Godot/system/AbstractResourceFactorySystem.cs new file mode 100644 index 0000000..3205f7b --- /dev/null +++ b/GFramework.Core.Godot/system/AbstractResourceFactorySystem.cs @@ -0,0 +1,90 @@ +using GFramework.Core.extensions; +using GFramework.Core.system; +using Godot; + +namespace GFramework.Core.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(); + RegisterResources(); + // 执行预加载 + _registry.PreloadAll(); + } + + /// + /// 注册系统所需的各种资源类型。由子类实现具体注册逻辑。 + /// + protected abstract void RegisterResources(); + + /// + /// 获取指定类型的资源工厂函数。 + /// + /// 要获取工厂的资源类型 + /// 返回创建指定类型资源的工厂函数 + public Func Get() => _registry!.Resolve(); + + #region Register Helpers(声明式) + + /// + /// 注册场景资源工厂。 + /// 根据场景键名获取场景ID,并将场景加载工厂注册到注册表中。 + /// + /// 场景节点类型,必须继承自Node + /// 场景在资产目录中的键名 + /// 是否需要预加载该场景资源 + private void RegisterScene( + string sceneKey, + bool preload = false) + where T : Node + { + var id = _assetCatalogSystem!.GetScene(sceneKey); + + _registry!.Register( + _resourceLoadSystem!.GetOrRegisterSceneFactory(id), + preload + ); + } + + /// + /// 注册普通资源工厂。 + /// 根据资源键名获取资源ID,并将资源加载工厂注册到注册表中。 + /// + /// 资源类型,必须继承自Resource + /// 资源在资产目录中的键名 + /// 是否需要复制资源实例 + /// 是否需要预加载该资源 + private void RegisterResource( + string resourceKey, + bool duplicate = false, + bool preload = false) + where T : Resource + { + var id = _assetCatalogSystem!.GetResource(resourceKey); + + _registry!.Register( + _resourceLoadSystem!.GetOrRegisterResourceFactory(id, duplicate), + preload + ); + } + + #endregion +} diff --git a/GFramework.Core.Godot/system/AssetCatalog.cs b/GFramework.Core.Godot/system/AssetCatalog.cs new file mode 100644 index 0000000..19020e9 --- /dev/null +++ b/GFramework.Core.Godot/system/AssetCatalog.cs @@ -0,0 +1,20 @@ + +namespace GFramework.Core.Godot.system; + +/// +/// 资源目录类,用于定义和管理游戏中的场景和资源标识符 +/// +public static class AssetCatalog +{ + /// + /// 场景标识符结构体,用于唯一标识一个场景资源 + /// + /// 场景资源的路径 + public readonly record struct SceneId(string Path); + + /// + /// 资源标识符结构体,用于唯一标识一个游戏资源 + /// + /// 游戏资源的路径 + public readonly record struct ResourceId(string Path); +} diff --git a/GFramework.Core.Godot/system/IAssetCatalogSystem.cs b/GFramework.Core.Godot/system/IAssetCatalogSystem.cs new file mode 100644 index 0000000..6982a02 --- /dev/null +++ b/GFramework.Core.Godot/system/IAssetCatalogSystem.cs @@ -0,0 +1,37 @@ +using GFramework.Core.system; + +namespace GFramework.Core.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.Core.Godot/system/IResourceFactorySystem.cs b/GFramework.Core.Godot/system/IResourceFactorySystem.cs new file mode 100644 index 0000000..eb84e49 --- /dev/null +++ b/GFramework.Core.Godot/system/IResourceFactorySystem.cs @@ -0,0 +1,16 @@ +using GFramework.Core.system; + +namespace GFramework.Core.Godot.system; + +/// +/// 资源工厂系统接口,用于获取指定类型的资源创建函数 +/// +public interface IResourceFactorySystem : ISystem +{ + /// + /// 获取指定类型T的资源创建函数 + /// + /// 要获取创建函数的资源类型 + /// 返回一个创建T类型实例的函数委托 + Func Get(); +} diff --git a/GFramework.Core.Godot/system/IResourceLoadSystem.cs b/GFramework.Core.Godot/system/IResourceLoadSystem.cs new file mode 100644 index 0000000..6df2893 --- /dev/null +++ b/GFramework.Core.Godot/system/IResourceLoadSystem.cs @@ -0,0 +1,68 @@ +using GFramework.Core.system; +using Godot; + +namespace GFramework.Core.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.Core.Godot/system/ResourceFactory.cs b/GFramework.Core.Godot/system/ResourceFactory.cs new file mode 100644 index 0000000..fd680fb --- /dev/null +++ b/GFramework.Core.Godot/system/ResourceFactory.cs @@ -0,0 +1,102 @@ +namespace GFramework.Core.Godot.system; + +/// +/// 资源工厂类,用于注册和解析各种资源的创建工厂 +/// +public static class ResourceFactory +{ + /// + /// 可预加载条目接口,定义了是否需要预加载以及执行工厂的方法 + /// + private interface IPreloadableEntry + { + /// + /// 获取一个值,表示该资源是否需要预加载 + /// + bool Preload { get; } + + /// + /// 执行与该条目关联的工厂方法 + /// + void ExecuteFactory(); + } + + /// + /// 表示一个具体的资源工厂条目,实现 IPreloadableEntry 接口 + /// + /// 资源类型 + private sealed class Entry(Func factory, bool preload) : IPreloadableEntry + { + /// + /// 获取用于创建资源的工厂函数 + /// + public Func Factory { get; } = factory; + + /// + /// 获取一个值,表示该资源是否需要预加载 + /// + public bool Preload { get; } = preload; + + /// + /// 执行工厂函数以创建资源实例 + /// + public void ExecuteFactory() => Factory(); + } + + /// + /// 工厂注册表,管理所有已注册的资源工厂 + /// + internal sealed class Registry + { + /// + /// 存储所有已注册的工厂函数,键为资源类型,值为对应的工厂条目对象 + /// + private readonly Dictionary _factories = new(); + + /// + /// 注册指定类型的资源工厂 + /// + /// 要注册的资源类型 + /// 创建该类型资源的工厂函数 + /// 是否需要预加载该资源,默认为false + public void Register(Func factory, bool preload = false) + { + _factories[typeof(T)] = new Entry(factory, preload); + } + + /// + /// 解析并获取指定类型的工厂函数 + /// + /// 要获取工厂函数的资源类型 + /// 指定类型的工厂函数 + /// 当指定类型的工厂未注册时抛出异常 + public Func Resolve() + { + // 尝试从字典中查找对应类型的工厂条目 + if (_factories.TryGetValue(typeof(T), out var obj) + && obj is Entry entry) + return entry.Factory; + + // 若未找到则抛出异常 + throw new InvalidOperationException($"Factory not registered: {typeof(T).Name}"); + } + + /// + /// 预加载所有标记为需要预加载的资源 + /// + public void PreloadAll() + { + // 遍历所有已注册的工厂 + foreach (var entry in _factories.Values) + { + // 检查当前条目是否支持预加载且被标记为需预加载 + if (entry is IPreloadableEntry preloadable && preloadable.Preload) + { + // 执行其工厂方法进行预加载 + preloadable.ExecuteFactory(); + } + } + } + } + +} diff --git a/GFramework.Core.Godot/system/ResourceLoadSystem.cs b/GFramework.Core.Godot/system/ResourceLoadSystem.cs new file mode 100644 index 0000000..8cd6912 --- /dev/null +++ b/GFramework.Core.Godot/system/ResourceLoadSystem.cs @@ -0,0 +1,209 @@ +using GFramework.Core.system; +using Godot; + +namespace GFramework.Core.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 +}